nandi/gleanpublic⑂ Fork 0
⑂ 788527a
Commits
⬇ Clone ▾
git clone https://git.rickub.com/nandi/glean.git
git clone ssh://git@rickub.com/nandi/glean.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Display feed info and add clickable feed linksUnverified

Julien Robert committed 2026-04-21T17:30:27+02:00 Browse files
788527a parent: e635425
modified internal/db/article.go +3 -2
@@ -72,11 +72,12 @@ func (db *DB) ListArticles(ctx context.Context, userDID, feedURL string, limit,
7272 JOIN subscriptions s ON a.feed_url = s.feed_url AND s.user_did = ?
7373 LEFT JOIN feeds f ON a.feed_url = f.feed_url
7474 LEFT JOIN read_state r ON r.user_did = ? AND r.article_id = a.id
75+ WHERE 1=1
7576 `
7677 args := []any{userDID, userDID}
7778
7879 if feedURL != "" {
79- query += ` AND s.feed_url = ?`
80+ query += ` AND a.feed_url = ?`
8081 args = append(args, feedURL)
8182 }
8283
@@ -111,7 +112,7 @@ func (db *DB) ListUnreadArticles(ctx context.Context, userDID, feedURL string, l
111112 JOIN subscriptions s ON a.feed_url = s.feed_url AND s.user_did = ?
112113 LEFT JOIN feeds f ON a.feed_url = f.feed_url
113114 LEFT JOIN read_state r ON r.user_did = ? AND r.article_id = a.id
114- WHERE r.is_read = 0 OR r.is_read IS NULL
115+ WHERE (r.is_read = 0 OR r.is_read IS NULL)
115116 `
116117 args := []any{userDID, userDID}
117118
@@ -72,11 +72,12 @@ func (db *DB) ListArticles(ctx context.Context, userDID, feedURL string, limit,
72 JOIN subscriptions s ON a.feed_url = s.feed_url AND s.user_did = ?72 JOIN subscriptions s ON a.feed_url = s.feed_url AND s.user_did = ?
73 LEFT JOIN feeds f ON a.feed_url = f.feed_url73 LEFT JOIN feeds f ON a.feed_url = f.feed_url
74 LEFT JOIN read_state r ON r.user_did = ? AND r.article_id = a.id74 LEFT JOIN read_state r ON r.user_did = ? AND r.article_id = a.id
75+ WHERE 1=1
75 `76 `
76 args := []any{userDID, userDID}77 args := []any{userDID, userDID}
77 78
78 if feedURL != "" {79 if feedURL != "" {
79- query += ` AND s.feed_url = ?`80+ query += ` AND a.feed_url = ?`
80 args = append(args, feedURL)81 args = append(args, feedURL)
81 }82 }
82 83
@@ -111,7 +112,7 @@ func (db *DB) ListUnreadArticles(ctx context.Context, userDID, feedURL string, l
111 JOIN subscriptions s ON a.feed_url = s.feed_url AND s.user_did = ?112 JOIN subscriptions s ON a.feed_url = s.feed_url AND s.user_did = ?
112 LEFT JOIN feeds f ON a.feed_url = f.feed_url113 LEFT JOIN feeds f ON a.feed_url = f.feed_url
113 LEFT JOIN read_state r ON r.user_did = ? AND r.article_id = a.id114 LEFT JOIN read_state r ON r.user_did = ? AND r.article_id = a.id
114- WHERE r.is_read = 0 OR r.is_read IS NULL115+ WHERE (r.is_read = 0 OR r.is_read IS NULL)
115 `116 `
116 args := []any{userDID, userDID}117 args := []any{userDID, userDID}
117 118
modified internal/server/articles_handler.go +8 -0
@@ -75,6 +75,14 @@ func (s *Server) handleArticles(w http.ResponseWriter, r *http.Request) {
7575 "Now": time.Now(),
7676 }
7777
78+ if feedURL != "" {
79+ if feed, err := s.db.GetFeed(r.Context(), feedURL); err == nil {
80+ data["Feed"] = feed
81+ }
82+ unreadCount, _ := s.db.GetUnreadCount(r.Context(), user.DID, feedURL)
83+ data["FeedUnreadCount"] = unreadCount
84+ }
85+
7886 if r.Header.Get("HX-Request") == "true" {
7987 s.render(w, r, "articles-content.html", data)
8088 return
@@ -75,6 +75,14 @@ func (s *Server) handleArticles(w http.ResponseWriter, r *http.Request) {
75 "Now": time.Now(),75 "Now": time.Now(),
76 }76 }
77 77
78+ if feedURL != "" {
79+ if feed, err := s.db.GetFeed(r.Context(), feedURL); err == nil {
80+ data["Feed"] = feed
81+ }
82+ unreadCount, _ := s.db.GetUnreadCount(r.Context(), user.DID, feedURL)
83+ data["FeedUnreadCount"] = unreadCount
84+ }
85+
78 if r.Header.Get("HX-Request") == "true" {86 if r.Header.Get("HX-Request") == "true" {
79 s.render(w, r, "articles-content.html", data)87 s.render(w, r, "articles-content.html", data)
80 return88 return
modified internal/tmpl/articles.html +28 -2
@@ -1,14 +1,40 @@
11 {{define "articles.html"}}
2- <div id="new-articles-poll" hx-get="/articles/new-count?since={{.Now.Unix}}&return=/articles" hx-trigger="every 30s" hx-swap="innerHTML"></div>
2+ <div id="new-articles-poll" hx-get="/articles/new-count?since={{.Now.Unix}}&return={{if .FeedURL}}/articles?feed={{.FeedURL}}{{else}}/articles{{end}}" hx-trigger="every 30s" hx-swap="innerHTML"></div>
3+
4+ {{if .Feed}}
5+ <div class="mb-6">
6+ <div class="flex items-center gap-2 mb-3">
7+ <a href="/articles" class="text-xs text-spot-secondary hover:text-spot-text transition">&larr; All articles</a>
8+ </div>
9+ <div class="flex items-center justify-between">
10+ <div class="flex items-center gap-3">
11+ {{if .Feed.FaviconURL.Valid}}<img src="{{.Feed.FaviconURL.String}}" class="w-8 h-8 rounded shrink-0" loading="lazy">{{end}}
12+ <div>
13+ <h1 class="text-2xl font-bold text-spot-text">{{if .Feed.Title.Valid}}{{.Feed.Title.String}}{{else}}{{.Feed.FeedURL}}{{end}}</h1>
14+ {{if .Feed.SiteURL.Valid}}<a href="{{.Feed.SiteURL.String}}" target="_blank" rel="noopener noreferrer" class="text-xs text-spot-secondary hover:text-spot-green transition">{{.Feed.SiteURL.String}}</a>{{end}}
15+ </div>
16+ </div>
17+ <div class="flex items-center gap-3">
18+ {{if .FeedUnreadCount}}<span class="text-xs bg-spot-green/20 text-spot-green px-2.5 py-0.5 rounded-full font-bold">{{.FeedUnreadCount}} unread</span>{{end}}
19+ <form hx-post="/articles/mark-all-read" hx-confirm="Mark all articles as read?">
20+ {{csrfInput .CSRFToken}}
21+ <input type="hidden" name="feed" value="{{.FeedURL}}">
22+ <button type="submit" class="border border-spot-outline text-spot-text rounded-pill px-4 py-1.5 text-xs font-bold uppercase tracking-button hover:border-spot-text transition">Mark all read</button>
23+ </form>
24+ </div>
25+ </div>
26+ {{if .Feed.Description.Valid}}<p class="text-sm text-spot-secondary mt-2">{{.Feed.Description.String}}</p>{{end}}
27+ </div>
28+ {{else}}
329 <div class="flex items-center justify-between mb-2">
430 <h1 class="text-2xl font-bold text-spot-text">Articles</h1>
531 <form hx-post="/articles/mark-all-read" hx-confirm="Mark all articles as read?">
632 {{csrfInput .CSRFToken}}
7- {{if .FeedURL}}<input type="hidden" name="feed" value="{{.FeedURL}}">{{end}}
833 <button type="submit" class="border border-spot-outline text-spot-text rounded-pill px-4 py-1.5 text-xs font-bold uppercase tracking-button hover:border-spot-text transition">Mark all read</button>
934 </form>
1035 </div>
1136 <p class="text-sm text-spot-secondary mb-6">All your subscribed articles, newest first.</p>
37+ {{end}}
1238
1339 <div class="flex items-center gap-3 mb-6">
1440 <form method="GET" action="/articles" class="flex-1" id="search-form">
@@ -1,14 +1,40 @@
1 {{define "articles.html"}}1 {{define "articles.html"}}
2- <div id="new-articles-poll" hx-get="/articles/new-count?since={{.Now.Unix}}&return=/articles" hx-trigger="every 30s" hx-swap="innerHTML"></div>2+ <div id="new-articles-poll" hx-get="/articles/new-count?since={{.Now.Unix}}&return={{if .FeedURL}}/articles?feed={{.FeedURL}}{{else}}/articles{{end}}" hx-trigger="every 30s" hx-swap="innerHTML"></div>
3+
4+ {{if .Feed}}
5+ <div class="mb-6">
6+ <div class="flex items-center gap-2 mb-3">
7+ <a href="/articles" class="text-xs text-spot-secondary hover:text-spot-text transition">&larr; All articles</a>
8+ </div>
9+ <div class="flex items-center justify-between">
10+ <div class="flex items-center gap-3">
11+ {{if .Feed.FaviconURL.Valid}}<img src="{{.Feed.FaviconURL.String}}" class="w-8 h-8 rounded shrink-0" loading="lazy">{{end}}
12+ <div>
13+ <h1 class="text-2xl font-bold text-spot-text">{{if .Feed.Title.Valid}}{{.Feed.Title.String}}{{else}}{{.Feed.FeedURL}}{{end}}</h1>
14+ {{if .Feed.SiteURL.Valid}}<a href="{{.Feed.SiteURL.String}}" target="_blank" rel="noopener noreferrer" class="text-xs text-spot-secondary hover:text-spot-green transition">{{.Feed.SiteURL.String}}</a>{{end}}
15+ </div>
16+ </div>
17+ <div class="flex items-center gap-3">
18+ {{if .FeedUnreadCount}}<span class="text-xs bg-spot-green/20 text-spot-green px-2.5 py-0.5 rounded-full font-bold">{{.FeedUnreadCount}} unread</span>{{end}}
19+ <form hx-post="/articles/mark-all-read" hx-confirm="Mark all articles as read?">
20+ {{csrfInput .CSRFToken}}
21+ <input type="hidden" name="feed" value="{{.FeedURL}}">
22+ <button type="submit" class="border border-spot-outline text-spot-text rounded-pill px-4 py-1.5 text-xs font-bold uppercase tracking-button hover:border-spot-text transition">Mark all read</button>
23+ </form>
24+ </div>
25+ </div>
26+ {{if .Feed.Description.Valid}}<p class="text-sm text-spot-secondary mt-2">{{.Feed.Description.String}}</p>{{end}}
27+ </div>
28+ {{else}}
3 <div class="flex items-center justify-between mb-2">29 <div class="flex items-center justify-between mb-2">
4 <h1 class="text-2xl font-bold text-spot-text">Articles</h1>30 <h1 class="text-2xl font-bold text-spot-text">Articles</h1>
5 <form hx-post="/articles/mark-all-read" hx-confirm="Mark all articles as read?">31 <form hx-post="/articles/mark-all-read" hx-confirm="Mark all articles as read?">
6 {{csrfInput .CSRFToken}}32 {{csrfInput .CSRFToken}}
7- {{if .FeedURL}}<input type="hidden" name="feed" value="{{.FeedURL}}">{{end}}
8 <button type="submit" class="border border-spot-outline text-spot-text rounded-pill px-4 py-1.5 text-xs font-bold uppercase tracking-button hover:border-spot-text transition">Mark all read</button>33 <button type="submit" class="border border-spot-outline text-spot-text rounded-pill px-4 py-1.5 text-xs font-bold uppercase tracking-button hover:border-spot-text transition">Mark all read</button>
9 </form>34 </form>
10 </div>35 </div>
11 <p class="text-sm text-spot-secondary mb-6">All your subscribed articles, newest first.</p>36 <p class="text-sm text-spot-secondary mb-6">All your subscribed articles, newest first.</p>
37+ {{end}}
12 38
13 <div class="flex items-center gap-3 mb-6">39 <div class="flex items-center gap-3 mb-6">
14 <form method="GET" action="/articles" class="flex-1" id="search-form">40 <form method="GET" action="/articles" class="flex-1" id="search-form">
modified internal/tmpl/dashboard.html +52 -52
@@ -27,53 +27,53 @@
2727 </div>
2828 <div class="grid grid-cols-1 md:grid-cols-2 gap-3">
2929 {{range .GlobalTrending}}
30- <a href="/articles/{{.ArticleID}}" class="block bg-spot-surface rounded-xl p-4 hover:bg-spot-hover-50 transition">
31- <div class="font-bold text-sm text-spot-text leading-tight">{{.Title}}</div>
32- <div class="flex items-center gap-2 mt-1 text-xs text-spot-secondary">
33- {{if .Author}}<span>{{.Author}}</span>{{end}}
34- <span class="text-spot-muted">{{if .FeedTitle}}{{.FeedTitle}}{{end}}</span>
35- </div>
36- <div class="flex items-center gap-3 mt-2 text-xs text-spot-secondary">
37- <span class="inline-flex items-center gap-1"><svg class="w-4 h-4 text-spot-muted" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">{{template "icon-heart"}}</svg> {{.LikeCount}}</span>
38- <span class="text-spot-muted">{{.AnnotationCount}} notes</span>
30+ <div class="bg-spot-surface rounded-xl p-4 hover:bg-spot-hover-50 transition">
31+ <a href="/articles/{{.ArticleID}}" class="font-bold text-sm text-spot-text leading-tight hover:text-spot-green transition">{{.Title}}</a>
32+ <div class="flex items-center gap-2 mt-1 text-xs text-spot-secondary">
33+ {{if .Author}}<span>{{.Author}}</span>{{end}}
34+ <span class="text-spot-muted"><a href="/articles?feed={{.FeedURL}}" class="hover:text-spot-green transition">{{if .FeedTitle}}{{.FeedTitle}}{{end}}</a></span>
35+ </div>
36+ <div class="flex items-center gap-3 mt-2 text-xs text-spot-secondary">
37+ <span class="inline-flex items-center gap-1"><svg class="w-4 h-4 text-spot-muted" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">{{template "icon-heart"}}</svg> {{.LikeCount}}</span>
38+ <span class="text-spot-muted">{{.AnnotationCount}} notes</span>
39+ </div>
40+ </div>
41+ {{end}}
3942 </div>
40- </a>
43+ </div>
4144 {{end}}
42- </div>
43-</div>
44-{{end}}
4545
46-{{if .FeedRecommendations}}
47-<div>
48- <h2 class="text-lg font-semibold text-spot-text mb-3">Popular feeds to get started</h2>
49- <div class="grid grid-cols-1 md:grid-cols-2 gap-3">
50- {{range .FeedRecommendations}}
51- {{template "recommendation-card.html" (dict "title" .Title "feed_url" .FeedURL "description" .Description "favicon_url" .FaviconURL "subscriber_count" .SubscriberCount "CSRFToken" $.CSRFToken)}}
46+ {{if .FeedRecommendations}}
47+ <div>
48+ <h2 class="text-lg font-semibold text-spot-text mb-3">Popular feeds to get started</h2>
49+ <div class="grid grid-cols-1 md:grid-cols-2 gap-3">
50+ {{range .FeedRecommendations}}
51+ {{template "recommendation-card.html" (dict "title" .Title "feed_url" .FeedURL "description" .Description "favicon_url" .FaviconURL "subscriber_count" .SubscriberCount "CSRFToken" $.CSRFToken)}}
52+ {{end}}
53+ </div>
54+ </div>
5255 {{end}}
53- </div>
54-</div>
55-{{end}}
56-{{else}}
57-<p class="text-sm text-spot-secondary mb-6">Your personalized feed, based on your social graph.</p>
56+ {{else}}
57+ <p class="text-sm text-spot-secondary mb-6">Your personalized feed, based on your social graph.</p>
5858
59-<div id="new-articles-poll" hx-get="/articles/new-count?since={{.Now.Unix}}&return=/dashboard" hx-trigger="every 30s" hx-swap="innerHTML"></div>
59+ <div id="new-articles-poll" hx-get="/articles/new-count?since={{.Now.Unix}}&return=/dashboard" hx-trigger="every 30s" hx-swap="innerHTML"></div>
6060
61-<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
62- <div class="lg:col-span-2">
63- {{if .ArticleRecommendations}}
64- <div class="mb-6">
65- <h2 class="text-lg font-semibold text-spot-text mb-3">Recommended for you</h2>
66- <div class="space-y-3">
67- {{range .ArticleRecommendations}}
68- <a href="/articles/{{.ArticleID}}" class="block bg-spot-surface rounded-xl p-4 hover:bg-spot-hover-50 transition">
69- <div class="font-bold text-sm text-spot-text leading-tight">{{.Title}}</div>
70- <div class="flex items-center gap-2 mt-1 text-xs text-spot-secondary">
71- {{if .Author}}<span>{{.Author}}</span>{{end}}
72- <span class="text-spot-muted">{{.FeedTitle}}</span>
73- {{if .Published.Valid}}<span>{{.Published.Time.Format "Jan 2"}}</span>{{end}}
74- </div>
75- {{if .Summary}}<p class="text-xs text-spot-secondary mt-2 line-clamp-2">{{.Summary}}</p>{{end}}
76- </a>
61+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
62+ <div class="lg:col-span-2">
63+ {{if .ArticleRecommendations}}
64+ <div class="mb-6">
65+ <h2 class="text-lg font-semibold text-spot-text mb-3">Recommended for you</h2>
66+ <div class="space-y-3">
67+ {{range .ArticleRecommendations}}
68+ <div class="bg-spot-surface rounded-xl p-4 hover:bg-spot-hover-50 transition">
69+ <a href="/articles/{{.ArticleID}}" class="font-bold text-sm text-spot-text leading-tight hover:text-spot-green transition">{{.Title}}</a>
70+ <div class="flex items-center gap-2 mt-1 text-xs text-spot-secondary">
71+ {{if .Author}}<span>{{.Author}}</span>{{end}}
72+ <a href="/articles?feed={{.FeedURL}}" class="text-spot-muted hover:text-spot-green transition">{{.FeedTitle}}</a>
73+ {{if .Published.Valid}}<span>{{.Published.Time.Format "Jan 2"}}</span>{{end}}
74+ </div>
75+ {{if .Summary}}<p class="text-xs text-spot-secondary mt-2 line-clamp-2">{{.Summary}}</p>{{end}}
76+ </div>
7777 {{end}}
7878 </div>
7979 </div>
@@ -122,20 +122,20 @@
122122 </div>
123123 <div class="space-y-3">
124124 {{range .PersonalTrending}}
125- <a href="/articles/{{.ArticleID}}" class="block bg-spot-surface rounded-xl p-4 hover:bg-spot-hover-50 transition">
126- <div class="font-bold text-sm text-spot-text leading-tight">{{.Title}}</div>
125+ <div class="bg-spot-surface rounded-xl p-4 hover:bg-spot-hover-50 transition">
126+ <a href="/articles/{{.ArticleID}}" class="font-bold text-sm text-spot-text leading-tight hover:text-spot-green transition">{{.Title}}</a>
127127 <div class="flex items-center gap-2 mt-1 text-xs text-spot-secondary">
128- {{if .Author}}<span>{{.Author}}</span>{{end}}
129- <span class="text-spot-muted">{{if .FeedTitle}}{{.FeedTitle}}{{end}}</span>
128+ {{if .Author}}<span>{{.Author}}</span>{{end}}
129+ <span class="text-spot-muted"><a href="/articles?feed={{.FeedURL}}" class="hover:text-spot-green transition">{{if .FeedTitle}}{{.FeedTitle}}{{end}}</a></span>
130+ </div>
131+ <div class="flex items-center gap-3 mt-2 text-xs text-spot-secondary">
132+ <span class="inline-flex items-center gap-1"><svg class="w-4 h-4 text-spot-muted" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">{{template "icon-heart"}}</svg> {{.LikeCount}}</span>
133+ <span class="text-spot-muted">{{.AnnotationCount}} notes</span>
134+ </div>
130135 </div>
131- <div class="flex items-center gap-3 mt-2 text-xs text-spot-secondary">
132- <span class="inline-flex items-center gap-1"><svg class="w-4 h-4 text-spot-muted" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">{{template "icon-heart"}}</svg> {{.LikeCount}}</span>
133- <span class="text-spot-muted">{{.AnnotationCount}} notes</span>
134- </div>
135- </a>
136- {{end}}
136+ {{end}}
137+ </div>
137138 </div>
138- </div>
139139 {{end}}
140140
141141 {{if .PeopleRecommendations}}
@@ -27,53 +27,53 @@
27 </div>27 </div>
28 <div class="grid grid-cols-1 md:grid-cols-2 gap-3">28 <div class="grid grid-cols-1 md:grid-cols-2 gap-3">
29 {{range .GlobalTrending}}29 {{range .GlobalTrending}}
30- <a href="/articles/{{.ArticleID}}" class="block bg-spot-surface rounded-xl p-4 hover:bg-spot-hover-50 transition">30+ <div class="bg-spot-surface rounded-xl p-4 hover:bg-spot-hover-50 transition">
31- <div class="font-bold text-sm text-spot-text leading-tight">{{.Title}}</div>31+ <a href="/articles/{{.ArticleID}}" class="font-bold text-sm text-spot-text leading-tight hover:text-spot-green transition">{{.Title}}</a>
32- <div class="flex items-center gap-2 mt-1 text-xs text-spot-secondary">32+ <div class="flex items-center gap-2 mt-1 text-xs text-spot-secondary">
33- {{if .Author}}<span>{{.Author}}</span>{{end}}33+ {{if .Author}}<span>{{.Author}}</span>{{end}}
34- <span class="text-spot-muted">{{if .FeedTitle}}{{.FeedTitle}}{{end}}</span>34+ <span class="text-spot-muted"><a href="/articles?feed={{.FeedURL}}" class="hover:text-spot-green transition">{{if .FeedTitle}}{{.FeedTitle}}{{end}}</a></span>
35- </div>35+ </div>
36- <div class="flex items-center gap-3 mt-2 text-xs text-spot-secondary">36+ <div class="flex items-center gap-3 mt-2 text-xs text-spot-secondary">
37- <span class="inline-flex items-center gap-1"><svg class="w-4 h-4 text-spot-muted" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">{{template "icon-heart"}}</svg> {{.LikeCount}}</span>37+ <span class="inline-flex items-center gap-1"><svg class="w-4 h-4 text-spot-muted" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">{{template "icon-heart"}}</svg> {{.LikeCount}}</span>
38- <span class="text-spot-muted">{{.AnnotationCount}} notes</span>38+ <span class="text-spot-muted">{{.AnnotationCount}} notes</span>
39+ </div>
40+ </div>
41+ {{end}}
39 </div>42 </div>
40- </a>43+ </div>
41 {{end}}44 {{end}}
42- </div>
43-</div>
44-{{end}}
45 45
46-{{if .FeedRecommendations}}46+ {{if .FeedRecommendations}}
47-<div>47+ <div>
48- <h2 class="text-lg font-semibold text-spot-text mb-3">Popular feeds to get started</h2>48+ <h2 class="text-lg font-semibold text-spot-text mb-3">Popular feeds to get started</h2>
49- <div class="grid grid-cols-1 md:grid-cols-2 gap-3">49+ <div class="grid grid-cols-1 md:grid-cols-2 gap-3">
50- {{range .FeedRecommendations}}50+ {{range .FeedRecommendations}}
51- {{template "recommendation-card.html" (dict "title" .Title "feed_url" .FeedURL "description" .Description "favicon_url" .FaviconURL "subscriber_count" .SubscriberCount "CSRFToken" $.CSRFToken)}}51+ {{template "recommendation-card.html" (dict "title" .Title "feed_url" .FeedURL "description" .Description "favicon_url" .FaviconURL "subscriber_count" .SubscriberCount "CSRFToken" $.CSRFToken)}}
52+ {{end}}
53+ </div>
54+ </div>
52 {{end}}55 {{end}}
53- </div>56+ {{else}}
54-</div>57+ <p class="text-sm text-spot-secondary mb-6">Your personalized feed, based on your social graph.</p>
55-{{end}}
56-{{else}}
57-<p class="text-sm text-spot-secondary mb-6">Your personalized feed, based on your social graph.</p>
58 58
59-<div id="new-articles-poll" hx-get="/articles/new-count?since={{.Now.Unix}}&return=/dashboard" hx-trigger="every 30s" hx-swap="innerHTML"></div>59+ <div id="new-articles-poll" hx-get="/articles/new-count?since={{.Now.Unix}}&return=/dashboard" hx-trigger="every 30s" hx-swap="innerHTML"></div>
60 60
61-<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">61+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
62- <div class="lg:col-span-2">62+ <div class="lg:col-span-2">
63- {{if .ArticleRecommendations}}63+ {{if .ArticleRecommendations}}
64- <div class="mb-6">64+ <div class="mb-6">
65- <h2 class="text-lg font-semibold text-spot-text mb-3">Recommended for you</h2>65+ <h2 class="text-lg font-semibold text-spot-text mb-3">Recommended for you</h2>
66- <div class="space-y-3">66+ <div class="space-y-3">
67- {{range .ArticleRecommendations}}67+ {{range .ArticleRecommendations}}
68- <a href="/articles/{{.ArticleID}}" class="block bg-spot-surface rounded-xl p-4 hover:bg-spot-hover-50 transition">68+ <div class="bg-spot-surface rounded-xl p-4 hover:bg-spot-hover-50 transition">
69- <div class="font-bold text-sm text-spot-text leading-tight">{{.Title}}</div>69+ <a href="/articles/{{.ArticleID}}" class="font-bold text-sm text-spot-text leading-tight hover:text-spot-green transition">{{.Title}}</a>
70- <div class="flex items-center gap-2 mt-1 text-xs text-spot-secondary">70+ <div class="flex items-center gap-2 mt-1 text-xs text-spot-secondary">
71- {{if .Author}}<span>{{.Author}}</span>{{end}}71+ {{if .Author}}<span>{{.Author}}</span>{{end}}
72- <span class="text-spot-muted">{{.FeedTitle}}</span>72+ <a href="/articles?feed={{.FeedURL}}" class="text-spot-muted hover:text-spot-green transition">{{.FeedTitle}}</a>
73- {{if .Published.Valid}}<span>{{.Published.Time.Format "Jan 2"}}</span>{{end}}73+ {{if .Published.Valid}}<span>{{.Published.Time.Format "Jan 2"}}</span>{{end}}
74- </div>74+ </div>
75- {{if .Summary}}<p class="text-xs text-spot-secondary mt-2 line-clamp-2">{{.Summary}}</p>{{end}}75+ {{if .Summary}}<p class="text-xs text-spot-secondary mt-2 line-clamp-2">{{.Summary}}</p>{{end}}
76- </a>76+ </div>
77 {{end}}77 {{end}}
78 </div>78 </div>
79 </div>79 </div>
@@ -122,20 +122,20 @@
122 </div>122 </div>
123 <div class="space-y-3">123 <div class="space-y-3">
124 {{range .PersonalTrending}}124 {{range .PersonalTrending}}
125- <a href="/articles/{{.ArticleID}}" class="block bg-spot-surface rounded-xl p-4 hover:bg-spot-hover-50 transition">125+ <div class="bg-spot-surface rounded-xl p-4 hover:bg-spot-hover-50 transition">
126- <div class="font-bold text-sm text-spot-text leading-tight">{{.Title}}</div>126+ <a href="/articles/{{.ArticleID}}" class="font-bold text-sm text-spot-text leading-tight hover:text-spot-green transition">{{.Title}}</a>
127 <div class="flex items-center gap-2 mt-1 text-xs text-spot-secondary">127 <div class="flex items-center gap-2 mt-1 text-xs text-spot-secondary">
128- {{if .Author}}<span>{{.Author}}</span>{{end}}128+ {{if .Author}}<span>{{.Author}}</span>{{end}}
129- <span class="text-spot-muted">{{if .FeedTitle}}{{.FeedTitle}}{{end}}</span>129+ <span class="text-spot-muted"><a href="/articles?feed={{.FeedURL}}" class="hover:text-spot-green transition">{{if .FeedTitle}}{{.FeedTitle}}{{end}}</a></span>
130+ </div>
131+ <div class="flex items-center gap-3 mt-2 text-xs text-spot-secondary">
132+ <span class="inline-flex items-center gap-1"><svg class="w-4 h-4 text-spot-muted" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">{{template "icon-heart"}}</svg> {{.LikeCount}}</span>
133+ <span class="text-spot-muted">{{.AnnotationCount}} notes</span>
134+ </div>
130 </div>135 </div>
131- <div class="flex items-center gap-3 mt-2 text-xs text-spot-secondary">136+ {{end}}
132- <span class="inline-flex items-center gap-1"><svg class="w-4 h-4 text-spot-muted" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">{{template "icon-heart"}}</svg> {{.LikeCount}}</span>137+ </div>
133- <span class="text-spot-muted">{{.AnnotationCount}} notes</span>
134- </div>
135- </a>
136- {{end}}
137 </div>138 </div>
138- </div>
139 {{end}}139 {{end}}
140 140
141 {{if .PeopleRecommendations}}141 {{if .PeopleRecommendations}}
modified internal/tmpl/feeds.html +2 -2
@@ -62,7 +62,7 @@
6262 <div id="feed-list" class="bg-spot-surface rounded-xl divide-y divide-spot-divider">
6363 {{range .Subscriptions}}
6464 <div class="feed-item px-5 py-4 flex items-center justify-between hover:bg-spot-hover-50 transition rounded-xl">
65- <div class="min-w-0 flex-1 flex items-center gap-3">
65+ <a href="/articles?feed={{.FeedURL}}" class="min-w-0 flex-1 flex items-center gap-3">
6666 {{if .FaviconURL.Valid}}<img src="{{.FaviconURL.String}}" class="w-5 h-5 rounded shrink-0" loading="lazy">{{else}}<span class="shrink-0 w-5 h-5 flex items-center justify-center text-spot-muted">{{template "icon-globe"}}</span>{{end}}
6767 <div class="min-w-0 flex-1">
6868 <div class="flex items-center gap-2">
@@ -71,7 +71,7 @@
7171 </div>
7272 <div class="text-xs text-spot-muted truncate mt-0.5">{{.FeedURL}}</div>
7373 </div>
74- </div>
74+ </a>
7575 <div class="flex items-center gap-3">
7676 {{if .UnreadCount}}<span class="text-xs bg-spot-green/20 text-spot-green px-2.5 py-0.5 rounded-full font-bold">{{.UnreadCount}}</span>{{end}}
7777 <form hx-delete="/feeds/remove" hx-target="closest .feed-item" hx-swap="outerHTML swap:0.3s"
@@ -62,7 +62,7 @@
62 <div id="feed-list" class="bg-spot-surface rounded-xl divide-y divide-spot-divider">62 <div id="feed-list" class="bg-spot-surface rounded-xl divide-y divide-spot-divider">
63 {{range .Subscriptions}}63 {{range .Subscriptions}}
64 <div class="feed-item px-5 py-4 flex items-center justify-between hover:bg-spot-hover-50 transition rounded-xl">64 <div class="feed-item px-5 py-4 flex items-center justify-between hover:bg-spot-hover-50 transition rounded-xl">
65- <div class="min-w-0 flex-1 flex items-center gap-3">65+ <a href="/articles?feed={{.FeedURL}}" class="min-w-0 flex-1 flex items-center gap-3">
66 {{if .FaviconURL.Valid}}<img src="{{.FaviconURL.String}}" class="w-5 h-5 rounded shrink-0" loading="lazy">{{else}}<span class="shrink-0 w-5 h-5 flex items-center justify-center text-spot-muted">{{template "icon-globe"}}</span>{{end}}66 {{if .FaviconURL.Valid}}<img src="{{.FaviconURL.String}}" class="w-5 h-5 rounded shrink-0" loading="lazy">{{else}}<span class="shrink-0 w-5 h-5 flex items-center justify-center text-spot-muted">{{template "icon-globe"}}</span>{{end}}
67 <div class="min-w-0 flex-1">67 <div class="min-w-0 flex-1">
68 <div class="flex items-center gap-2">68 <div class="flex items-center gap-2">
@@ -71,7 +71,7 @@
71 </div>71 </div>
72 <div class="text-xs text-spot-muted truncate mt-0.5">{{.FeedURL}}</div>72 <div class="text-xs text-spot-muted truncate mt-0.5">{{.FeedURL}}</div>
73 </div>73 </div>
74- </div>74+ </a>
75 <div class="flex items-center gap-3">75 <div class="flex items-center gap-3">
76 {{if .UnreadCount}}<span class="text-xs bg-spot-green/20 text-spot-green px-2.5 py-0.5 rounded-full font-bold">{{.UnreadCount}}</span>{{end}}76 {{if .UnreadCount}}<span class="text-xs bg-spot-green/20 text-spot-green px-2.5 py-0.5 rounded-full font-bold">{{.UnreadCount}}</span>{{end}}
77 <form hx-delete="/feeds/remove" hx-target="closest .feed-item" hx-swap="outerHTML swap:0.3s"77 <form hx-delete="/feeds/remove" hx-target="closest .feed-item" hx-swap="outerHTML swap:0.3s"
modified internal/tmpl/partials/article-card.html +1 -1
@@ -10,7 +10,7 @@
1010 {{if .FeedFaviconURL.Valid}}<img src="{{.FeedFaviconURL.String}}" class="w-4 h-4 rounded shrink-0" loading="lazy">{{end}}
1111 {{if .Author.Valid}}{{if .Author.String}}<span>{{.Author.String}}</span>{{end}}{{end}}
1212 {{if .Published.Valid}}<span>{{.Published.Time.Format "Jan 2, 2006 15:04"}}</span>{{end}}
13- <span class="text-spot-muted">{{if .FeedTitle}}{{.FeedTitle}}{{else}}{{.FeedURL}}{{end}}</span>
13+ <span class="text-spot-muted"><a href="/articles?feed={{.FeedURL}}" class="hover:text-spot-green transition">{{if .FeedTitle}}{{.FeedTitle}}{{else}}{{.FeedURL}}{{end}}</a></span>
1414 </div>
1515 {{if .Summary.Valid}}{{if .Summary.String}}
1616 <p class="text-sm text-spot-secondary mt-2 line-clamp-2">{{plainText .Summary.String}}</p>
@@ -10,7 +10,7 @@
10 {{if .FeedFaviconURL.Valid}}<img src="{{.FeedFaviconURL.String}}" class="w-4 h-4 rounded shrink-0" loading="lazy">{{end}}10 {{if .FeedFaviconURL.Valid}}<img src="{{.FeedFaviconURL.String}}" class="w-4 h-4 rounded shrink-0" loading="lazy">{{end}}
11 {{if .Author.Valid}}{{if .Author.String}}<span>{{.Author.String}}</span>{{end}}{{end}}11 {{if .Author.Valid}}{{if .Author.String}}<span>{{.Author.String}}</span>{{end}}{{end}}
12 {{if .Published.Valid}}<span>{{.Published.Time.Format "Jan 2, 2006 15:04"}}</span>{{end}}12 {{if .Published.Valid}}<span>{{.Published.Time.Format "Jan 2, 2006 15:04"}}</span>{{end}}
13- <span class="text-spot-muted">{{if .FeedTitle}}{{.FeedTitle}}{{else}}{{.FeedURL}}{{end}}</span>13+ <span class="text-spot-muted"><a href="/articles?feed={{.FeedURL}}" class="hover:text-spot-green transition">{{if .FeedTitle}}{{.FeedTitle}}{{else}}{{.FeedURL}}{{end}}</a></span>
14 </div>14 </div>
15 {{if .Summary.Valid}}{{if .Summary.String}}15 {{if .Summary.Valid}}{{if .Summary.String}}
16 <p class="text-sm text-spot-secondary mt-2 line-clamp-2">{{plainText .Summary.String}}</p>16 <p class="text-sm text-spot-secondary mt-2 line-clamp-2">{{plainText .Summary.String}}</p>
modified internal/tmpl/partials/recommendation-card.html +2 -2
@@ -1,13 +1,13 @@
11 {{define "recommendation-card.html"}}
22 <div class="bg-spot-surface rounded-xl p-3 hover:bg-spot-hover-50 transition">
33 <div class="flex items-center justify-between gap-2">
4- <div class="min-w-0 flex items-center gap-2">
4+ <a href="/articles?feed={{.feed_url}}" class="min-w-0 flex items-center gap-2 flex-1">
55 {{if .favicon_url}}<img src="{{.favicon_url}}" class="w-4 h-4 rounded shrink-0" loading="lazy">{{end}}
66 <div class="min-w-0">
77 <div class="font-bold text-sm text-spot-text truncate">{{if .title}}{{.title}}{{else}}{{.feed_url}}{{end}}</div>
88 {{if .description}}<p class="text-xs text-spot-secondary truncate mt-0.5">{{.description}}</p>{{end}}
99 </div>
10- </div>
10+ </a>
1111 <div class="flex items-center gap-2 shrink-0">
1212 <span class="text-xs text-spot-secondary">{{.subscriber_count}} subs</span>
1313 <form hx-post="/feeds/add" hx-target="closest .bg-spot-surface" hx-swap="outerHTML" class="inline">
@@ -1,13 +1,13 @@
1 {{define "recommendation-card.html"}}1 {{define "recommendation-card.html"}}
2 <div class="bg-spot-surface rounded-xl p-3 hover:bg-spot-hover-50 transition">2 <div class="bg-spot-surface rounded-xl p-3 hover:bg-spot-hover-50 transition">
3 <div class="flex items-center justify-between gap-2">3 <div class="flex items-center justify-between gap-2">
4- <div class="min-w-0 flex items-center gap-2">4+ <a href="/articles?feed={{.feed_url}}" class="min-w-0 flex items-center gap-2 flex-1">
5 {{if .favicon_url}}<img src="{{.favicon_url}}" class="w-4 h-4 rounded shrink-0" loading="lazy">{{end}}5 {{if .favicon_url}}<img src="{{.favicon_url}}" class="w-4 h-4 rounded shrink-0" loading="lazy">{{end}}
6 <div class="min-w-0">6 <div class="min-w-0">
7 <div class="font-bold text-sm text-spot-text truncate">{{if .title}}{{.title}}{{else}}{{.feed_url}}{{end}}</div>7 <div class="font-bold text-sm text-spot-text truncate">{{if .title}}{{.title}}{{else}}{{.feed_url}}{{end}}</div>
8 {{if .description}}<p class="text-xs text-spot-secondary truncate mt-0.5">{{.description}}</p>{{end}}8 {{if .description}}<p class="text-xs text-spot-secondary truncate mt-0.5">{{.description}}</p>{{end}}
9 </div>9 </div>
10- </div>10+ </a>
11 <div class="flex items-center gap-2 shrink-0">11 <div class="flex items-center gap-2 shrink-0">
12 <span class="text-xs text-spot-secondary">{{.subscriber_count}} subs</span>12 <span class="text-xs text-spot-secondary">{{.subscriber_count}} subs</span>
13 <form hx-post="/feeds/add" hx-target="closest .bg-spot-surface" hx-swap="outerHTML" class="inline">13 <form hx-post="/feeds/add" hx-target="closest .bg-spot-surface" hx-swap="outerHTML" class="inline">