Skip to content

Commit

Permalink
feat: add ability to favourite items (#60)
Browse files Browse the repository at this point in the history
allows favouriting, showing all favourites and do not delete
favourite items when cleaning up old feeds

closes #5
  • Loading branch information
guyfedwards authored Apr 17, 2024
1 parent aea25c1 commit d0b5208
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 30 deletions.
6 changes: 5 additions & 1 deletion internal/commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func (c Commands) CleanFeeds() error {
}

for _, url := range urlsToRemove {
err := c.store.DeleteByFeedURL(url)
err := c.store.DeleteByFeedURL(url, false)
if err != nil {
return fmt.Errorf("[commands.go]: %w", err)
}
Expand Down Expand Up @@ -279,6 +279,10 @@ func (c Commands) GetAllFeeds() ([]store.Item, error) {
// filter out read and add feedname
var items []store.Item
for i := range is {
if c.config.ShowFavourites && !is[i].Favourite {
continue
}

if !c.config.ShowRead && is[i].Read() {
continue
}
Expand Down
95 changes: 76 additions & 19 deletions internal/commands/tui.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,29 @@ import (
)

var (
appStyle = lipgloss.NewStyle().Padding(0).Margin(0)
titleStyle = list.DefaultStyles().Title.Margin(1, 0, 0, 0).Width(5)
itemStyle = lipgloss.NewStyle().PaddingLeft(4)
selectedItemStyle = lipgloss.NewStyle().PaddingLeft(2).Foreground(lipgloss.Color("170"))
readStyle = lipgloss.NewStyle().PaddingLeft(4).Foreground(lipgloss.Color("240"))
selectedReadStyle = lipgloss.NewStyle().PaddingLeft(2).Foreground(lipgloss.Color("170"))
paginationStyle = list.DefaultStyles().PaginationStyle.PaddingLeft(4)
helpStyle = list.DefaultStyles().
appStyle = lipgloss.NewStyle().Padding(0).Margin(0)
titleStyle = list.DefaultStyles().Title.Margin(1, 0, 0, 0).Width(5)
itemStyle = lipgloss.NewStyle().PaddingLeft(4)
selectedItemStyle = lipgloss.NewStyle().PaddingLeft(2).Foreground(lipgloss.Color("170"))
readStyle = lipgloss.NewStyle().PaddingLeft(4).Foreground(lipgloss.Color("240"))
selectedReadStyle = lipgloss.NewStyle().PaddingLeft(2).Foreground(lipgloss.Color("170"))
favouriteStyle = itemStyle.Copy().PaddingLeft(2).Bold(true)
selectedFavouriteStyle = selectedItemStyle.Copy().Bold(true)
paginationStyle = list.DefaultStyles().PaginationStyle.PaddingLeft(4)
helpStyle = list.DefaultStyles().
HelpStyle.
PaddingLeft(4).
PaddingBottom(1).
Foreground(lipgloss.Color("#4A4A4A"))
)

type TUIItem struct {
ID int
Title string
FeedName string
URL string
Read bool
ID int
Title string
FeedName string
URL string
Read bool
Favourite bool
}

func (i TUIItem) FilterValue() string { return i.Title }
Expand Down Expand Up @@ -64,11 +67,20 @@ func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list
fn = readStyle.Render
}

if i.Favourite {
fn = func(s string) string {
return favouriteStyle.Render("* " + s)
}
}

if index == m.Index() {
fn = func(s string) string {
if i.Read {
return selectedReadStyle.Render("> " + s)
}
if i.Favourite {
return selectedFavouriteStyle.Render("* " + s)
}
return selectedItemStyle.Render("> " + s)
}
}
Expand Down Expand Up @@ -186,6 +198,36 @@ func updateList(msg tea.Msg, m model) (tea.Model, tea.Cmd) {
m.commands.config.ToggleShowRead()
m.UpdateList()

case "f":
if m.list.SettingFilter() {
break
}

if len(m.list.Items()) == 0 {
return m, m.list.NewStatusMessage("No items to favourite.")
}

current := m.list.SelectedItem().(TUIItem)
err := m.commands.store.ToggleFavourite(current.ID)
if err != nil {
return m, tea.Quit
}
m.UpdateList()

case "F":
if m.list.SettingFilter() {
break
}

if m.commands.config.ShowFavourites {
m.list.NewStatusMessage("")
} else {
m.list.NewStatusMessage("favourites")
}

m.commands.config.ToggleShowFavourites()
m.UpdateList()

case "o":
if m.list.SettingFilter() {
break
Expand Down Expand Up @@ -332,14 +374,17 @@ func (m model) viewportHelp() string {

func ItemToTUIItem(i store.Item) TUIItem {
return TUIItem{
ID: i.ID,
FeedName: i.FeedName,
Title: i.Title,
URL: i.Link,
Read: i.Read(),
ID: i.ID,
FeedName: i.FeedName,
Title: i.Title,
URL: i.Link,
Read: i.Read(),
Favourite: i.Favourite,
}
}

const defaultTitle = "nom"

func Render(items []list.Item, cmds Commands, errors []string) error {
const defaultWidth = 20
_, ts, _ := term.GetSize(int(os.Stdout.Fd()))
Expand All @@ -350,10 +395,14 @@ func Render(items []list.Item, cmds Commands, errors []string) error {

l := list.New(items, itemDelegate{}, defaultWidth, height)
l.SetShowStatusBar(false)
l.Title = "nom"
l.Title = defaultTitle
l.Styles.Title = titleStyle
l.Styles.PaginationStyle = paginationStyle
l.Styles.HelpStyle = helpStyle
// remove some extra keys from next/prev as used for other things
l.KeyMap.NextPage.SetKeys("right", "l", "pgdown")
l.KeyMap.PrevPage.SetKeys("left", "h", "pgup")

l.AdditionalFullHelpKeys = func() []key.Binding {
return []key.Binding{
key.NewBinding(
Expand All @@ -364,6 +413,14 @@ func Render(items []list.Item, cmds Commands, errors []string) error {
key.WithKeys("M"),
key.WithHelp("M", "show/hide read"),
),
key.NewBinding(
key.WithKeys("f"),
key.WithHelp("f", "toggle favourite"),
),
key.NewBinding(
key.WithKeys("F"),
key.WithHelp("F", "show/hide all favourites"),
),
key.NewBinding(
key.WithKeys("r"),
key.WithHelp("r", "refresh feed"),
Expand Down
13 changes: 9 additions & 4 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,21 @@ type Config struct {
Pager string `yaml:"pager,omitempty"`
Feeds []Feed `yaml:"feeds"`
// Preview feeds are distinguished from Feeds because we don't want to inadvertenly write those into the config file.
PreviewFeeds []Feed `yaml:"previewfeeds,omitempty"`
Backends *Backends `yaml:"backends,omitempty"`
ShowRead bool `yaml:"showread,omitempty"`
AutoRead bool `yaml:"autoread,omitempty"`
PreviewFeeds []Feed `yaml:"previewfeeds,omitempty"`
Backends *Backends `yaml:"backends,omitempty"`
ShowRead bool `yaml:"showread,omitempty"`
AutoRead bool `yaml:"autoread,omitempty"`
ShowFavourites bool
}

func (c *Config) ToggleShowRead() {
c.ShowRead = !c.ShowRead
}

func (c *Config) ToggleShowFavourites() {
c.ShowFavourites = !c.ShowFavourites
}

func New(configPath string, pager string, previewFeeds []string) (Config, error) {
var configDir string

Expand Down
35 changes: 29 additions & 6 deletions internal/store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type Item struct {
ID int
Author string
Title string
Favourite bool
FeedURL string
FeedName string // added from config if set
Link string
Expand All @@ -34,7 +35,8 @@ type Store interface {
GetAllItems() ([]Item, error)
GetAllFeedURLs() ([]string, error)
ToggleRead(ID int) error
DeleteByFeedURL(feedurl string) error
ToggleFavourite(ID int) error
DeleteByFeedURL(feedurl string, incFavourites bool) error
}

type SQLiteStore struct {
Expand Down Expand Up @@ -79,6 +81,7 @@ func dbSetup(db *sql.DB) error {
return fmt.Errorf("dbSetup: %w", err)
}

// See migrations below for additions
stm := `
create table items (id integer primary key, feedurl text, link text, title text, content text, author text, readat datetime, publishedat datetime, updatedat datetime, createdat datetime);
create table migrations (id integer not null, runat datetime);
Expand All @@ -100,7 +103,10 @@ func dbSetup(db *sql.DB) error {
func runMigrations(db *sql.DB) (err error) {
getCurrent := `select count(*) from migrations;`

migrations := []string{}
// Index based so all new migrations must go at the end of the array
migrations := []string{
`alter table items add favourite boolean not null default 0;`,
}

tx, _ := db.Begin()
updateMigrations, _ := tx.Prepare(`insert into migrations (id, runat) values (?, ?);`)
Expand Down Expand Up @@ -178,7 +184,7 @@ func (sls SQLiteStore) UpsertItem(item Item) error {
// TODO: pagination
func (sls SQLiteStore) GetAllItems() ([]Item, error) {
stmt := `
select id, feedurl, link, title, content, author, readat, publishedat, createdat, updatedat from items order by coalesce(publishedat, createdat);
select id, feedurl, link, title, content, author, readat, favourite, publishedat, createdat, updatedat from items order by coalesce(publishedat, createdat);
`

rows, err := sls.db.Query(stmt)
Expand All @@ -194,7 +200,7 @@ func (sls SQLiteStore) GetAllItems() ([]Item, error) {
var publishedAtNull sql.NullTime
var linkNull sql.NullString

if err := rows.Scan(&item.ID, &item.FeedURL, &linkNull, &item.Title, &item.Content, &item.Author, &readAtNull, &publishedAtNull, &item.CreatedAt, &item.UpdatedAt); err != nil {
if err := rows.Scan(&item.ID, &item.FeedURL, &linkNull, &item.Title, &item.Content, &item.Author, &readAtNull, &item.Favourite, &publishedAtNull, &item.CreatedAt, &item.UpdatedAt); err != nil {
fmt.Println("errrerre: ", err)
continue
}
Expand All @@ -220,6 +226,17 @@ func (sls SQLiteStore) ToggleRead(ID int) error {
return nil
}

func (sls SQLiteStore) ToggleFavourite(ID int) error {
stmt, _ := sls.db.Prepare(`update items set favourite = case when favourite is true then false else true end where id = ?`)

_, err := stmt.Exec(ID)
if err != nil {
return fmt.Errorf("[store.go] ToggleFavourite: %w", err)
}

return nil
}

func (sls SQLiteStore) GetAllFeedURLs() ([]string, error) {
var urls []string

Expand All @@ -244,8 +261,14 @@ func (sls SQLiteStore) GetAllFeedURLs() ([]string, error) {
return urls, nil
}

func (sls SQLiteStore) DeleteByFeedURL(feedurl string) error {
stmt, _ := sls.db.Prepare(`delete from items where feedurl = ?`)
func (sls SQLiteStore) DeleteByFeedURL(feedurl string, incFavourites bool) error {

var stmt *sql.Stmt
if incFavourites {
stmt, _ = sls.db.Prepare(`delete from items where feedurl = ?;`)
} else {
stmt, _ = sls.db.Prepare(`delete from items where feedurl = ? and favourite = false;`)
}

_, err := stmt.Exec(feedurl)
if err != nil {
Expand Down

0 comments on commit d0b5208

Please sign in to comment.