package tui import ( "os" "os/exec" "time" "github.com/atotto/clipboard" tea "github.com/charmbracelet/bubbletea" "github.com/lerko/nib/internal/carddata" "github.com/lerko/nib/internal/db" ) type entitiesLoadedMsg struct { entities []*db.Entity } type entityCreatedMsg struct { entity *db.Entity } type entityDeletedMsg struct { id string } type entityUpdatedMsg struct { entity *db.Entity action string } type entityPromotedMsg struct { id string cardType db.CardType } type entityDemotedMsg struct { id string } type entityCopiedMsg struct{} type tagsLoadedMsg struct { tags []db.TagCount } type editorFinishedMsg struct { err error } type errMsg struct { err error } func loadEntities(store *db.Store, params db.ListParams) tea.Cmd { return func() tea.Msg { entities, err := store.List(params) if err != nil { return errMsg{err} } return entitiesLoadedMsg{entities} } } func createEntity(store *db.Store, e *db.Entity) tea.Cmd { return func() tea.Msg { if err := store.Create(e); err != nil { return errMsg{err} } return entityCreatedMsg{e} } } func deleteEntity(store *db.Store, id string) tea.Cmd { return func() tea.Msg { if _, err := store.SoftDelete(id); err != nil { return errMsg{err} } return entityDeletedMsg{id} } } func toggleTodo(store *db.Store, e *db.Entity) tea.Cmd { return func() tea.Msg { var update db.EntityUpdate if e.CompletedAt == nil { now := time.Now().UTC() update = db.EntityUpdate{CompletedAt: &now} } else { update = db.EntityUpdate{ClearCompleted: true} } if err := store.Update(e.ID, &update); err != nil { return errMsg{err} } updated, err := store.Get(e.ID) if err != nil { return errMsg{err} } action := "completed" if e.CompletedAt != nil { action = "reopened" } return entityUpdatedMsg{updated, action} } } func pinEntity(store *db.Store, e *db.Entity) tea.Cmd { return func() tea.Msg { newPinned := !e.Pinned update := db.EntityUpdate{Pinned: &newPinned} if err := store.Update(e.ID, &update); err != nil { return errMsg{err} } updated, err := store.Get(e.ID) if err != nil { return errMsg{err} } action := "pinned" if !newPinned { action = "unpinned" } return entityUpdatedMsg{updated, action} } } func promoteEntity(store *db.Store, id string, ct db.CardType, body string) tea.Cmd { return func() tea.Msg { cd := carddata.GenerateCardData(ct, body) if err := store.Promote(id, ct, cd); err != nil { return errMsg{err} } return entityPromotedMsg{id, ct} } } func demoteEntity(store *db.Store, id string) tea.Cmd { return func() tea.Msg { if err := store.Demote(id); err != nil { return errMsg{err} } return entityDemotedMsg{id} } } func copyToClipboard(store *db.Store, e *db.Entity) tea.Cmd { return func() tea.Msg { if err := clipboard.WriteAll(e.Body); err != nil { return errMsg{err} } if err := store.IncrementUse(e.ID); err != nil { return errMsg{err} } return entityCopiedMsg{} } } func loadTags(store *db.Store) tea.Cmd { return func() tea.Msg { tags, err := store.ListTags(false) if err != nil { return errMsg{err} } return tagsLoadedMsg{tags} } } func editInEditor(store *db.Store, e *db.Entity) tea.Cmd { editor := os.Getenv("EDITOR") if editor == "" { editor = "vi" } f, err := os.CreateTemp("", "nib-edit-*.md") if err != nil { return func() tea.Msg { return errMsg{err} } } if _, err := f.WriteString(e.Body); err != nil { f.Close() os.Remove(f.Name()) return func() tea.Msg { return errMsg{err} } } f.Close() c := exec.Command(editor, f.Name()) return tea.ExecProcess(c, func(err error) tea.Msg { defer os.Remove(f.Name()) if err != nil { return editorFinishedMsg{err} } content, readErr := os.ReadFile(f.Name()) if readErr != nil { return editorFinishedMsg{readErr} } newBody := string(content) if newBody == e.Body { return editorFinishedMsg{nil} } update := db.EntityUpdate{Body: &newBody} if updateErr := store.Update(e.ID, &update); updateErr != nil { return editorFinishedMsg{updateErr} } return editorFinishedMsg{nil} }) }