diff --git a/internal/commands/commands.go b/internal/commands/commands.go index 9823de5..a7fc0c9 100644 --- a/internal/commands/commands.go +++ b/internal/commands/commands.go @@ -252,7 +252,7 @@ func (c Commands) GetGlamourisedArticle(ID int) (string, error) { return "", fmt.Errorf("commands.FindGlamourisedArticle: %w", err) } - if c.config.AutoRead { + if c.config.AutoRead && !article.Read() { err = c.store.ToggleRead(article.ID) if err != nil { return "", fmt.Errorf("[commands.go] GetGlamourisedArticle: %w", err) @@ -291,7 +291,12 @@ func getStyleConfigWithOverrides(theme config.Theme) (sc ansi.StyleConfig) { func glamouriseItem(item store.Item, theme config.Theme) (string, error) { var mdown string - mdown += "# " + item.Title + title := item.Title + if item.Read() { + title = fmt.Sprintf("%s - %s", item.Title, theme.ReadIcon) + } + + mdown += "# " + title mdown += "\n" mdown += item.Author if !item.PublishedAt.IsZero() { diff --git a/internal/commands/list.go b/internal/commands/list.go index 9746d07..967e7c3 100644 --- a/internal/commands/list.go +++ b/internal/commands/list.go @@ -188,7 +188,12 @@ func updateList(msg tea.Msg, m model) (tea.Model, tea.Cmd) { return m, m.list.NewStatusMessage("No items to mark.") } - current := m.list.SelectedItem().(TUIItem) + item := m.list.SelectedItem() + if item == nil { + return m, m.list.NewStatusMessage("No item selected.") + } + + current := item.(TUIItem) err := m.commands.store.ToggleRead(current.ID) if err != nil { return m, tea.Quit @@ -220,7 +225,12 @@ func updateList(msg tea.Msg, m model) (tea.Model, tea.Cmd) { return m, m.list.NewStatusMessage("No items to favourite.") } - current := m.list.SelectedItem().(TUIItem) + item := m.list.SelectedItem() + if item == nil { + return m, m.list.NewStatusMessage("No item selected.") + } + + current := item.(TUIItem) err := m.commands.store.ToggleFavourite(current.ID) if err != nil { return m, tea.Quit diff --git a/internal/commands/tui.go b/internal/commands/tui.go index a01480e..3d0c013 100644 --- a/internal/commands/tui.go +++ b/internal/commands/tui.go @@ -42,6 +42,8 @@ type model struct { list list.Model help help.Model viewport viewport.Model + lastRead *list.Item + lastReadIndex int } func (m model) Init() tea.Cmd { diff --git a/internal/commands/viewport.go b/internal/commands/viewport.go index 4c72e43..3e6e045 100644 --- a/internal/commands/viewport.go +++ b/internal/commands/viewport.go @@ -29,7 +29,15 @@ func updateViewport(msg tea.Msg, m model) (tea.Model, tea.Cmd) { m.viewport.GotoBottom() case key.Matches(msg, ViewportKeyMap.Escape): + // reset cursor if last post is read and quit + index := m.list.Index() + length := len(m.list.Items()) + if index >= length && length >= 1 { + m.list.Select(index - 1) + } + m.selectedArticle = nil + cmds = append(cmds, m.UpdateList()) case key.Matches(msg, ViewportKeyMap.OpenInBrowser): current, err := m.commands.store.GetItemByID(*m.selectedArticle) @@ -50,7 +58,6 @@ func updateViewport(msg tea.Msg, m model) (tea.Model, tea.Cmd) { if err != nil { return m, tea.Quit } - cmds = append(cmds, m.UpdateList()) case key.Matches(msg, ViewportKeyMap.Read): if m.commands.config.AutoRead { @@ -64,17 +71,41 @@ func updateViewport(msg tea.Msg, m model) (tea.Model, tea.Cmd) { if err != nil { return m, tea.Quit } - cmds = append(cmds, m.UpdateList()) + + if !m.commands.config.ShowRead { + index := m.list.Index() + + if m.lastRead != nil && current.ID == (*m.lastRead).(TUIItem).ID { + // un-read re-add post back to list + m.list.InsertItem(index, *m.lastRead) + m.lastReadIndex = index + m.lastRead = nil + } else { + // remove post and store backup for un-read + items := m.list.Items() + item := items[index] + m.list.RemoveItem(index) + m.lastReadIndex = index + m.lastRead = &item + } + } + + // trigger refresh to update read indication + content, err := m.commands.GetGlamourisedArticle(*m.selectedArticle) + if err != nil { + return m, tea.Quit + } + m.viewport.SetContent(content) case key.Matches(msg, ViewportKeyMap.Prev): - current := m.list.Index() - if current-1 < 0 { + navIndex := m.getPrevIndex() + items := m.list.Items() + if m.isPrevOutOfBounds(navIndex) { return m, nil } - m.list.Select(current - 1) - items := m.list.Items() - item := items[current-1] + m.list.Select(navIndex) + item := items[navIndex] id := item.(TUIItem).ID m.selectedArticle = &id @@ -84,16 +115,19 @@ func updateViewport(msg tea.Msg, m model) (tea.Model, tea.Cmd) { } m.viewport.SetContent(content) + if m.commands.config.AutoRead && !m.commands.config.ShowRead { + m.list.RemoveItem(m.list.Index()) + } case key.Matches(msg, ViewportKeyMap.Next): - current := m.list.Index() + navIndex := m.getNextIndex() items := m.list.Items() - if current+1 >= len(items) { + if m.isNextOutOfBounds(navIndex, len(items)) { return m, nil } - m.list.Select(current + 1) - item := items[current+1] + m.list.Select(navIndex) + item := items[navIndex] id := item.(TUIItem).ID m.selectedArticle = &id @@ -103,6 +137,10 @@ func updateViewport(msg tea.Msg, m model) (tea.Model, tea.Cmd) { } m.viewport.SetContent(content) + if m.commands.config.AutoRead && !m.commands.config.ShowRead { + m.list.RemoveItem(m.list.Index()) + } + case key.Matches(msg, ViewportKeyMap.Quit): return m, tea.Quit @@ -122,6 +160,57 @@ func updateViewport(msg tea.Msg, m model) (tea.Model, tea.Cmd) { return m, tea.Batch(cmds...) } +func (m *model) isPrevOutOfBounds(i int) bool { + if len(m.list.Items()) == 0 { + return true + } + return i < 0 +} + +func (m *model) isNextOutOfBounds(i int, l int) bool { + maxIndex := l - 1 + + // when autoread and don't show read the first opened item doesn't exist in list + if m.commands.config.AutoRead && !m.commands.config.ShowRead && i == 0 { + maxIndex = l + } + + if i < 0 || i > maxIndex || maxIndex < 0 || l == 0 { + return true + } + return false +} + +func (m *model) getNextIndex() int { + if m.commands.config.AutoRead && !m.commands.config.ShowRead { + return m.list.Index() + } + + // check for favorite within post + current, err := m.commands.store.GetItemByID(*m.selectedArticle) + if err != nil { + return m.list.Index() + } + if !m.commands.config.AutoRead && current.Read() && !m.commands.config.ShowRead { + return m.list.Index() + } + + return m.list.Index() + 1 +} + +func (m *model) getPrevIndex() int { + current := m.list.Index() + if m.commands.config.AutoRead && !m.commands.config.ShowRead && current < len(m.list.Items()) { + return m.list.Index() + } + + if current == 0 { + return 0 + } + + return m.list.Index() - 1 +} + func viewportView(m model) string { return m.viewport.View() + "\n" + m.viewportHelp() } diff --git a/internal/config/config.go b/internal/config/config.go index 757a68e..5f4cb09 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -48,6 +48,7 @@ type Theme struct { TitleColor string `yaml:"titleColor,omitempty"` FilterColor string `yaml:"filterColor,omitempty"` SelectedItemColor string `yaml:"selectedItemColor,omitempty"` + ReadIcon string `yaml:"readIcon,omitempty"` } // need to add to Load() below if loading from config file @@ -111,6 +112,7 @@ func New(configPath string, pager string, previewFeeds []string, version string) SelectedItemColor: "170", TitleColor: "62", FilterColor: "62", + ReadIcon: "\u2713", }, Ordering: constants.DefaultOrdering, HTTPOptions: &HTTPOptions{ @@ -157,6 +159,10 @@ func (c *Config) Load() error { c.Ordering = fileConfig.Ordering } + if len(fileConfig.Theme.ReadIcon) > 0 { + c.Theme.ReadIcon = fileConfig.Theme.ReadIcon + } + if fileConfig.Theme.Glamour != "" { c.Theme.Glamour = fileConfig.Theme.Glamour }