This commit is contained in:
IamTheFij 2022-02-11 15:56:04 -08:00
parent cc2efed9a6
commit 7d69298a64
9 changed files with 591 additions and 0 deletions

View File

@ -22,6 +22,7 @@ var configFile string
func Execute() {
cobra.OnInitialize(func() { lib.InitializeConfig(configFile) })
rootCmd.AddCommand(loginCmd)
rootCmd.AddCommand(tuiCmd)
addConfigCmd()
addFoldersCmd()
addNotesCmd()

16
cmd/tui.go Normal file
View File

@ -0,0 +1,16 @@
package cmd
import (
"github.com/spf13/cobra"
"git.iamthefij.com/iamthefij/imap-notes/tui"
)
var tuiCmd = &cobra.Command{
Use: "gui",
Short: "Load GUI for IMAP Notes",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
tui.LaunchTUI()
},
}

14
go.mod
View File

@ -5,6 +5,9 @@ go 1.17
require (
git.iamthefij.com/iamthefij/slog v1.3.0
github.com/JohannesKaufmann/html-to-markdown v1.3.3
github.com/charmbracelet/bubbles v0.10.2
github.com/charmbracelet/bubbletea v0.19.3
github.com/charmbracelet/lipgloss v0.4.0
github.com/emersion/go-imap v1.2.0
github.com/russross/blackfriday/v2 v2.1.0
github.com/spf13/cobra v1.3.0
@ -15,13 +18,24 @@ require (
require (
github.com/PuerkitoBio/goquery v1.5.1 // indirect
github.com/andybalholm/cascadia v1.1.0 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/containerd/console v1.0.2 // indirect
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/magiconair/properties v1.8.5 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/mitchellh/mapstructure v1.4.3 // indirect
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.9.0 // indirect
github.com/pelletier/go-toml v1.9.4 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/sahilm/fuzzy v0.1.0 // indirect
github.com/spf13/afero v1.6.0 // indirect
github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect

37
go.sum
View File

@ -68,6 +68,8 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV
github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
@ -77,6 +79,13 @@ github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charmbracelet/bubbles v0.10.2 h1:VK1Q7nnBMDFTlrMmvBgE9nidtU5udsIcZvFXvjE2Cfk=
github.com/charmbracelet/bubbles v0.10.2/go.mod h1:jOA+DUF1rjZm7gZHcNyIVW+YrBPALKfpGVdJu8UiJsA=
github.com/charmbracelet/bubbletea v0.19.3 h1:OKeO/Y13rQQqt4snX+lePB0QrnW80UdrMNolnCcmoAw=
github.com/charmbracelet/bubbletea v0.19.3/go.mod h1:VuXF2pToRxDUHcBUcPmCRUHRvFATM4Ckb/ql1rBl3KA=
github.com/charmbracelet/harmonica v0.1.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
github.com/charmbracelet/lipgloss v0.4.0 h1:768h64EFkGUr8V5yAKV7/Ta0NiVceiPaV+PphaW1K9g=
github.com/charmbracelet/lipgloss v0.4.0/go.mod h1:vmdkHvce7UzX6xkyf4cca8WlwdQ5RQr8fzta+xl7BOM=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@ -93,6 +102,8 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/containerd/console v1.0.2 h1:Pi6D+aZXM+oUw1czuKgH5IJ+y0jhYcwBJfx5/Ghn9dE=
github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
@ -260,6 +271,10 @@ github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
@ -273,7 +288,13 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
@ -290,6 +311,13 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
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.9.0 h1:wnbOaGz+LUR3jNT0zOzinPnyDaCZUQRZj9GxK8eRVl8=
github.com/muesli/termenv v0.9.0/go.mod h1:R/LzAKf+suGs4IsO95y7+7DpFHO0KABgnZqtlyx2mBw=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
@ -297,6 +325,8 @@ github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhEC
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@ -314,6 +344,9 @@ github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8b
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
@ -321,6 +354,8 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
github.com/sagikazarmark/crypt v0.4.0/go.mod h1:ALv2SRj7GxYV4HO9elxH9nS6M9gW+xDNxqmyJ6RfDFM=
github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sebdah/goldie/v2 v2.5.1 h1:hh70HvG4n3T3MNRJN2z/baxPR8xutxo7JVxyi2svl+s=
github.com/sebdah/goldie/v2 v2.5.1/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
@ -538,6 +573,7 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -563,6 +599,7 @@ golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211210111614-af8b64212486 h1:5hpz5aRr+W1erYCL5JRhSUBJRph7l9XkNveoExlrKYk=
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210422114643-f5beecf764ed/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

104
tui/folders.go Normal file
View File

@ -0,0 +1,104 @@
package tui
import (
"git.iamthefij.com/iamthefij/slog"
"github.com/charmbracelet/bubbles/list"
tea "github.com/charmbracelet/bubbletea"
"git.iamthefij.com/iamthefij/imap-notes/lib"
)
type folderListItem struct {
folder *lib.NoteFolder
}
func (fi folderListItem) Title() string {
return fi.folder.Name
}
func (fi folderListItem) Description() string {
return ""
}
func (fi folderListItem) FilterValue() string {
return fi.folder.Name
}
type folderListModel struct {
list list.Model
selectedFolder *lib.NoteFolder
}
func newFolderList() folderListModel {
m := folderListModel{}
client, err := lib.GetClient()
slog.OnErrFatalf(err, "failed to log on")
folders, err := client.ListNoteFolders()
slog.OnErrFatalf(err, "failed get folder list")
folderItems := []list.Item{}
for _, folder := range folders {
item := folderListItem{folder}
// item := listItem{title: folder.Name, desc: "", folder: folder}
// slog.Debugf("adding %s to list", item.Title())
folderItems = append(folderItems, item)
}
itemDelegate := list.NewDefaultDelegate()
itemDelegate.ShowDescription = false
m.list = list.New(folderItems, itemDelegate, 0, 0)
m.list.Title = "Note Folders"
m.list.SetShowHelp(false)
m.list.DisableQuitKeybindings()
return m
}
func (m folderListModel) Init() tea.Cmd {
return nil
}
func (m folderListModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "q":
return m, tea.Quit
case "enter":
folderItem, ok := m.list.SelectedItem().(folderListItem)
if ok {
folder := folderItem.folder
// slog.Debugf("Yay! We selected folder %s", folder.Name)
m.selectedFolder = folder
return m, nil
}
slog.Fatalf("selected an unknown item?")
return m, tea.Quit
}
case tea.WindowSizeMsg:
m.list.SetSize(msg.Width, msg.Height)
}
var cmd tea.Cmd
m.list, cmd = m.list.Update(msg)
return m, cmd
}
func (m folderListModel) View() string {
return m.list.View()
}
func LaunchFolderView() {
p := tea.NewProgram(newFolderList(), tea.WithAltScreen())
slog.OnErrFatalf(p.Start(), "error running TUI")
}

54
tui/main.go Normal file
View File

@ -0,0 +1,54 @@
package tui
import (
"git.iamthefij.com/iamthefij/slog"
tea "github.com/charmbracelet/bubbletea"
)
type ActiveSection int
const (
FolderSelect ActiveSection = iota
NoteSelect
NotePager
)
func LaunchTUI() {
// LaunchFolderView()
// p := tea.NewProgram(initialModel(), tea.WithAltScreen())
mvm := NewMultiViewModel([]tea.Model{newFolderList(), newNoteList()}, nil)
mvm.OnColumnSelect = func(baseModel *MultiViewModel, msg tea.Msg) tea.Cmd {
switch baseModel.GetActiveIndex() {
case int(FolderSelect):
folderView := (*baseModel.GetActiveView()).(folderListModel)
noteView := (*baseModel.NextView()).(noteListModel)
noteView.SetFolder(folderView.selectedFolder)
baseModel.SetActiveView(noteView)
case int(NoteSelect):
noteView := (*baseModel.GetActiveView()).(noteListModel)
markdown, err := noteView.selectedNote.ReadMarkdown()
slog.OnErrFatalf(err, "failed getting markdown for selected note")
slog.Debugf("got markdown: %s", markdown)
pager := NewPagerModel(markdown)
baseModel.SetMainView(pager)
baseModel.NextView()
}
return nil
}
mvm.OnColumnBack = func(baseModel *MultiViewModel, msg tea.Msg) tea.Cmd {
if baseModel.GetActiveIndex() == int(NotePager) {
baseModel.ClearMainView()
}
baseModel.PreviousView()
return nil
}
p := tea.NewProgram(mvm, tea.WithAltScreen())
slog.OnErrFatalf(p.Start(), "error running TUI")
}

213
tui/mutliview.go Normal file
View File

@ -0,0 +1,213 @@
package tui
import (
// "git.iamthefij.com/iamthefij/slog"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
type MultiViewModel struct {
// Internal state
columns []tea.Model
activeIndex int
mainModel tea.Model
lastWindowSizeMsg tea.WindowSizeMsg
// Styles for system
BaseStyle lipgloss.Style
ActiveStyle lipgloss.Style
NumVisibleSections int
SideColumnWidth int
// Select functionality
SelectKeys []string
OnColumnSelect func(baseModel *MultiViewModel, msg tea.Msg) tea.Cmd
BackKeys []string
OnColumnBack func(baseModel *MultiViewModel, msg tea.Msg) tea.Cmd
QuitKeys []string
}
func NewMultiViewModel(columns []tea.Model, main tea.Model) *MultiViewModel {
baseStyle := lipgloss.NewStyle().Margin(1)
return &MultiViewModel{
columns: columns,
activeIndex: 0,
mainModel: main,
BaseStyle: baseStyle,
ActiveStyle: baseStyle.Copy().UnsetMargins().BorderStyle(lipgloss.NormalBorder()).BorderForeground(
lipgloss.Color("21"),
),
NumVisibleSections: 2,
SideColumnWidth: 40,
SelectKeys: []string{tea.KeyEnter.String(), tea.KeyTab.String()},
OnColumnSelect: func(baseModel *MultiViewModel, msg tea.Msg) tea.Cmd {
baseModel.NextView()
return nil
},
BackKeys: []string{tea.KeyEsc.String(), tea.KeyShiftTab.String()},
OnColumnBack: func(baseModel *MultiViewModel, msg tea.Msg) tea.Cmd {
baseModel.PreviousView()
return nil
},
QuitKeys: []string{"q"},
}
}
func (m MultiViewModel) GetActiveIndex() int {
return m.activeIndex
}
func (m MultiViewModel) GetActiveView() *tea.Model {
if m.activeIndex < len(m.columns) {
return &m.columns[m.activeIndex]
}
return &m.mainModel
}
func (m *MultiViewModel) SetActiveView(viewModel tea.Model) {
if m.activeIndex < len(m.columns) {
m.columns[m.activeIndex] = viewModel
} else {
m.mainModel = viewModel
}
}
func (m *MultiViewModel) NextView() *tea.Model {
if m.activeIndex < len(m.columns) {
m.activeIndex++
}
return m.GetActiveView()
}
func (m *MultiViewModel) PreviousView() *tea.Model {
if m.activeIndex > 0 {
m.activeIndex--
}
return m.GetActiveView()
}
func (m *MultiViewModel) SetMainView(mainView tea.Model) {
top, right, bottom, left := m.BaseStyle.GetMargin()
m.mainModel, _ = mainView.Update(tea.WindowSizeMsg{
Height: m.lastWindowSizeMsg.Height - top - bottom,
Width: m.MainSectionWidth() - right - left,
})
}
func (m *MultiViewModel) ClearMainView() {
m.mainModel = nil
}
func (m MultiViewModel) Init() tea.Cmd { return nil }
func (m MultiViewModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
switch msg := msg.(type) {
case tea.KeyMsg:
// Pass key strokes to active view
var updatedView tea.Model
updatedView, cmd = (*m.GetActiveView()).Update(msg)
switch {
case m.activeIndex < 0:
panic("where are we!?")
case m.activeIndex < len(m.columns):
m.columns[m.activeIndex] = updatedView
case m.activeIndex >= len(m.columns):
m.mainModel = updatedView
}
// Handle global quit
for _, quitKey := range m.QuitKeys {
if quitKey == msg.String() {
return m, tea.Quit
}
}
// Handle select key
for _, selectKey := range m.SelectKeys {
if selectKey == msg.String() {
m.OnColumnSelect(&m, msg)
break
}
}
// Handle back key
for _, selectKey := range m.BackKeys {
if selectKey == msg.String() {
m.OnColumnBack(&m, msg)
break
}
}
case tea.WindowSizeMsg:
// Pass size message to all sub views
top, right, bottom, left := m.BaseStyle.GetMargin()
// top, right, bottom, left := m.BaseStyle.GetBorderStyle().GetTopSize
for i, model := range m.columns {
m.columns[i], _ = model.Update(tea.WindowSizeMsg{
Height: msg.Height - top - bottom,
Width: m.SideColumnWidth - right - left,
})
}
if m.mainModel != nil {
m.mainModel, _ = m.mainModel.Update(tea.WindowSizeMsg{
Height: msg.Height - top - bottom,
Width: m.MainSectionWidth() - right - left,
})
}
m.lastWindowSizeMsg = msg
}
return m, cmd
}
func (m MultiViewModel) MainSectionWidth() int {
return m.lastWindowSizeMsg.Width - (m.SideColumnWidth * m.NumVisibleSections)
}
func (m MultiViewModel) View() string {
allModels := []tea.Model{}
allModels = append(allModels, m.columns...)
if m.mainModel != nil {
allModels = append(allModels, m.mainModel)
}
visibleContent := []string{}
// Add visible columns
sideColStyle := m.BaseStyle.Copy().Width(m.SideColumnWidth)
mainStyle := m.BaseStyle.Copy().Width(m.MainSectionWidth())
for i, column := range allModels {
// Get the correct style
style := sideColStyle
if i >= len(m.columns) {
style = mainStyle
}
isVisible := m.NumVisibleSections < 1 || i < m.activeIndex+m.NumVisibleSections
if isVisible {
if i == m.activeIndex {
visibleContent = append(visibleContent, m.ActiveStyle.Inherit(style).Render(column.View()))
} else {
visibleContent = append(visibleContent, style.Render(column.View()))
}
}
}
return lipgloss.JoinHorizontal(lipgloss.Top, visibleContent...)
}

109
tui/notes.go Normal file
View File

@ -0,0 +1,109 @@
package tui
import (
"fmt"
"git.iamthefij.com/iamthefij/slog"
"github.com/charmbracelet/bubbles/list"
tea "github.com/charmbracelet/bubbletea"
"git.iamthefij.com/iamthefij/imap-notes/lib"
)
type noteListItem struct {
note *lib.Note
}
func (fi noteListItem) Title() string {
return fi.note.Name()
}
func (fi noteListItem) Description() string {
return "some body description"
}
func (fi noteListItem) FilterValue() string {
return fi.note.Name()
}
type noteListModel struct {
list list.Model
selectedFolder *lib.NoteFolder
selectedNote *lib.Note
}
func newNoteList() noteListModel {
m := noteListModel{}
noteItems := []list.Item{}
m.list = list.New(noteItems, list.NewDefaultDelegate(), 0, 0)
m.list.Title = "Notes"
m.list.SetShowHelp(false)
m.list.DisableQuitKeybindings()
return m
}
func (m noteListModel) Init() tea.Cmd {
return nil
}
func (m *noteListModel) SetFolder(folder *lib.NoteFolder) error {
m.selectedFolder = folder
noteItems := []list.Item{}
notes, err := folder.ListNotes()
if err != nil {
return fmt.Errorf("failed to get notes for list view: %w", err)
}
for _, note := range notes {
noteItems = append(noteItems, noteListItem{note})
}
m.list.SetItems(noteItems)
return nil
}
func (m noteListModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "q":
return m, tea.Quit
case "enter":
noteItem, ok := m.list.SelectedItem().(noteListItem)
if ok {
note := noteItem.note
// slog.Debugf("Yay! We selected note %s", note.Name)
m.selectedNote = note
return m, nil
}
slog.Fatalf("selected an unknown item?")
return m, tea.Quit
}
case tea.WindowSizeMsg:
m.list.SetSize(msg.Width, msg.Height)
}
var cmd tea.Cmd
m.list, cmd = m.list.Update(msg)
return m, cmd
}
func (m noteListModel) View() string {
return m.list.View()
}
func LaunchNoteView() {
p := tea.NewProgram(newFolderList(), tea.WithAltScreen())
slog.OnErrFatalf(p.Start(), "error running TUI")
}

43
tui/pager.go Normal file
View File

@ -0,0 +1,43 @@
package tui
import (
// "git.iamthefij.com/iamthefij/slog"
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
)
type pagerModel struct {
content string
viewport viewport.Model
}
func NewPagerModel(content string) pagerModel {
return pagerModel{
content: content,
}
}
func (m pagerModel) Init() tea.Cmd { return nil }
func (m pagerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
if msg.String() == "e" {
// edit!
}
case tea.WindowSizeMsg:
m.viewport = viewport.New(msg.Width, msg.Height)
m.viewport.SetContent(m.content)
}
var cmd tea.Cmd
// m.viewport, cmd = m.viewport.Update(msg)
return m, cmd
}
func (m pagerModel) View() string {
// slog.Debugf("viewport view? %s", m.viewport.View())
// return m.viewport.View()
return m.content
}