[mirror] yet another tui rss reader github.com/olexsmir/smutok

feat: setup tui

olexsmir.xyz 54180a9d cfe45f84

verified
Changed files
+218 -15
internal
+17 -2
go.mod
··· 5 5 require ( 6 6 ariga.io/atlas v0.38.0 7 7 github.com/adrg/xdg v0.5.3 8 + github.com/charmbracelet/bubbles v0.21.0 8 9 github.com/charmbracelet/bubbletea v1.3.10 10 + github.com/charmbracelet/glamour v0.10.0 9 11 github.com/pelletier/go-toml/v2 v2.2.4 10 12 github.com/tidwall/gjson v1.18.0 11 13 github.com/urfave/cli/v3 v3.6.1 ··· 15 17 16 18 require ( 17 19 github.com/agext/levenshtein v1.2.1 // indirect 20 + github.com/alecthomas/chroma/v2 v2.14.0 // indirect 18 21 github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect 19 22 github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect 20 23 github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 24 + github.com/aymerick/douceur v0.2.0 // indirect 21 25 github.com/bmatcuk/doublestar v1.3.4 // indirect 22 26 github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect 23 - github.com/charmbracelet/lipgloss v1.1.0 // indirect 27 + github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 // indirect 24 28 github.com/charmbracelet/x/ansi v0.10.1 // indirect 25 - github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect 29 + github.com/charmbracelet/x/cellbuf v0.0.13 // indirect 30 + github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect 26 31 github.com/charmbracelet/x/term v0.2.1 // indirect 32 + github.com/dlclark/regexp2 v1.11.0 // indirect 27 33 github.com/dustin/go-humanize v1.0.1 // indirect 28 34 github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect 29 35 github.com/go-openapi/inflect v0.19.0 // indirect 30 36 github.com/google/go-cmp v0.7.0 // indirect 31 37 github.com/google/uuid v1.6.0 // indirect 38 + github.com/gorilla/css v1.0.1 // indirect 32 39 github.com/hashicorp/hcl/v2 v2.13.0 // indirect 33 40 github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 34 41 github.com/mattn/go-isatty v0.0.20 // indirect 35 42 github.com/mattn/go-localereader v0.0.1 // indirect 36 43 github.com/mattn/go-runewidth v0.0.16 // indirect 44 + github.com/microcosm-cc/bluemonday v1.0.27 // indirect 37 45 github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect 38 46 github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect 39 47 github.com/muesli/cancelreader v0.2.2 // indirect 48 + github.com/muesli/reflow v0.3.0 // indirect 40 49 github.com/muesli/termenv v0.16.0 // indirect 41 50 github.com/ncruces/go-strftime v0.1.9 // indirect 42 51 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect 43 52 github.com/rivo/uniseg v0.4.7 // indirect 53 + github.com/sergi/go-diff v1.3.1 // indirect 44 54 github.com/tidwall/match v1.1.1 // indirect 45 55 github.com/tidwall/pretty v1.2.0 // indirect 46 56 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 57 + github.com/yuin/goldmark v1.7.8 // indirect 58 + github.com/yuin/goldmark-emoji v1.0.5 // indirect 47 59 github.com/zclconf/go-cty v1.14.4 // indirect 48 60 github.com/zclconf/go-cty-yaml v1.1.0 // indirect 49 61 golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect 62 + golang.org/x/net v0.33.0 // indirect 50 63 golang.org/x/sys v0.36.0 // indirect 64 + golang.org/x/term v0.31.0 // indirect 51 65 golang.org/x/text v0.28.0 // indirect 66 + gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect 52 67 gopkg.in/yaml.v3 v3.0.1 // indirect 53 68 modernc.org/libc v1.66.10 // indirect 54 69 modernc.org/mathutil v1.7.1 // indirect
+54 -7
go.sum
··· 6 6 github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= 7 7 github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8= 8 8 github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= 9 + github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE= 10 + github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= 11 + github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E= 12 + github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I= 13 + github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= 14 + github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= 9 15 github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= 10 16 github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= 11 17 github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= 12 18 github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= 13 19 github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= 14 20 github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 21 + github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= 22 + github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= 23 + github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= 24 + github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= 15 25 github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0= 16 26 github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= 27 + github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs= 28 + github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg= 17 29 github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= 18 30 github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4= 19 31 github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= 20 32 github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= 21 - github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= 22 - github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= 33 + github.com/charmbracelet/glamour v0.10.0 h1:MtZvfwsYCx8jEPFJm3rIBFIMZUfUJ765oX8V6kXldcY= 34 + github.com/charmbracelet/glamour v0.10.0/go.mod h1:f+uf+I/ChNmqo087elLnVdCiVgjSKWuXa/l6NU2ndYk= 35 + github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 h1:ZR7e0ro+SZZiIZD7msJyA+NjkCNNavuiPBLgerbOziE= 36 + github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA= 23 37 github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ= 24 38 github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= 25 - github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8= 26 - github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= 39 + github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k= 40 + github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= 41 + github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ= 42 + github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= 43 + github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf h1:rLG0Yb6MQSDKdB52aGX55JT1oi0P0Kuaj7wi1bLUpnI= 44 + github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf/go.mod h1:B3UgsnsBZS/eX42BlaNiJkD1pPOUa+oF1IYC6Yd2CEU= 27 45 github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= 28 46 github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= 47 + github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 29 48 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 30 49 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 50 + github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= 51 + github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= 31 52 github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= 32 53 github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 33 54 github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= ··· 42 63 github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= 43 64 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 44 65 github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 66 + github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= 67 + github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= 45 68 github.com/hashicorp/hcl/v2 v2.13.0 h1:0Apadu1w6M11dyGFxWnmhhcMjkbAiKCv7G1r/2QgCNc= 46 69 github.com/hashicorp/hcl/v2 v2.13.0/go.mod h1:e4z5nxYlWNPdDSNYX+ph14EvWYMFm3eP0zIUqPc2jr0= 70 + github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= 71 + github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= 47 72 github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 48 73 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 74 + github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 75 + github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 49 76 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 50 77 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 51 78 github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= ··· 56 83 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 57 84 github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= 58 85 github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= 86 + github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= 59 87 github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= 60 88 github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 89 + github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= 90 + github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= 61 91 github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM= 62 92 github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= 63 93 github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= 64 94 github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= 65 95 github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= 66 96 github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= 97 + github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= 98 + github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= 67 99 github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= 68 100 github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= 69 101 github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= ··· 74 106 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 75 107 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= 76 108 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 109 + github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 77 110 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 78 111 github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 79 112 github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 80 - github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= 81 - github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 113 + github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= 114 + github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= 115 + github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 116 + github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 82 117 github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 83 118 github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 84 119 github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= ··· 91 126 github.com/urfave/cli/v3 v3.6.1/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= 92 127 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= 93 128 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= 129 + github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= 130 + github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= 131 + github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= 132 + github.com/yuin/goldmark-emoji v1.0.5 h1:EMVWyCGPlXJfUXBXpuMu+ii3TIaxbVBnEX9uaDC4cIk= 133 + github.com/yuin/goldmark-emoji v1.0.5/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U= 94 134 github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8= 95 135 github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= 96 136 github.com/zclconf/go-cty-yaml v1.1.0 h1:nP+jp0qPHv2IhUVqmQSzjvqAWcObN0KBkUl2rWBdig0= ··· 99 139 golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= 100 140 golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= 101 141 golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= 142 + golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= 143 + golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= 102 144 golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= 103 145 golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 104 146 golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 105 147 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 106 148 golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= 107 149 golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 150 + golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= 151 + golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= 108 152 golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= 109 153 golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= 110 154 golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= 111 155 golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= 112 - gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 113 156 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 157 + gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 158 + gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 159 + gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 160 + gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 114 161 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 115 162 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 116 163 modernc.org/cc/v4 v4.26.5 h1:xM3bX7Mve6G8K8b+T11ReenJOT+BmVqQj0FY5T4+5Y4=
+45
internal/store/sqlite_articles.go
··· 56 56 return err 57 57 } 58 58 59 + type Article struct { 60 + ID string 61 + Title string 62 + Href string 63 + Content string 64 + Author string 65 + IsRead bool 66 + IsStarred bool 67 + FeedID string 68 + FeedTitle string 69 + PublishedAt int64 70 + } 71 + 72 + func (s *Sqlite) GetArticles(ctx context.Context) ([]Article, error) { 73 + query := `--sql 74 + select a.id, a.title, a.href, a.content, a.author, s.is_read, s.is_starred, a.feed_id, f.title feed_name, a.published_at 75 + from articles a 76 + join article_statuses s on a.id = s.article_id 77 + join feeds f on f.id = a.feed_id 78 + where s.is_read = false 79 + order by a.published_at desc` 80 + 81 + rows, err := s.db.QueryContext(ctx, query) 82 + if err != nil { 83 + return nil, err 84 + } 85 + defer rows.Close() 86 + 87 + var res []Article 88 + for rows.Next() { 89 + var a Article 90 + if serr := rows.Scan(&a.ID, &a.Title, &a.Href, &a.Content, &a.Author, &a.IsRead, &a.IsStarred, &a.FeedID, &a.FeedTitle, &a.PublishedAt); serr != nil { 91 + return res, serr 92 + } 93 + 94 + res = append(res, a) 95 + } 96 + 97 + if err = rows.Err(); err != nil { 98 + return res, err 99 + } 100 + 101 + return res, nil 102 + } 103 + 59 104 func buildPlaceholdersAndArgs(in []string, prefixArgs ...any) (placeholders string, args []any) { 60 105 placeholders = strings.Repeat("?,", len(in)) 61 106 placeholders = placeholders[:len(placeholders)-1] // trim trailing comma
+18
internal/tui/fetcher.go
··· 1 + package tui 2 + 3 + import ( 4 + tea "github.com/charmbracelet/bubbletea" 5 + "olexsmir.xyz/smutok/internal/store" 6 + ) 7 + 8 + type fetchedArticles []store.Article 9 + 10 + func (m *Model) fetchArticles() tea.Cmd { 11 + return func() tea.Msg { 12 + articles, err := m.store.GetArticles(m.ctx) 13 + if err != nil { 14 + return sendErr(err) 15 + } 16 + return fetchedArticles(articles) 17 + } 18 + }
+1
internal/tui/styles.go
··· 1 + package tui
+83 -6
internal/tui/tui.go
··· 2 2 3 3 import ( 4 4 "context" 5 + "log/slog" 5 6 7 + "github.com/charmbracelet/bubbles/table" 8 + "github.com/charmbracelet/bubbles/viewport" 6 9 tea "github.com/charmbracelet/bubbletea" 10 + "github.com/charmbracelet/glamour" 7 11 "olexsmir.xyz/smutok/internal/store" 8 12 ) 9 13 10 14 type Syncer interface { 11 15 Sync(ctx context.Context) error 12 16 } 17 + 18 + type AppState int 19 + 20 + const ( 21 + ArticlesView AppState = iota 22 + FeedsView 23 + ReadingView 24 + ) 13 25 14 26 type Model struct { 15 27 ctx context.Context 16 28 29 + state AppState 17 30 isQutting bool 18 31 showErr bool 19 32 err error 20 33 21 34 syncer Syncer 22 35 store *store.Sqlite 36 + 37 + articles []store.Article 38 + 39 + glamur *glamour.TermRenderer 40 + table table.Model // feeds, articles feed 41 + viewport viewport.Model // article content reader 23 42 } 24 43 25 44 func NewModel( ··· 27 46 syncer Syncer, 28 47 store *store.Sqlite, 29 48 ) *Model { 49 + tbl := table.New(table.WithFocused(true)) 50 + vp := viewport.New(0, 0) 51 + 30 52 return &Model{ 31 - ctx: ctx, 32 - syncer: syncer, 33 - store: store, 53 + ctx: ctx, 54 + syncer: syncer, 55 + store: store, 56 + glamur: &glamour.TermRenderer{}, 57 + table: tbl, 58 + viewport: vp, 59 + state: ArticlesView, 60 + isQutting: false, 61 + showErr: false, 62 + err: nil, 63 + articles: nil, 34 64 } 35 65 } 36 66 37 67 func (m *Model) Init() tea.Cmd { 38 - return nil 68 + return tea.Batch( 69 + tea.SetWindowTitle("smutok"), 70 + m.fetchArticles(), 71 + ) 39 72 } 40 73 41 74 func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 75 + var cmd tea.Cmd 42 76 switch msg := msg.(type) { 43 77 case errMsg: 44 78 m.err = msg ··· 46 80 return m, nil 47 81 48 82 case tea.WindowSizeMsg: 83 + m.table.SetHeight(msg.Height) 84 + m.table.SetWidth(msg.Width) 85 + m.viewport.Height = msg.Height 86 + m.viewport.Width = msg.Width 87 + return m, nil 88 + 89 + case fetchedArticles: 90 + m.articles = msg 91 + 92 + columns := []table.Column{ 93 + // {Title: "read", Width: 4}, 94 + // {Title: "stared", Width: 6}, 95 + {Title: "author", Width: 14}, 96 + {Title: "title", Width: m.table.Width() - 14}, 97 + } 98 + 99 + rows := make([]table.Row, len(msg)) 100 + for i, article := range msg { 101 + rows[i] = table.Row{article.Author, article.Title} 102 + } 103 + 104 + m.table.SetColumns(columns) 105 + m.table.SetRows(rows) 106 + 107 + slog.Debug("got articles") 108 + return m, nil 49 109 50 110 case tea.KeyMsg: 111 + // page specific keys 112 + switch m.state { 113 + case ArticlesView, FeedsView: 114 + m.table, cmd = m.table.Update(msg) 115 + case ReadingView: 116 + m.viewport, cmd = m.viewport.Update(msg) 117 + } 118 + 119 + // global keys 51 120 switch msg.String() { 52 121 case "q": 53 122 m.isQutting = true 54 123 return m, tea.Quit 55 124 } 56 125 } 57 - return m, nil 126 + return m, cmd 58 127 } 59 128 60 129 func (m *Model) View() string { 61 130 if m.isQutting { 62 131 return "" 63 132 } 64 - return "are you feeling smutok?" 133 + 134 + var out string 135 + switch m.state { 136 + case ArticlesView, FeedsView: 137 + out = m.table.View() 138 + case ReadingView: 139 + out = m.viewport.View() 140 + } 141 + return out 65 142 }