Display feed info and add clickable feed linksUnverified
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, | ||
| 72 | 72 | JOIN subscriptions s ON a.feed_url = s.feed_url AND s.user_did = ? |
| 73 | 73 | LEFT JOIN feeds f ON a.feed_url = f.feed_url |
| 74 | 74 | LEFT JOIN read_state r ON r.user_did = ? AND r.article_id = a.id |
| 75 | + WHERE 1=1 | |
| 75 | 76 | ` |
| 76 | 77 | args := []any{userDID, userDID} |
| 77 | 78 | |
| 78 | 79 | if feedURL != "" { |
| 79 | - query += ` AND s.feed_url = ?` | |
| 80 | + query += ` AND a.feed_url = ?` | |
| 80 | 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 | 112 | JOIN subscriptions s ON a.feed_url = s.feed_url AND s.user_did = ? |
| 112 | 113 | LEFT JOIN feeds f ON a.feed_url = f.feed_url |
| 113 | 114 | 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) | |
| 115 | 116 | ` |
| 116 | 117 | args := []any{userDID, userDID} |
| 117 | 118 | |
| @@ -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_url | 73 | 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.id | 74 | 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_url | 113 | 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.id | 114 | 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) |
| 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) { | ||
| 75 | 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 | 86 | if r.Header.Get("HX-Request") == "true" { |
| 79 | 87 | s.render(w, r, "articles-content.html", data) |
| 80 | 88 | 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 | return | 88 | return |
modified
internal/tmpl/articles.html +28 -2 | @@ -1,14 +1,40 @@ | ||
| 1 | 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">← 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 | 29 | <div class="flex items-center justify-between mb-2"> |
| 4 | 30 | <h1 class="text-2xl font-bold text-spot-text">Articles</h1> |
| 5 | 31 | <form hx-post="/articles/mark-all-read" hx-confirm="Mark all articles as read?"> |
| 6 | 32 | {{csrfInput .CSRFToken}} |
| 7 | - {{if .FeedURL}}<input type="hidden" name="feed" value="{{.FeedURL}}">{{end}} | |
| 8 | 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 | 34 | </form> |
| 10 | 35 | </div> |
| 11 | 36 | <p class="text-sm text-spot-secondary mb-6">All your subscribed articles, newest first.</p> |
| 37 | + {{end}} | |
| 12 | 38 | |
| 13 | 39 | <div class="flex items-center gap-3 mb-6"> |
| 14 | 40 | <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">← 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 @@ | ||
| 27 | 27 | </div> |
| 28 | 28 | <div class="grid grid-cols-1 md:grid-cols-2 gap-3"> |
| 29 | 29 | {{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}} | |
| 39 | 42 | </div> |
| 40 | - </a> | |
| 43 | + </div> | |
| 41 | 44 | {{end}} |
| 42 | - </div> | |
| 43 | -</div> | |
| 44 | -{{end}} | |
| 45 | 45 | |
| 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> | |
| 52 | 55 | {{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> | |
| 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"> | |
| 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> | |
| 77 | 77 | {{end}} |
| 78 | 78 | </div> |
| 79 | 79 | </div> |
| @@ -122,20 +122,20 @@ | ||
| 122 | 122 | </div> |
| 123 | 123 | <div class="space-y-3"> |
| 124 | 124 | {{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> | |
| 127 | 127 | <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> | |
| 130 | 135 | </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> | |
| 137 | 138 | </div> |
| 138 | - </div> | |
| 139 | 139 | {{end}} |
| 140 | 140 | |
| 141 | 141 | {{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 @@ | ||
| 62 | 62 | <div id="feed-list" class="bg-spot-surface rounded-xl divide-y divide-spot-divider"> |
| 63 | 63 | {{range .Subscriptions}} |
| 64 | 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 | 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 | 67 | <div class="min-w-0 flex-1"> |
| 68 | 68 | <div class="flex items-center gap-2"> |
| @@ -71,7 +71,7 @@ | ||
| 71 | 71 | </div> |
| 72 | 72 | <div class="text-xs text-spot-muted truncate mt-0.5">{{.FeedURL}}</div> |
| 73 | 73 | </div> |
| 74 | - </div> | |
| 74 | + </a> | |
| 75 | 75 | <div class="flex items-center gap-3"> |
| 76 | 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 | 77 | <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 @@ | ||
| 10 | 10 | {{if .FeedFaviconURL.Valid}}<img src="{{.FeedFaviconURL.String}}" class="w-4 h-4 rounded shrink-0" loading="lazy">{{end}} |
| 11 | 11 | {{if .Author.Valid}}{{if .Author.String}}<span>{{.Author.String}}</span>{{end}}{{end}} |
| 12 | 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 | 14 | </div> |
| 15 | 15 | {{if .Summary.Valid}}{{if .Summary.String}} |
| 16 | 16 | <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 @@ | ||
| 1 | 1 | {{define "recommendation-card.html"}} |
| 2 | 2 | <div class="bg-spot-surface rounded-xl p-3 hover:bg-spot-hover-50 transition"> |
| 3 | 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 | 5 | {{if .favicon_url}}<img src="{{.favicon_url}}" class="w-4 h-4 rounded shrink-0" loading="lazy">{{end}} |
| 6 | 6 | <div class="min-w-0"> |
| 7 | 7 | <div class="font-bold text-sm text-spot-text truncate">{{if .title}}{{.title}}{{else}}{{.feed_url}}{{end}}</div> |
| 8 | 8 | {{if .description}}<p class="text-xs text-spot-secondary truncate mt-0.5">{{.description}}</p>{{end}} |
| 9 | 9 | </div> |
| 10 | - </div> | |
| 10 | + </a> | |
| 11 | 11 | <div class="flex items-center gap-2 shrink-0"> |
| 12 | 12 | <span class="text-xs text-spot-secondary">{{.subscriber_count}} subs</span> |
| 13 | 13 | <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"> |
modified
internal/tmpl/trending.html +1 -1 | @@ -21,7 +21,7 @@ | ||
| 21 | 21 | <div class="text-sm text-spot-secondary mt-1 flex items-center gap-2"> |
| 22 | 22 | {{if $t.FaviconURL}}<img src="{{$t.FaviconURL}}" class="w-4 h-4 rounded shrink-0" loading="lazy">{{end}} |
| 23 | 23 | {{if $t.Author}}<span>{{$t.Author}}</span>{{end}} |
| 24 | - <span class="text-spot-muted">{{if $t.FeedTitle}}{{$t.FeedTitle}}{{else}}{{$t.FeedURL}}{{end}}</span> | |
| 24 | + <span class="text-spot-muted"><a href="/articles?feed={{$t.FeedURL}}" class="hover:text-spot-green transition">{{if $t.FeedTitle}}{{$t.FeedTitle}}{{else}}{{$t.FeedURL}}{{end}}</a></span> | |
| 25 | 25 | </div> |
| 26 | 26 | {{if $t.Summary}} |
| 27 | 27 | <p class="text-sm text-spot-secondary mt-2 line-clamp-2">{{$t.Summary}}</p> |
| @@ -21,7 +21,7 @@ | |||
| 21 | <div class="text-sm text-spot-secondary mt-1 flex items-center gap-2"> | 21 | <div class="text-sm text-spot-secondary mt-1 flex items-center gap-2"> |
| 22 | {{if $t.FaviconURL}}<img src="{{$t.FaviconURL}}" class="w-4 h-4 rounded shrink-0" loading="lazy">{{end}} | 22 | {{if $t.FaviconURL}}<img src="{{$t.FaviconURL}}" class="w-4 h-4 rounded shrink-0" loading="lazy">{{end}} |
| 23 | {{if $t.Author}}<span>{{$t.Author}}</span>{{end}} | 23 | {{if $t.Author}}<span>{{$t.Author}}</span>{{end}} |
| 24 | - <span class="text-spot-muted">{{if $t.FeedTitle}}{{$t.FeedTitle}}{{else}}{{$t.FeedURL}}{{end}}</span> | 24 | + <span class="text-spot-muted"><a href="/articles?feed={{$t.FeedURL}}" class="hover:text-spot-green transition">{{if $t.FeedTitle}}{{$t.FeedTitle}}{{else}}{{$t.FeedURL}}{{end}}</a></span> |
| 25 | </div> | 25 | </div> |
| 26 | {{if $t.Summary}} | 26 | {{if $t.Summary}} |
| 27 | <p class="text-sm text-spot-secondary mt-2 line-clamp-2">{{$t.Summary}}</p> | 27 | <p class="text-sm text-spot-secondary mt-2 line-clamp-2">{{$t.Summary}}</p> |