Merge pull request 'feat(tui): layout and interaction polish' (#33) from fix/tui-polish into main
Reviewed-on: #33
This commit was merged in pull request #33.
This commit is contained in:
@@ -6,7 +6,7 @@ require (
|
||||
github.com/atotto/clipboard v0.1.4
|
||||
github.com/charmbracelet/bubbles v1.0.0
|
||||
github.com/charmbracelet/bubbletea v1.3.10
|
||||
github.com/charmbracelet/lipgloss v1.1.0
|
||||
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834
|
||||
github.com/go-chi/chi/v5 v5.2.5
|
||||
github.com/oklog/ulid/v2 v2.1.1
|
||||
github.com/spf13/cobra v1.10.2
|
||||
@@ -14,33 +14,45 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/alecthomas/chroma/v2 v2.20.0 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.4.1 // indirect
|
||||
github.com/charmbracelet/glamour v1.0.0 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.11.6 // indirect
|
||||
github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
|
||||
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect
|
||||
github.com/charmbracelet/x/term v0.2.2 // indirect
|
||||
github.com/clipperhouse/displaywidth v0.9.0 // indirect
|
||||
github.com/clipperhouse/stringish v0.1.1 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.5.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/css v1.0.1 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.19 // indirect
|
||||
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/muesli/reflow v0.3.0 // indirect
|
||||
github.com/muesli/termenv v0.16.0 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/spf13/pflag v1.0.9 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
github.com/yuin/goldmark v1.7.13 // indirect
|
||||
github.com/yuin/goldmark-emoji v1.0.6 // indirect
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
|
||||
golang.org/x/net v0.38.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/text v0.3.8 // indirect
|
||||
golang.org/x/term v0.36.0 // indirect
|
||||
golang.org/x/text v0.30.0 // indirect
|
||||
modernc.org/libc v1.65.7 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
|
||||
@@ -1,19 +1,29 @@
|
||||
github.com/alecthomas/chroma/v2 v2.20.0 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw=
|
||||
github.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA=
|
||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/charmbracelet/bubbles v1.0.0 h1:12J8/ak/uCZEMQ6KU7pcfwceyjLlWsDLAxB5fXonfvc=
|
||||
github.com/charmbracelet/bubbles v1.0.0/go.mod h1:9d/Zd5GdnauMI5ivUIVisuEm3ave1XwXtD1ckyV6r3E=
|
||||
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
|
||||
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
|
||||
github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk=
|
||||
github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk=
|
||||
github.com/charmbracelet/glamour v1.0.0 h1:AWMLOVFHTsysl4WV8T8QgkQ0s/ZNZo7CiE4WKhk8l08=
|
||||
github.com/charmbracelet/glamour v1.0.0/go.mod h1:DSdohgOBkMr2ZQNhw4LZxSGpx3SvpeujNoXrQyH2hxo=
|
||||
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
||||
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
||||
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 h1:ZR7e0ro+SZZiIZD7msJyA+NjkCNNavuiPBLgerbOziE=
|
||||
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA=
|
||||
github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=
|
||||
github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q=
|
||||
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf h1:rLG0Yb6MQSDKdB52aGX55JT1oi0P0Kuaj7wi1bLUpnI=
|
||||
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf/go.mod h1:B3UgsnsBZS/eX42BlaNiJkD1pPOUa+oF1IYC6Yd2CEU=
|
||||
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
|
||||
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
|
||||
github.com/clipperhouse/displaywidth v0.9.0 h1:Qb4KOhYwRiN3viMv1v/3cTBlz3AcAZX3+y9OLhMtAtA=
|
||||
@@ -23,6 +33,8 @@ github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEX
|
||||
github.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U=
|
||||
github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
||||
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||
@@ -33,6 +45,8 @@ github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17k
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
||||
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
|
||||
@@ -41,12 +55,17 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
|
||||
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
||||
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
@@ -56,6 +75,8 @@ github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNs
|
||||
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
@@ -65,21 +86,34 @@ github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA=
|
||||
github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
github.com/yuin/goldmark-emoji v1.0.6 h1:QWfF2FYaXwL74tfGOW5izeiZepUDroDJfWubQI9HTHs=
|
||||
github.com/yuin/goldmark-emoji v1.0.6/go.mod h1:ukxJDKFpdFb5x0a5HqbdlcKtebh086iJpI31LTKmWuA=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
|
||||
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
|
||||
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
|
||||
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
|
||||
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
|
||||
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
|
||||
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
modernc.org/cc/v4 v4.26.1 h1:+X5NtzVBn0KgsBCBe+xkDC7twLb/jNVj9FPgiwSQO3s=
|
||||
modernc.org/cc/v4 v4.26.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
|
||||
+117
-13
@@ -64,9 +64,16 @@ func matchesIntent(e *db.Entity, i intent) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type cardGroup struct {
|
||||
label string
|
||||
start int
|
||||
count int
|
||||
}
|
||||
|
||||
type cardsModel struct {
|
||||
entities []*db.Entity
|
||||
filtered []*db.Entity
|
||||
groups []cardGroup
|
||||
cursor int
|
||||
offset int
|
||||
height int
|
||||
@@ -91,24 +98,69 @@ func (c *cardsModel) setIntent(i intent) {
|
||||
}
|
||||
|
||||
func (c *cardsModel) applyFilter() {
|
||||
c.filtered = nil
|
||||
var pinned, rest []*db.Entity
|
||||
for _, e := range c.entities {
|
||||
if !matchesIntent(e, c.intent) {
|
||||
continue
|
||||
}
|
||||
if e.Pinned {
|
||||
pinned = append(pinned, e)
|
||||
} else {
|
||||
rest = append(rest, e)
|
||||
}
|
||||
}
|
||||
c.filtered = append(pinned, rest...)
|
||||
c.filtered, c.groups = sortAndGroupCards(c.entities, c.intent)
|
||||
if c.cursor >= len(c.filtered) {
|
||||
c.cursor = max(0, len(c.filtered)-1)
|
||||
}
|
||||
}
|
||||
|
||||
func sortAndGroupCards(entities []*db.Entity, intentFilter intent) ([]*db.Entity, []cardGroup) {
|
||||
if intentFilter != intentAll {
|
||||
var pinned, rest []*db.Entity
|
||||
for _, e := range entities {
|
||||
if !matchesIntent(e, intentFilter) {
|
||||
continue
|
||||
}
|
||||
if e.Pinned {
|
||||
pinned = append(pinned, e)
|
||||
} else {
|
||||
rest = append(rest, e)
|
||||
}
|
||||
}
|
||||
return append(pinned, rest...), nil
|
||||
}
|
||||
|
||||
var pinned, grab, read, fill []*db.Entity
|
||||
for _, e := range entities {
|
||||
if e.Pinned {
|
||||
pinned = append(pinned, e)
|
||||
} else {
|
||||
switch {
|
||||
case matchesIntent(e, intentGrab):
|
||||
grab = append(grab, e)
|
||||
case matchesIntent(e, intentRead):
|
||||
read = append(read, e)
|
||||
case matchesIntent(e, intentFill):
|
||||
fill = append(fill, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var filtered []*db.Entity
|
||||
var groups []cardGroup
|
||||
for _, bucket := range []struct {
|
||||
label string
|
||||
entities []*db.Entity
|
||||
}{
|
||||
{"pinned", pinned},
|
||||
{"grab", grab},
|
||||
{"read", read},
|
||||
{"fill", fill},
|
||||
} {
|
||||
if len(bucket.entities) == 0 {
|
||||
continue
|
||||
}
|
||||
groups = append(groups, cardGroup{
|
||||
label: bucket.label,
|
||||
start: len(filtered),
|
||||
count: len(bucket.entities),
|
||||
})
|
||||
filtered = append(filtered, bucket.entities...)
|
||||
}
|
||||
|
||||
return filtered, groups
|
||||
}
|
||||
|
||||
func (c *cardsModel) setSize(width, height int) {
|
||||
c.width = width
|
||||
c.height = height
|
||||
@@ -166,6 +218,9 @@ func (c cardsModel) view(width int) string {
|
||||
if len(c.filtered) == 0 {
|
||||
return statusStyle.Render("no cards")
|
||||
}
|
||||
if len(c.groups) > 0 {
|
||||
return c.groupedView(width)
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
visible := c.visibleCount()
|
||||
@@ -188,6 +243,55 @@ func (c cardsModel) view(width int) string {
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (c cardsModel) groupedView(width int) string {
|
||||
entityWidth := width - 4 - dateGutterWidth
|
||||
|
||||
type displayLine struct {
|
||||
text string
|
||||
entityIdx int
|
||||
}
|
||||
|
||||
var lines []displayLine
|
||||
for _, g := range c.groups {
|
||||
for i := 0; i < g.count; i++ {
|
||||
eIdx := g.start + i
|
||||
var gutter string
|
||||
if i == 0 {
|
||||
gutter = gutterStyle.Render(padRight(g.label, 6) + " │ ")
|
||||
} else {
|
||||
gutter = gutterStyle.Render(" │ ")
|
||||
}
|
||||
line := gutter + renderCard(c.filtered[eIdx], entityWidth)
|
||||
lines = append(lines, displayLine{text: line, entityIdx: eIdx})
|
||||
}
|
||||
}
|
||||
|
||||
visible := c.visibleCount()
|
||||
offset := c.offset
|
||||
if c.cursor < offset {
|
||||
offset = c.cursor
|
||||
}
|
||||
if c.cursor >= offset+visible {
|
||||
offset = c.cursor - visible + 1
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
end := min(offset+visible, len(lines))
|
||||
for i := offset; i < end; i++ {
|
||||
dl := lines[i]
|
||||
if dl.entityIdx == c.cursor {
|
||||
b.WriteString(selectedItemStyle.Render(" " + dl.text))
|
||||
} else {
|
||||
b.WriteString(listItemStyle.Render(dl.text))
|
||||
}
|
||||
if i < end-1 {
|
||||
b.WriteString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (c cardsModel) visibleCount() int {
|
||||
if c.height <= 0 {
|
||||
return 20
|
||||
|
||||
@@ -58,6 +58,8 @@ type tagsLoadedMsg struct {
|
||||
tags []db.TagCount
|
||||
}
|
||||
|
||||
type statusClearMsg struct{ seq int }
|
||||
|
||||
type editorFinishedMsg struct {
|
||||
err error
|
||||
}
|
||||
@@ -267,3 +269,9 @@ func copyResolved(store *db.Store, entityID string, resolved string) tea.Cmd {
|
||||
return templateCopiedMsg{}
|
||||
}
|
||||
}
|
||||
|
||||
func clearStatusAfter(d time.Duration, seq int) tea.Cmd {
|
||||
return tea.Tick(d, func(time.Time) tea.Msg {
|
||||
return statusClearMsg{seq: seq}
|
||||
})
|
||||
}
|
||||
|
||||
+44
-3
@@ -6,6 +6,7 @@ import (
|
||||
"time"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/glamour"
|
||||
|
||||
"github.com/lerko/nib/internal/db"
|
||||
"github.com/lerko/nib/internal/display"
|
||||
@@ -61,6 +62,17 @@ func (d detailModel) update(msg tea.KeyMsg) (detailModel, tea.Cmd) {
|
||||
}
|
||||
case "down", "j":
|
||||
d.scroll++
|
||||
case "pgdown", "ctrl+d":
|
||||
d.scroll += d.height
|
||||
case "pgup", "ctrl+u":
|
||||
d.scroll -= d.height
|
||||
if d.scroll < 0 {
|
||||
d.scroll = 0
|
||||
}
|
||||
case "home", "g":
|
||||
d.scroll = 0
|
||||
case "end", "G":
|
||||
d.scroll = 1<<31 - 1
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
@@ -98,7 +110,20 @@ func (d detailModel) previewView(width int) string {
|
||||
b.WriteString("\n")
|
||||
}
|
||||
|
||||
b.WriteString(detailBodyStyle.Render(e.Body))
|
||||
bodyWidth := width - 4
|
||||
if bodyWidth < 20 {
|
||||
bodyWidth = 20
|
||||
}
|
||||
r, _ := glamour.NewTermRenderer(
|
||||
glamour.WithStylePath("dark"),
|
||||
glamour.WithWordWrap(bodyWidth),
|
||||
)
|
||||
rendered, err := r.Render(e.Body)
|
||||
if err != nil {
|
||||
rendered = e.Body
|
||||
}
|
||||
rendered = strings.TrimRight(rendered, "\n")
|
||||
b.WriteString(detailBodyStyle.Render(rendered))
|
||||
b.WriteString("\n")
|
||||
|
||||
if e.CardType != nil {
|
||||
@@ -142,8 +167,24 @@ func (d detailModel) previewView(width int) string {
|
||||
b.WriteString(idStyle.Render(meta))
|
||||
|
||||
lines := strings.Split(b.String(), "\n")
|
||||
if d.scroll > 0 && d.scroll < len(lines) {
|
||||
lines = lines[d.scroll:]
|
||||
totalLines := len(lines)
|
||||
|
||||
maxScroll := totalLines - d.height
|
||||
if maxScroll < 0 {
|
||||
maxScroll = 0
|
||||
}
|
||||
scroll := d.scroll
|
||||
if scroll > maxScroll {
|
||||
scroll = maxScroll
|
||||
}
|
||||
|
||||
if totalLines > d.height && d.height > 0 && len(lines) > 0 {
|
||||
indicator := idStyle.Render(fmt.Sprintf(" %d/%d", scroll+1, totalLines))
|
||||
lines[0] = lines[0] + indicator
|
||||
}
|
||||
|
||||
if scroll > 0 && scroll < totalLines {
|
||||
lines = lines[scroll:]
|
||||
}
|
||||
if d.height > 0 && len(lines) > d.height {
|
||||
lines = lines[:d.height]
|
||||
|
||||
+14
-2
@@ -103,11 +103,23 @@ func (i inputModel) updateKey(msg tea.KeyMsg) inputModel {
|
||||
|
||||
func (i inputModel) view(width int) string {
|
||||
var b strings.Builder
|
||||
b.WriteString(drawerBorderStyle.Render(strings.Repeat("─", width)))
|
||||
label := "capture"
|
||||
prefix := "── "
|
||||
suffix := " "
|
||||
dashCount := width - len(prefix) - len(label) - len(suffix)
|
||||
if dashCount < 0 {
|
||||
dashCount = 0
|
||||
}
|
||||
b.WriteString(drawerBorderStyle.Render(prefix) +
|
||||
hintDescStyle.Render(label) +
|
||||
drawerBorderStyle.Render(suffix+strings.Repeat("─", dashCount)))
|
||||
b.WriteString("\n")
|
||||
b.WriteString(i.ti.View())
|
||||
b.WriteString("\n")
|
||||
b.WriteString(drawerHintsStyle.Render("enter:submit esc:cancel ?:search -:todo @:event !:reminder"))
|
||||
b.WriteString(drawerHintsStyle.Render(renderHints([]hint{
|
||||
{"enter", "submit"}, {"esc", "cancel"}, {"?", "search"},
|
||||
{"-", "todo"}, {"@", "event"}, {"!", "reminder"},
|
||||
})))
|
||||
b.WriteString("\n")
|
||||
b.WriteString(i.renderPreview(width))
|
||||
return b.String()
|
||||
|
||||
+46
-55
@@ -3,6 +3,7 @@ package tui
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
@@ -10,6 +11,8 @@ import (
|
||||
"github.com/lerko/nib/internal/db"
|
||||
)
|
||||
|
||||
const statusTimeout = 2 * time.Second
|
||||
|
||||
type viewState int
|
||||
|
||||
const (
|
||||
@@ -91,8 +94,9 @@ type model struct {
|
||||
searchQuery string
|
||||
searchTags []string
|
||||
|
||||
status string
|
||||
err error
|
||||
status string
|
||||
statusSeq int
|
||||
err error
|
||||
}
|
||||
|
||||
func newModel(store *db.Store) model {
|
||||
@@ -108,6 +112,12 @@ func newModel(store *db.Store) model {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *model) setStatus(msg string) tea.Cmd {
|
||||
m.statusSeq++
|
||||
m.status = msg
|
||||
return clearStatusAfter(statusTimeout, m.statusSeq)
|
||||
}
|
||||
|
||||
func (m model) Init() tea.Cmd {
|
||||
return loadEntities(m.store, m.listParams())
|
||||
}
|
||||
@@ -140,20 +150,14 @@ func (m model) hasSearch() bool {
|
||||
|
||||
func (m *model) applySearch() {
|
||||
if m.mode == modeCards {
|
||||
filtered := filterEntities(m.cards.entities, m.searchQuery, m.searchTags)
|
||||
m.cards.filtered = nil
|
||||
var pinned, rest []*db.Entity
|
||||
for _, e := range filtered {
|
||||
if !matchesIntent(e, m.cards.intent) {
|
||||
continue
|
||||
}
|
||||
if e.Pinned {
|
||||
pinned = append(pinned, e)
|
||||
} else {
|
||||
rest = append(rest, e)
|
||||
searchFiltered := filterEntities(m.cards.entities, m.searchQuery, m.searchTags)
|
||||
var intentFiltered []*db.Entity
|
||||
for _, e := range searchFiltered {
|
||||
if matchesIntent(e, m.cards.intent) {
|
||||
intentFiltered = append(intentFiltered, e)
|
||||
}
|
||||
}
|
||||
m.cards.filtered = append(pinned, rest...)
|
||||
m.cards.filtered, m.cards.groups = sortAndGroupCards(intentFiltered, m.cards.intent)
|
||||
if m.cards.cursor >= len(m.cards.filtered) {
|
||||
m.cards.cursor = max(0, len(m.cards.filtered)-1)
|
||||
}
|
||||
@@ -191,38 +195,31 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.state = stateList
|
||||
m.input.reset()
|
||||
m.recalcSizes()
|
||||
m.status = "created"
|
||||
return m, loadEntities(m.store, m.listParams())
|
||||
return m, tea.Batch(loadEntities(m.store, m.listParams()), m.setStatus("created"))
|
||||
|
||||
case entityDeletedMsg:
|
||||
m.status = "deleted"
|
||||
m.state = stateList
|
||||
return m, loadEntities(m.store, m.listParams())
|
||||
return m, tea.Batch(loadEntities(m.store, m.listParams()), m.setStatus("deleted"))
|
||||
|
||||
case entityUpdatedMsg:
|
||||
m.status = msg.action
|
||||
if m.state == stateDetail {
|
||||
m.detail.setEntity(msg.entity)
|
||||
}
|
||||
return m, loadEntities(m.store, m.listParams())
|
||||
return m, tea.Batch(loadEntities(m.store, m.listParams()), m.setStatus(msg.action))
|
||||
|
||||
case entityPromotedMsg:
|
||||
m.status = fmt.Sprintf("promoted → %s", msg.cardType)
|
||||
m.state = stateList
|
||||
return m, loadEntities(m.store, m.listParams())
|
||||
return m, tea.Batch(loadEntities(m.store, m.listParams()), m.setStatus(fmt.Sprintf("promoted → %s", msg.cardType)))
|
||||
|
||||
case entityDemotedMsg:
|
||||
m.status = "demoted → fluid"
|
||||
return m, m.reloadDetail(msg.id)
|
||||
return m, tea.Batch(m.reloadDetail(msg.id), m.setStatus("demoted → fluid"))
|
||||
|
||||
case entityCopiedMsg:
|
||||
m.status = "copied"
|
||||
return m, nil
|
||||
return m, m.setStatus("copied")
|
||||
|
||||
case entityAbsorbedMsg:
|
||||
m.status = "absorbed"
|
||||
m.state = stateList
|
||||
return m, loadEntities(m.store, m.listParams())
|
||||
return m, tea.Batch(loadEntities(m.store, m.listParams()), m.setStatus("absorbed"))
|
||||
|
||||
case absorbSourcesLoadedMsg:
|
||||
m.absorb = newAbsorbModel(msg.targetID)
|
||||
@@ -232,14 +229,12 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return m, nil
|
||||
|
||||
case stepsPersistedMsg:
|
||||
m.status = "steps saved"
|
||||
m.detail.mode = detailPreview
|
||||
return m, m.reloadDetail(m.detail.entity.ID)
|
||||
return m, tea.Batch(m.reloadDetail(m.detail.entity.ID), m.setStatus("steps saved"))
|
||||
|
||||
case templateCopiedMsg:
|
||||
m.status = "copied resolved"
|
||||
m.detail.mode = detailPreview
|
||||
return m, loadEntities(m.store, m.listParams())
|
||||
return m, tea.Batch(loadEntities(m.store, m.listParams()), m.setStatus("copied resolved"))
|
||||
|
||||
case tagsLoadedMsg:
|
||||
m.filter.setTags(msg.tags)
|
||||
@@ -249,10 +244,9 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
case editorFinishedMsg:
|
||||
if msg.err != nil {
|
||||
m.err = msg.err
|
||||
} else {
|
||||
m.status = "updated"
|
||||
return m, m.reloadAfterEdit()
|
||||
}
|
||||
return m, m.reloadAfterEdit()
|
||||
return m, tea.Batch(m.reloadAfterEdit(), m.setStatus("updated"))
|
||||
|
||||
case confirmTimeoutMsg:
|
||||
if m.state == stateConfirm {
|
||||
@@ -261,6 +255,12 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
}
|
||||
return m, nil
|
||||
|
||||
case statusClearMsg:
|
||||
if msg.seq == m.statusSeq {
|
||||
m.status = ""
|
||||
}
|
||||
return m, nil
|
||||
|
||||
case errMsg:
|
||||
m.err = msg.err
|
||||
return m, nil
|
||||
@@ -414,8 +414,7 @@ func (m model) updateKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
case "s":
|
||||
if m.mode == modeCards && m.state == stateList {
|
||||
m.cardsSort = m.cardsSort.next()
|
||||
m.status = "sort: " + m.cardsSort.String()
|
||||
return m, loadEntities(m.store, m.listParams())
|
||||
return m, tea.Batch(loadEntities(m.store, m.listParams()), m.setStatus("sort: "+m.cardsSort.String()))
|
||||
}
|
||||
return m, nil
|
||||
|
||||
@@ -531,8 +530,7 @@ func (m model) updateKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
e := m.selectedEntity()
|
||||
if e != nil {
|
||||
if e.CardType != nil {
|
||||
m.status = "target must be fluid"
|
||||
return m, nil
|
||||
return m, m.setStatus("target must be fluid")
|
||||
}
|
||||
return m, loadAbsorbSources(m.store, e.ID)
|
||||
}
|
||||
@@ -542,8 +540,7 @@ func (m model) updateKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
e := m.selectedEntity()
|
||||
if e != nil {
|
||||
if e.CardType != nil {
|
||||
m.status = "already a card"
|
||||
return m, nil
|
||||
return m, m.setStatus("already a card")
|
||||
}
|
||||
m.promote = newPromoteModel(e.ID, e.Body)
|
||||
m.state = statePromote
|
||||
@@ -554,8 +551,7 @@ func (m model) updateKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
case "D":
|
||||
if m.state == stateDetail && m.detail.entity != nil {
|
||||
if m.detail.entity.CardType == nil {
|
||||
m.status = "already fluid"
|
||||
return m, nil
|
||||
return m, m.setStatus("already fluid")
|
||||
}
|
||||
return m, demoteEntity(m.store, m.detail.entity.ID)
|
||||
}
|
||||
@@ -758,6 +754,8 @@ func (m model) View() string {
|
||||
header := m.headerView()
|
||||
footer := m.footerView()
|
||||
|
||||
content = lipgloss.NewStyle().Width(m.width).Height(m.contentHeight()).Render(content)
|
||||
|
||||
return header + "\n" + content + "\n" + footer
|
||||
}
|
||||
|
||||
@@ -777,16 +775,13 @@ func (m model) listContent() string {
|
||||
}
|
||||
|
||||
func (m model) headerView() string {
|
||||
header := titleStyle.Render("nib")
|
||||
|
||||
modeName := "stream"
|
||||
if m.mode == modeCards {
|
||||
modeName = "cards"
|
||||
}
|
||||
header += " " + modeStyle.Render(modeName)
|
||||
header := titleStyle.Render("nib") + " "
|
||||
header += renderTab("stream", "1", m.mode == modeStream)
|
||||
header += " " + separatorStyle.Render("│") + " "
|
||||
header += renderTab("cards", "2", m.mode == modeCards)
|
||||
|
||||
if m.filterTag != "" {
|
||||
header += " " + filterPillStyle.Render("#"+m.filterTag)
|
||||
header += " " + filterPillStyle.Render("#"+m.filterTag)
|
||||
}
|
||||
|
||||
if m.hasSearch() {
|
||||
@@ -824,10 +819,6 @@ func (m model) footerView() string {
|
||||
return errorStyle.Render("error: " + m.err.Error())
|
||||
}
|
||||
|
||||
if m.status != "" {
|
||||
return statusStyle.Render(m.status) + " " + helpStyle.Render(contextHints(m))
|
||||
}
|
||||
|
||||
return renderStatusBar(m, m.width)
|
||||
}
|
||||
|
||||
|
||||
+49
-19
@@ -2,24 +2,54 @@ package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
type hint struct {
|
||||
key string
|
||||
desc string
|
||||
}
|
||||
|
||||
func renderHints(hints []hint) string {
|
||||
parts := make([]string, len(hints))
|
||||
for i, h := range hints {
|
||||
parts[i] = hintKeyStyle.Render(h.key) + " " + hintDescStyle.Render(h.desc)
|
||||
}
|
||||
return strings.Join(parts, " ")
|
||||
}
|
||||
|
||||
func renderTab(label, key string, active bool) string {
|
||||
if active {
|
||||
return hintKeyStyle.Render(label) + " " + hintDescStyle.Render(key)
|
||||
}
|
||||
return hintDescStyle.Render(label) + " " + hintKeyStyle.Render(key)
|
||||
}
|
||||
|
||||
func renderStatusBar(m model, width int) string {
|
||||
left := countText(m)
|
||||
right := contextHints(m)
|
||||
var leftParts []string
|
||||
|
||||
leftRendered := statusStyle.Render(left)
|
||||
rightRendered := helpStyle.Render(right)
|
||||
if m.state == stateList {
|
||||
leftParts = append(leftParts, renderTab("capture", "a", false))
|
||||
}
|
||||
|
||||
gap := width - lipgloss.Width(leftRendered) - lipgloss.Width(rightRendered)
|
||||
if m.status != "" {
|
||||
leftParts = append(leftParts, statusStyle.Render(m.status))
|
||||
} else {
|
||||
leftParts = append(leftParts, statusStyle.Render(countText(m)))
|
||||
}
|
||||
|
||||
leftRendered := strings.Join(leftParts, " "+separatorStyle.Render("│")+" ")
|
||||
right := renderHints(contextHints(m))
|
||||
|
||||
gap := width - lipgloss.Width(leftRendered) - lipgloss.Width(right)
|
||||
if gap < 0 {
|
||||
gap = 0
|
||||
}
|
||||
|
||||
pad := lipgloss.NewStyle().Width(gap).Render("")
|
||||
return leftRendered + pad + rightRendered
|
||||
return leftRendered + pad + right
|
||||
}
|
||||
|
||||
func countText(m model) string {
|
||||
@@ -35,37 +65,37 @@ func countText(m model) string {
|
||||
return fmt.Sprintf("%d entities", total)
|
||||
}
|
||||
|
||||
func contextHints(m model) string {
|
||||
func contextHints(m model) []hint {
|
||||
switch m.state {
|
||||
case stateDetail:
|
||||
switch m.detail.mode {
|
||||
case detailRun:
|
||||
return "space:toggle j/k:nav r:reset esc:save+exit"
|
||||
return []hint{{"space", "toggle"}, {"j/k", "nav"}, {"r", "reset"}, {"esc", "save+exit"}}
|
||||
case detailFill:
|
||||
return "tab:next shift+tab:prev enter:copy esc:cancel"
|
||||
return []hint{{"tab", "next"}, {"⇧tab", "prev"}, {"enter", "copy"}, {"esc", "cancel"}}
|
||||
default:
|
||||
return "p:promote D:demote c:copy e:edit r:run f:fill !:pin esc:back"
|
||||
return []hint{{"p", "promote"}, {"D", "demote"}, {"c", "copy"}, {"e", "edit"}, {"r", "run"}, {"f", "fill"}, {"!", "pin"}, {"esc", "back"}}
|
||||
}
|
||||
case stateInput:
|
||||
return ""
|
||||
return nil
|
||||
case stateTagFilter:
|
||||
return "j/k:nav enter:select esc:cancel"
|
||||
return []hint{{"j/k", "nav"}, {"enter", "select"}, {"esc", "cancel"}}
|
||||
case stateConfirm:
|
||||
return "y:confirm n:cancel"
|
||||
return []hint{{"y", "confirm"}, {"n", "cancel"}}
|
||||
case statePromote:
|
||||
return "j/k:nav enter:select esc:cancel"
|
||||
return []hint{{"j/k", "nav"}, {"enter", "select"}, {"esc", "cancel"}}
|
||||
case stateAbsorb:
|
||||
return "j/k:nav enter:absorb esc:cancel"
|
||||
return []hint{{"j/k", "nav"}, {"enter", "absorb"}, {"esc", "cancel"}}
|
||||
default:
|
||||
if m.splitDetail {
|
||||
if m.focus == focusDetail {
|
||||
return "h:list c:copy e:edit p:promote D:demote !:pin esc:back"
|
||||
return []hint{{"h", "list"}, {"c", "copy"}, {"e", "edit"}, {"p", "promote"}, {"D", "demote"}, {"!", "pin"}, {"esc", "back"}}
|
||||
}
|
||||
return "l:detail a:add d:del #:filter esc:close ?:help q:quit"
|
||||
return []hint{{"l", "detail"}, {"d", "del"}, {"#", "filter"}, {"esc", "close"}, {"?", "help"}, {"q", "quit"}}
|
||||
}
|
||||
if m.mode == modeCards {
|
||||
return "1:stream 2:cards s:sort tab:intent a:add ?:help q:quit"
|
||||
return []hint{{"s", "sort"}, {"tab", "intent"}, {"?", "help"}, {"q", "quit"}}
|
||||
}
|
||||
return "1:stream 2:cards a:add/?search m:absorb d:del #:filter ?:help q:quit"
|
||||
return []hint{{"?", "search"}, {"m", "absorb"}, {"d", "del"}, {"#", "filter"}, {"?", "help"}, {"q", "quit"}}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,4 +122,11 @@ var (
|
||||
|
||||
separatorStyle = lipgloss.NewStyle().
|
||||
Foreground(dim)
|
||||
|
||||
hintKeyStyle = lipgloss.NewStyle().
|
||||
Foreground(highlight).
|
||||
Bold(true)
|
||||
|
||||
hintDescStyle = lipgloss.NewStyle().
|
||||
Foreground(dim)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user