+18
-4
internal/store/sqlite_articles.go
+18
-4
internal/store/sqlite_articles.go
···
56
return err
57
}
58
59
type Article struct {
60
ID string
61
Title string
···
69
PublishedAt int64
70
}
71
72
-
func (s *Sqlite) GetArticles(ctx context.Context) ([]Article, error) {
73
-
query := `--sql
74
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
from articles a
76
join article_statuses s on a.id = s.article_id
77
join feeds f on f.id = a.feed_id
78
-
where s.is_read = false
79
-
order by a.published_at desc`
80
81
rows, err := s.db.QueryContext(ctx, query)
82
if err != nil {
···
56
return err
57
}
58
59
+
type ArticleKind int
60
+
61
+
const (
62
+
ArticleStarred ArticleKind = iota
63
+
ArticleUnread
64
+
ArticleAll
65
+
)
66
+
67
type Article struct {
68
ID string
69
Title string
···
77
PublishedAt int64
78
}
79
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
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
89
from articles a
90
join article_statuses s on a.id = s.article_id
91
join feeds f on f.id = a.feed_id
92
+
%s
93
+
order by a.published_at desc`, getArticlesWhereClause[kind])
94
95
rows, err := s.db.QueryContext(ctx, query)
96
if err != nil {
+45
-2
internal/tui/fetcher.go
+45
-2
internal/tui/fetcher.go
···
1
package tui
2
3
import (
4
tea "github.com/charmbracelet/bubbletea"
5
"olexsmir.xyz/smutok/internal/store"
6
)
7
8
type fetchedArticles []store.Article
9
10
-
func (m *Model) fetchArticles() tea.Cmd {
11
return func() tea.Msg {
12
-
articles, err := m.store.GetArticles(m.ctx)
13
if err != nil {
14
return sendErr(err)
15
}
16
return fetchedArticles(articles)
17
}
18
}
···
1
package tui
2
3
import (
4
+
"time"
5
+
6
+
"github.com/charmbracelet/bubbles/table"
7
tea "github.com/charmbracelet/bubbletea"
8
"olexsmir.xyz/smutok/internal/store"
9
)
10
11
type fetchedArticles []store.Article
12
13
+
func (m *Model) fetchArticles(kind store.ArticleKind) tea.Cmd {
14
return func() tea.Msg {
15
+
articles, err := m.store.GetArticles(m.ctx, kind)
16
if err != nil {
17
return sendErr(err)
18
}
19
return fetchedArticles(articles)
20
}
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
3
import (
4
"context"
5
-
"log/slog"
6
7
"github.com/charmbracelet/bubbles/table"
8
"github.com/charmbracelet/bubbles/viewport"
···
67
func (m *Model) Init() tea.Cmd {
68
return tea.Batch(
69
tea.SetWindowTitle("smutok"),
70
-
m.fetchArticles(),
71
)
72
}
73
···
81
82
case tea.WindowSizeMsg:
83
m.table.SetHeight(msg.Height)
84
-
m.table.SetWidth(msg.Width)
85
m.viewport.Height = msg.Height
86
m.viewport.Width = msg.Width
87
return m, nil
88
89
case fetchedArticles:
90
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")
108
return m, nil
109
110
case tea.KeyMsg:
···
2
3
import (
4
"context"
5
6
"github.com/charmbracelet/bubbles/table"
7
"github.com/charmbracelet/bubbles/viewport"
···
66
func (m *Model) Init() tea.Cmd {
67
return tea.Batch(
68
tea.SetWindowTitle("smutok"),
69
+
m.fetchArticles(store.ArticleAll),
70
)
71
}
72
···
80
81
case tea.WindowSizeMsg:
82
m.table.SetHeight(msg.Height)
83
+
m.table.SetWidth(msg.Width - 2)
84
m.viewport.Height = msg.Height
85
m.viewport.Width = msg.Width
86
return m, nil
87
88
case fetchedArticles:
89
m.articles = msg
90
+
m.setupTableWithArticles()
91
return m, nil
92
93
case tea.KeyMsg: