From b84cad66f701e274d8e8abd65fb46f95b340bcda Mon Sep 17 00:00:00 2001 From: David Schlachter Date: Thu, 8 Jan 2026 01:02:29 -0500 Subject: Application is now interactiv --- main.go | 135 ++++++++++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 93 insertions(+), 42 deletions(-) (limited to 'main.go') diff --git a/main.go b/main.go index ea0f382..235be18 100644 --- a/main.go +++ b/main.go @@ -3,12 +3,16 @@ package main import ( + "database/sql" + "fmt" "log" "net/http" - "os" "time" + "github.com/charmbracelet/bubbles/textinput" + tea "github.com/charmbracelet/bubbletea" _ "github.com/mattn/go-sqlite3" + "github.com/microcosm-cc/bluemonday" ) const ( @@ -20,32 +24,99 @@ const ( modelName = "Basic-830ae" ) -type addNote struct { - Action string `json:"action"` - Version int `json:"version"` - Params addNoteParams `json:"params"` +type model struct { + wordInput textinput.Model + err error + currentWord string + currentDefinition string + db *sql.DB + c *http.Client + wordAddStatus string + p bluemonday.Policy } -type addNoteParams struct { - Note note `json:"note"` +type ( + errMsg error + definitionMsg string + wordAddedMsg string +) + +func initialModel(c *http.Client, db *sql.DB) model { + ti := textinput.New() + ti.Placeholder = "" + ti.Focus() + ti.CharLimit = 156 + ti.Width = 36 + + return model{ + wordInput: ti, + err: nil, + db: db, + c: c, + wordAddStatus: "(Press 'Enter' to add this word and its definition to Anki)", + p: *bluemonday.StrictPolicy(), + } } -type note struct { - DeckName string `json:"deckName"` - ModelName string `json:"modelName"` - // Fields will not be trivial to generalize - Fields fields `json:"fields"` - Options options `json:"options"` +func (m model) Init() tea.Cmd { + return textinput.Blink } -type fields struct { - Front string `json:"Front"` - Back string `json:"Back"` +func lookupWord(db *sql.DB, word string) tea.Cmd { + return func() tea.Msg { + var definition string + row := db.QueryRow(`select definition from words where word = ? limit 1`, word) + err := row.Scan(&definition) + if err != nil { + return errMsg(fmt.Errorf("looking up '%s': %s", word, err)) + } + return definitionMsg(definition) + } } -type options struct { - AllowDuplicate bool `json:"allowDuplicate"` - DuplicateScope string `json:"duplicateScope"` +func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmd tea.Cmd + + var textPickerLongerCmds []tea.Cmd + + switch msg := msg.(type) { + case definitionMsg: + m.currentDefinition = string(msg) + return m, nil + case wordAddedMsg: + m.wordAddStatus = fmt.Sprintf("✅ Added '%s' to Anki", string(msg)) + case tea.KeyMsg: + switch msg.Type { + case tea.KeyCtrlC, tea.KeyEsc: + return m, tea.Quit + case tea.KeyEnter: + return m, addCard(m.c, m.currentWord, m.currentDefinition) + } + + case errMsg: + m.err = msg + return m, nil + } + + if m.wordInput.Value() != m.currentWord { + m.currentWord = m.wordInput.Value() + textPickerLongerCmds = append(textPickerLongerCmds, lookupWord(m.db, m.currentWord)) + } + + m.wordInput, cmd = m.wordInput.Update(msg) + textPickerLongerCmds = append(textPickerLongerCmds, cmd) + return m, tea.Batch(textPickerLongerCmds...) +} + +func (m model) View() string { + return fmt.Sprintf( + "Look up a word:\n\n%s\n\n%s\n%s\n\n%s\n%s", + m.wordInput.View(), + m.wordAddStatus, + "(esc to quit)", + "Current definition:\n", + m.p.Sanitize(m.currentDefinition), + ) + "\n" } func main() { @@ -55,31 +126,11 @@ func main() { } defer db.Close() - // We're going to start this app very simply! The first iteration will take - // a word as its first command-line argument. We will search for the word in - // the dictionary and create a new Anki card using the first exact match. - // - // In the future, we may make it easy for the user to edit cards (flag?), - // and possibly implement a TUI to choose definitions more interactively as - // well (e.g. search with partial matches). - // - // I would also like to make more things less hard-coded. - if len(os.Args) < 2 { - log.Fatalf("no word was provided") - } - word := os.Args[1] - - var definition string - row := db.QueryRow(`select definition from words where word = ? limit 1`, word) - err = row.Scan(&definition) - if err != nil { - log.Fatalf("looking up '%s': %s", word, err) - } - c := http.DefaultClient c.Timeout = 5 * time.Second - if err := addCard(c, word, definition); err != nil { - log.Fatalf("creating card: %s", err) + p := tea.NewProgram(initialModel(c, db)) + if _, err := p.Run(); err != nil { + log.Fatal(err) } } -- cgit v1.2.3