+18
-4
internal/store/sqlite_articles.go
+18
-4
internal/store/sqlite_articles.go
···
56
56
return err
57
57
}
58
58
59
+
type ArticleKind int
60
+
61
+
const (
62
+
ArticleStarred ArticleKind = iota
63
+
ArticleUnread
64
+
ArticleAll
65
+
)
66
+
59
67
type Article struct {
60
68
ID string
61
69
Title string
···
69
77
PublishedAt int64
70
78
}
71
79
72
-
func (s *Sqlite) GetArticles(ctx context.Context) ([]Article, error) {
73
-
query := `--sql
80
+
var getArticlesWhereClause = map[ArticleKind]string{
81
+
ArticleAll: "",
82
+
ArticleStarred: "where s.is_starred = true",
83
+
ArticleUnread: "where s.is_read = false",
84
+
}
85
+
86
+
func (s *Sqlite) GetArticles(ctx context.Context, kind ArticleKind) ([]Article, error) {
87
+
query := fmt.Sprintf(`--sql
74
88
select a.id, a.title, a.href, a.content, a.author, s.is_read, s.is_starred, a.feed_id, f.title feed_name, a.published_at
75
89
from articles a
76
90
join article_statuses s on a.id = s.article_id
77
91
join feeds f on f.id = a.feed_id
78
-
where s.is_read = false
79
-
order by a.published_at desc`
92
+
%s
93
+
order by a.published_at desc`, getArticlesWhereClause[kind])
80
94
81
95
rows, err := s.db.QueryContext(ctx, query)
82
96
if err != nil {
+45
-2
internal/tui/fetcher.go
+45
-2
internal/tui/fetcher.go
···
1
1
package tui
2
2
3
3
import (
4
+
"time"
5
+
6
+
"github.com/charmbracelet/bubbles/table"
4
7
tea "github.com/charmbracelet/bubbletea"
5
8
"olexsmir.xyz/smutok/internal/store"
6
9
)
7
10
8
11
type fetchedArticles []store.Article
9
12
10
-
func (m *Model) fetchArticles() tea.Cmd {
13
+
func (m *Model) fetchArticles(kind store.ArticleKind) tea.Cmd {
11
14
return func() tea.Msg {
12
-
articles, err := m.store.GetArticles(m.ctx)
15
+
articles, err := m.store.GetArticles(m.ctx, kind)
13
16
if err != nil {
14
17
return sendErr(err)
15
18
}
16
19
return fetchedArticles(articles)
17
20
}
18
21
}
22
+
23
+
func (m *Model) setupTableWithArticles() {
24
+
// clean up previous state
25
+
m.table.SetRows([]table.Row{})
26
+
27
+
columns := []table.Column{
28
+
{Title: "date", Width: 10},
29
+
{Title: "status", Width: 6},
30
+
{Title: "author", Width: 14},
31
+
{Title: "title", Width: m.table.Width() - 30},
32
+
}
33
+
34
+
rows := make([]table.Row, len(m.articles))
35
+
for i, a := range m.articles {
36
+
rows[i] = table.Row{
37
+
m.toArticleDate(a.PublishedAt),
38
+
m.toArticleStatus(a.IsRead, a.IsStarred),
39
+
a.Author,
40
+
a.Title,
41
+
}
42
+
}
43
+
44
+
m.table.SetColumns(columns)
45
+
m.table.SetRows(rows)
46
+
}
47
+
48
+
func (m *Model) toArticleStatus(isRead, isStarred bool) string {
49
+
var out string
50
+
if isRead {
51
+
out += "✓ "
52
+
}
53
+
if isStarred {
54
+
out += "★ "
55
+
}
56
+
return out
57
+
}
58
+
59
+
func (m *Model) toArticleDate(publishedAt int64) string {
60
+
return time.Unix(publishedAt, 0).Format("2006/01/02")
61
+
}
+3
-20
internal/tui/tui.go
+3
-20
internal/tui/tui.go
···
2
2
3
3
import (
4
4
"context"
5
-
"log/slog"
6
5
7
6
"github.com/charmbracelet/bubbles/table"
8
7
"github.com/charmbracelet/bubbles/viewport"
···
67
66
func (m *Model) Init() tea.Cmd {
68
67
return tea.Batch(
69
68
tea.SetWindowTitle("smutok"),
70
-
m.fetchArticles(),
69
+
m.fetchArticles(store.ArticleAll),
71
70
)
72
71
}
73
72
···
81
80
82
81
case tea.WindowSizeMsg:
83
82
m.table.SetHeight(msg.Height)
84
-
m.table.SetWidth(msg.Width)
83
+
m.table.SetWidth(msg.Width - 2)
85
84
m.viewport.Height = msg.Height
86
85
m.viewport.Width = msg.Width
87
86
return m, nil
88
87
89
88
case fetchedArticles:
90
89
m.articles = msg
91
-
92
-
columns := []table.Column{
93
-
// {Title: "read", Width: 4},
94
-
// {Title: "stared", Width: 6},
95
-
{Title: "author", Width: 14},
96
-
{Title: "title", Width: m.table.Width() - 14},
97
-
}
98
-
99
-
rows := make([]table.Row, len(msg))
100
-
for i, article := range msg {
101
-
rows[i] = table.Row{article.Author, article.Title}
102
-
}
103
-
104
-
m.table.SetColumns(columns)
105
-
m.table.SetRows(rows)
106
-
107
-
slog.Debug("got articles")
90
+
m.setupTableWithArticles()
108
91
return m, nil
109
92
110
93
case tea.KeyMsg: