nandi/gleanpublic⑂ Fork 0
⑂ ffa0890
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.

Extract pagination templates and align detail card.Unverified

Julien Robert committed 2026-04-21T20:46:32+02:00 Browse files
ffa0890 parent: 078b9a3
modified docs/specs.md +8 -5
@@ -303,10 +303,10 @@ A background scheduler polls subscribed feeds on a fixed 5-minute tick. Feeds ar
303303 │
304304 ┌──────────────┼──────────────┐
305305 │ │ │
306- ┌────▼────┐ ┌─────▼─────┐ ┌─────▼─────┐
307- │RSS/XML │ │Atom/XML │ │JSON Feed │
308- │Parser │ │Parser │ │Parser │
309- └─────────┘ └───────────┘ └───────────┘
306+ ┌────▼────┐ ┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
307+ │RSS/XML │ │RSS1/RDF │ │Atom/XML │ │JSON Feed │
308+ │Parser │ │Parser │ │Parser │ │Parser │
309+ └─────────┘ └───────────┘ └───────────┘ └───────────┘
310310 ```
311311
312312 ### 4.2 Fetch Schedule
@@ -799,6 +799,7 @@ glean/
799799 │ ├── atproto/
800800 │ │ ├── auth.go # DID resolution, OAuth flow
801801 │ │ ├── client.go # XRPC client (write to user PDS)
802+│ │ ├── collectiondir.go # Collection directory backfill (startup)
802803 │ │ ├── jetstream.go # Subscribe to Jetstream via official client
803804 │ │ ├── stream_handler.go # Stream event → DB handler
804805 │ │ ├── lexicon.go # Lexicon record types (at.glean.*, maintained by hand)
@@ -817,7 +818,7 @@ glean/
817818 │ │ ├── oauth_store.go # OAuth session storage
818819 │ │ └── store.go # FeedStore adapter for scheduler
819820 │ ├── feed/
820-│ │ ├── parser.go # RSS/Atom/JSON feed parser
821+│ │ ├── parser.go # RSS/Atom/RDF/JSON feed parser
821822 │ │ ├── fetcher.go # Scheduler with dedup + Fetcher
822823 │ │ ├── discover.go # Feed auto-discovery from URLs
823824 │ │ └── opml.go # OPML import/export
@@ -847,6 +848,7 @@ glean/
847848 │ └── tmpl/
848849 │ ├── base.html # Base template with htmx + Tailwind
849850 │ ├── index.html # Landing page
851+│ ├── login.html # Login page
850852 │ ├── dashboard.html # Dashboard
851853 │ ├── feeds.html # Feed management
852854 │ ├── articles.html # Article listing
@@ -854,6 +856,7 @@ glean/
854856 │ ├── trending.html # Trending articles
855857 │ ├── library.html # Liked articles + annotations
856858 │ ├── profile.html # User profile
859+│ ├── 404.html # Not found page
857860 │ └── partials/ # Reusable template fragments
858861 ├── static/
859862 │ ├── input.css # Tailwind input
@@ -303,10 +303,10 @@ A background scheduler polls subscribed feeds on a fixed 5-minute tick. Feeds ar
303 │303 │
304 ┌──────────────┼──────────────┐304 ┌──────────────┼──────────────┐
305 │ │ │305 │ │ │
306- ┌────▼────┐ ┌─────▼─────┐ ┌─────▼─────┐306+ ┌────▼────┐ ┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
307- │RSS/XML │ │Atom/XML │ │JSON Feed │307+ │RSS/XML │ │RSS1/RDF │ │Atom/XML │ │JSON Feed │
308- │Parser │ │Parser │ │Parser │308+ │Parser │ │Parser │ │Parser │ │Parser │
309- └─────────┘ └───────────┘ └───────────┘309+ └─────────┘ └───────────┘ └───────────┘ └───────────┘
310 ```310 ```
311 311
312 ### 4.2 Fetch Schedule312 ### 4.2 Fetch Schedule
@@ -799,6 +799,7 @@ glean/
799 │ ├── atproto/799 │ ├── atproto/
800 │ │ ├── auth.go # DID resolution, OAuth flow800 │ │ ├── auth.go # DID resolution, OAuth flow
801 │ │ ├── client.go # XRPC client (write to user PDS)801 │ │ ├── client.go # XRPC client (write to user PDS)
802+│ │ ├── collectiondir.go # Collection directory backfill (startup)
802 │ │ ├── jetstream.go # Subscribe to Jetstream via official client803 │ │ ├── jetstream.go # Subscribe to Jetstream via official client
803 │ │ ├── stream_handler.go # Stream event → DB handler804 │ │ ├── stream_handler.go # Stream event → DB handler
804 │ │ ├── lexicon.go # Lexicon record types (at.glean.*, maintained by hand)805 │ │ ├── lexicon.go # Lexicon record types (at.glean.*, maintained by hand)
@@ -817,7 +818,7 @@ glean/
817 │ │ ├── oauth_store.go # OAuth session storage818 │ │ ├── oauth_store.go # OAuth session storage
818 │ │ └── store.go # FeedStore adapter for scheduler819 │ │ └── store.go # FeedStore adapter for scheduler
819 │ ├── feed/820 │ ├── feed/
820-│ │ ├── parser.go # RSS/Atom/JSON feed parser821+│ │ ├── parser.go # RSS/Atom/RDF/JSON feed parser
821 │ │ ├── fetcher.go # Scheduler with dedup + Fetcher822 │ │ ├── fetcher.go # Scheduler with dedup + Fetcher
822 │ │ ├── discover.go # Feed auto-discovery from URLs823 │ │ ├── discover.go # Feed auto-discovery from URLs
823 │ │ └── opml.go # OPML import/export824 │ │ └── opml.go # OPML import/export
@@ -847,6 +848,7 @@ glean/
847 │ └── tmpl/848 │ └── tmpl/
848 │ ├── base.html # Base template with htmx + Tailwind849 │ ├── base.html # Base template with htmx + Tailwind
849 │ ├── index.html # Landing page850 │ ├── index.html # Landing page
851+│ ├── login.html # Login page
850 │ ├── dashboard.html # Dashboard852 │ ├── dashboard.html # Dashboard
851 │ ├── feeds.html # Feed management853 │ ├── feeds.html # Feed management
852 │ ├── articles.html # Article listing854 │ ├── articles.html # Article listing
@@ -854,6 +856,7 @@ glean/
854 │ ├── trending.html # Trending articles856 │ ├── trending.html # Trending articles
855 │ ├── library.html # Liked articles + annotations857 │ ├── library.html # Liked articles + annotations
856 │ ├── profile.html # User profile858 │ ├── profile.html # User profile
859+│ ├── 404.html # Not found page
857 │ └── partials/ # Reusable template fragments860 │ └── partials/ # Reusable template fragments
858 ├── static/861 ├── static/
859 │ ├── input.css # Tailwind input862 │ ├── input.css # Tailwind input
modified internal/tmpl/article_detail.html +2 -2
@@ -16,7 +16,7 @@
1616 {{if .Article.Published.Valid}}<span>{{.Article.Published.Time.Format "Jan 2, 2006 15:04"}}</span>{{end}}
1717 {{if .Feed}}
1818 <a href="/articles?feed={{.Feed.FeedURL}}" class="inline-flex items-center gap-1 hover:text-spot-green transition">
19- {{if .Feed.FaviconURL.Valid}}<img src="{{.Feed.FaviconURL.String}}" class="w-4 h-4 rounded-sm">{{end}}
19+ {{if .Feed.FaviconURL.Valid}}<img src="{{.Feed.FaviconURL.String}}" class="w-4 h-4 rounded">{{end}}
2020 {{if .Feed.Title.Valid}}{{.Feed.Title.String}}{{else}}{{.Feed.FeedURL}}{{end}}
2121 </a>
2222 {{end}}
@@ -109,7 +109,7 @@
109109 <input type="hidden" name="quote" id="quote-input">
110110 <div>
111111 <textarea name="note" rows="2" placeholder="Add a note..."
112- class="w-full bg-spot-hover text-spot-text rounded-lg px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-spot-green placeholder:text-spot-placeholder resize-none"></textarea>
112+ class="w-full bg-spot-hover text-spot-text rounded-pill px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-spot-green placeholder:text-spot-placeholder resize-none"></textarea>
113113 </div>
114114 <div class="flex gap-2">
115115 <input type="text" name="tags" placeholder="Tags (comma separated)"
@@ -16,7 +16,7 @@
16 {{if .Article.Published.Valid}}<span>{{.Article.Published.Time.Format "Jan 2, 2006 15:04"}}</span>{{end}}16 {{if .Article.Published.Valid}}<span>{{.Article.Published.Time.Format "Jan 2, 2006 15:04"}}</span>{{end}}
17 {{if .Feed}}17 {{if .Feed}}
18 <a href="/articles?feed={{.Feed.FeedURL}}" class="inline-flex items-center gap-1 hover:text-spot-green transition">18 <a href="/articles?feed={{.Feed.FeedURL}}" class="inline-flex items-center gap-1 hover:text-spot-green transition">
19- {{if .Feed.FaviconURL.Valid}}<img src="{{.Feed.FaviconURL.String}}" class="w-4 h-4 rounded-sm">{{end}}19+ {{if .Feed.FaviconURL.Valid}}<img src="{{.Feed.FaviconURL.String}}" class="w-4 h-4 rounded">{{end}}
20 {{if .Feed.Title.Valid}}{{.Feed.Title.String}}{{else}}{{.Feed.FeedURL}}{{end}}20 {{if .Feed.Title.Valid}}{{.Feed.Title.String}}{{else}}{{.Feed.FeedURL}}{{end}}
21 </a>21 </a>
22 {{end}}22 {{end}}
@@ -109,7 +109,7 @@
109 <input type="hidden" name="quote" id="quote-input">109 <input type="hidden" name="quote" id="quote-input">
110 <div>110 <div>
111 <textarea name="note" rows="2" placeholder="Add a note..."111 <textarea name="note" rows="2" placeholder="Add a note..."
112- class="w-full bg-spot-hover text-spot-text rounded-lg px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-spot-green placeholder:text-spot-placeholder resize-none"></textarea>112+ class="w-full bg-spot-hover text-spot-text rounded-pill px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-spot-green placeholder:text-spot-placeholder resize-none"></textarea>
113 </div>113 </div>
114 <div class="flex gap-2">114 <div class="flex gap-2">
115 <input type="text" name="tags" placeholder="Tags (comma separated)"115 <input type="text" name="tags" placeholder="Tags (comma separated)"
modified internal/tmpl/articles.html +1 -17
@@ -70,23 +70,7 @@
7070 {{template "articles-content.html" .}}
7171 </div>
7272
73-{{if or .Page.HasPrev .Page.HasNext}}
74-<div class="flex items-center justify-center gap-3 py-6">
75- {{if .Page.HasPrev}}
76- <a href="{{paginationURL .BaseURL .Page.PrevPage .QueryParams}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">&laquo; Prev</a>
77- {{else}}
78- <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">&laquo; Prev</span>
79- {{end}}
80-
81- <span class="text-sm text-spot-secondary">Page {{.Page.Page}}</span>
82-
83- {{if .Page.HasNext}}
84- <a href="{{paginationURL .BaseURL .Page.NextPage .QueryParams}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">Next &raquo;</a>
85- {{else}}
86- <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">Next &raquo;</span>
87- {{end}}
88-</div>
89-{{end}}
73+{{template "pagination.html" (dict "HasPrev" .Page.HasPrev "HasNext" .Page.HasNext "PrevPage" .Page.PrevPage "NextPage" .Page.NextPage "Page" .Page.Page "BaseURL" .BaseURL "QueryParams" .QueryParams)}}
9074
9175 <script>
9276 (function() {
@@ -70,23 +70,7 @@
70 {{template "articles-content.html" .}}70 {{template "articles-content.html" .}}
71 </div>71 </div>
72 72
73-{{if or .Page.HasPrev .Page.HasNext}}73+{{template "pagination.html" (dict "HasPrev" .Page.HasPrev "HasNext" .Page.HasNext "PrevPage" .Page.PrevPage "NextPage" .Page.NextPage "Page" .Page.Page "BaseURL" .BaseURL "QueryParams" .QueryParams)}}
74-<div class="flex items-center justify-center gap-3 py-6">
75- {{if .Page.HasPrev}}
76- <a href="{{paginationURL .BaseURL .Page.PrevPage .QueryParams}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">&laquo; Prev</a>
77- {{else}}
78- <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">&laquo; Prev</span>
79- {{end}}
80-
81- <span class="text-sm text-spot-secondary">Page {{.Page.Page}}</span>
82-
83- {{if .Page.HasNext}}
84- <a href="{{paginationURL .BaseURL .Page.NextPage .QueryParams}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">Next &raquo;</a>
85- {{else}}
86- <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">Next &raquo;</span>
87- {{end}}
88-</div>
89-{{end}}
90 74
91 <script>75 <script>
92 (function() {76 (function() {
modified internal/tmpl/dashboard.html +2 -25
@@ -97,23 +97,7 @@
9797 {{end}}
9898 </div>
9999
100- {{if or .Page.HasPrev .Page.HasNext}}
101- <div class="flex items-center justify-center gap-3 py-6">
102- {{if .Page.HasPrev}}
103- <a href="{{paginationURL .BaseURL .Page.PrevPage .QueryParams}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">&laquo; Prev</a>
104- {{else}}
105- <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">&laquo; Prev</span>
106- {{end}}
107-
108- <span class="text-sm text-spot-secondary">Page {{.Page.Page}}</span>
109-
110- {{if .Page.HasNext}}
111- <a href="{{paginationURL .BaseURL .Page.NextPage .QueryParams}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">Next &raquo;</a>
112- {{else}}
113- <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">Next &raquo;</span>
114- {{end}}
115- </div>
116- {{end}}
100+ {{template "pagination.html" (dict "HasPrev" .Page.HasPrev "HasNext" .Page.HasNext "PrevPage" .Page.PrevPage "NextPage" .Page.NextPage "Page" .Page.Page "BaseURL" .BaseURL "QueryParams" .QueryParams)}}
117101 </div>
118102
119103 <div class="space-y-8">
@@ -136,14 +120,7 @@
136120 <h2 class="text-lg font-semibold text-spot-text mb-4">Similar readers</h2>
137121 <div class="space-y-3">
138122 {{range .PeopleRecommendations}}
139- <div class="bg-spot-surface rounded-xl p-4 flex items-center gap-3 hover:bg-spot-hover-50 transition">
140- {{if .AvatarURL}}<img src="{{.AvatarURL}}" class="w-10 h-10 rounded-full">{{end}}
141- <div class="min-w-0 flex-1">
142- <a href="/profile/{{.Handle}}" class="font-bold text-spot-text hover:text-spot-green transition">@{{.Handle}}</a>
143- {{if .DisplayName}}<div class="text-sm text-spot-secondary">{{.DisplayName}}</div>{{end}}
144- </div>
145- <span class="text-xs text-spot-secondary">{{.CommonFeeds}} shared</span>
146- </div>
123+ {{template "profile-card.html" .}}
147124 {{end}}
148125 </div>
149126 </div>
@@ -97,23 +97,7 @@
97 {{end}}97 {{end}}
98 </div>98 </div>
99 99
100- {{if or .Page.HasPrev .Page.HasNext}}100+ {{template "pagination.html" (dict "HasPrev" .Page.HasPrev "HasNext" .Page.HasNext "PrevPage" .Page.PrevPage "NextPage" .Page.NextPage "Page" .Page.Page "BaseURL" .BaseURL "QueryParams" .QueryParams)}}
101- <div class="flex items-center justify-center gap-3 py-6">
102- {{if .Page.HasPrev}}
103- <a href="{{paginationURL .BaseURL .Page.PrevPage .QueryParams}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">&laquo; Prev</a>
104- {{else}}
105- <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">&laquo; Prev</span>
106- {{end}}
107-
108- <span class="text-sm text-spot-secondary">Page {{.Page.Page}}</span>
109-
110- {{if .Page.HasNext}}
111- <a href="{{paginationURL .BaseURL .Page.NextPage .QueryParams}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">Next &raquo;</a>
112- {{else}}
113- <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">Next &raquo;</span>
114- {{end}}
115- </div>
116- {{end}}
117 </div>101 </div>
118 102
119 <div class="space-y-8">103 <div class="space-y-8">
@@ -136,14 +120,7 @@
136 <h2 class="text-lg font-semibold text-spot-text mb-4">Similar readers</h2>120 <h2 class="text-lg font-semibold text-spot-text mb-4">Similar readers</h2>
137 <div class="space-y-3">121 <div class="space-y-3">
138 {{range .PeopleRecommendations}}122 {{range .PeopleRecommendations}}
139- <div class="bg-spot-surface rounded-xl p-4 flex items-center gap-3 hover:bg-spot-hover-50 transition">123+ {{template "profile-card.html" .}}
140- {{if .AvatarURL}}<img src="{{.AvatarURL}}" class="w-10 h-10 rounded-full">{{end}}
141- <div class="min-w-0 flex-1">
142- <a href="/profile/{{.Handle}}" class="font-bold text-spot-text hover:text-spot-green transition">@{{.Handle}}</a>
143- {{if .DisplayName}}<div class="text-sm text-spot-secondary">{{.DisplayName}}</div>{{end}}
144- </div>
145- <span class="text-xs text-spot-secondary">{{.CommonFeeds}} shared</span>
146- </div>
147 {{end}}124 {{end}}
148 </div>125 </div>
149 </div>126 </div>
modified internal/tmpl/feeds.html +2 -25
@@ -93,23 +93,7 @@
9393 {{end}}
9494 </div>
9595
96- {{if or .Page.HasPrev .Page.HasNext}}
97- <div class="flex items-center justify-center gap-3 py-6">
98- {{if .Page.HasPrev}}
99- <a href="{{paginationURL .BaseURL .Page.PrevPage .QueryParams}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">&laquo; Prev</a>
100- {{else}}
101- <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">&laquo; Prev</span>
102- {{end}}
103-
104- <span class="text-sm text-spot-secondary">Page {{.Page.Page}}</span>
105-
106- {{if .Page.HasNext}}
107- <a href="{{paginationURL .BaseURL .Page.NextPage .QueryParams}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">Next &raquo;</a>
108- {{else}}
109- <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">Next &raquo;</span>
110- {{end}}
111- </div>
112- {{end}}
96+ {{template "pagination.html" (dict "HasPrev" .Page.HasPrev "HasNext" .Page.HasNext "PrevPage" .Page.PrevPage "NextPage" .Page.NextPage "Page" .Page.Page "BaseURL" .BaseURL "QueryParams" .QueryParams)}}
11397 </div>
11498
11599 <div class="space-y-6">
@@ -140,14 +124,7 @@
140124 <h2 class="text-sm font-bold text-spot-text uppercase tracking-wide mb-4">Similar readers</h2>
141125 <div class="space-y-3">
142126 {{range .PeopleRecommendations}}
143- <div class="bg-spot-surface rounded-xl p-4 flex items-center gap-3 hover:bg-spot-hover-50 transition">
144- {{if .AvatarURL}}<img src="{{.AvatarURL}}" class="w-10 h-10 rounded-full">{{end}}
145- <div class="min-w-0 flex-1">
146- <a href="/profile/{{.Handle}}" class="font-bold text-spot-text hover:text-spot-green transition">@{{.Handle}}</a>
147- {{if .DisplayName}}<div class="text-sm text-spot-secondary">{{.DisplayName}}</div>{{end}}
148- </div>
149- <span class="text-xs text-spot-secondary">{{.CommonFeeds}} shared</span>
150- </div>
127+ {{template "profile-card.html" .}}
151128 {{end}}
152129 </div>
153130 </div>
@@ -93,23 +93,7 @@
93 {{end}}93 {{end}}
94 </div>94 </div>
95 95
96- {{if or .Page.HasPrev .Page.HasNext}}96+ {{template "pagination.html" (dict "HasPrev" .Page.HasPrev "HasNext" .Page.HasNext "PrevPage" .Page.PrevPage "NextPage" .Page.NextPage "Page" .Page.Page "BaseURL" .BaseURL "QueryParams" .QueryParams)}}
97- <div class="flex items-center justify-center gap-3 py-6">
98- {{if .Page.HasPrev}}
99- <a href="{{paginationURL .BaseURL .Page.PrevPage .QueryParams}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">&laquo; Prev</a>
100- {{else}}
101- <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">&laquo; Prev</span>
102- {{end}}
103-
104- <span class="text-sm text-spot-secondary">Page {{.Page.Page}}</span>
105-
106- {{if .Page.HasNext}}
107- <a href="{{paginationURL .BaseURL .Page.NextPage .QueryParams}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">Next &raquo;</a>
108- {{else}}
109- <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">Next &raquo;</span>
110- {{end}}
111- </div>
112- {{end}}
113 </div>97 </div>
114 98
115 <div class="space-y-6">99 <div class="space-y-6">
@@ -140,14 +124,7 @@
140 <h2 class="text-sm font-bold text-spot-text uppercase tracking-wide mb-4">Similar readers</h2>124 <h2 class="text-sm font-bold text-spot-text uppercase tracking-wide mb-4">Similar readers</h2>
141 <div class="space-y-3">125 <div class="space-y-3">
142 {{range .PeopleRecommendations}}126 {{range .PeopleRecommendations}}
143- <div class="bg-spot-surface rounded-xl p-4 flex items-center gap-3 hover:bg-spot-hover-50 transition">127+ {{template "profile-card.html" .}}
144- {{if .AvatarURL}}<img src="{{.AvatarURL}}" class="w-10 h-10 rounded-full">{{end}}
145- <div class="min-w-0 flex-1">
146- <a href="/profile/{{.Handle}}" class="font-bold text-spot-text hover:text-spot-green transition">@{{.Handle}}</a>
147- {{if .DisplayName}}<div class="text-sm text-spot-secondary">{{.DisplayName}}</div>{{end}}
148- </div>
149- <span class="text-xs text-spot-secondary">{{.CommonFeeds}} shared</span>
150- </div>
151 {{end}}128 {{end}}
152 </div>129 </div>
153 </div>130 </div>
deleted internal/tmpl/partials/article-list.html +0 -22
deleted file mode 100644
@@ -1,22 +0,0 @@
1-{{define "article-list.html"}}
2-{{range .Articles}}
3-<article class="bg-spot-surface rounded-xl px-5 py-4 hover:bg-spot-hover-50 transition shadow-spot">
4- <div class="flex items-start justify-between gap-4">
5- <div class="min-w-0 flex-1">
6- <a href="/articles/{{.ID}}" class="font-bold text-spot-text hover:text-spot-green transition text-lg leading-tight">{{.Title}}</a>
7- <div class="text-sm text-spot-secondary mt-1 flex items-center gap-2">
8- {{if .FeedFaviconURL.Valid}}<img src="{{.FeedFaviconURL.String}}" class="w-4 h-4 rounded shrink-0" loading="lazy">{{end}}
9- {{if .Author.Valid}}{{if .Author.String}}<span>{{.Author.String}}</span>{{end}}{{end}}
10- {{if .Published.Valid}}<span>{{.Published.Time.Format "Jan 2, 2006 15:04"}}</span>{{end}}
11- <span class="text-spot-muted">{{if .FeedTitle}}{{.FeedTitle}}{{else}}{{.FeedURL}}{{end}}</span>
12- </div>
13- {{if .Summary.Valid}}{{if .Summary.String}}
14- <p class="text-sm text-spot-secondary mt-2 line-clamp-2">{{plainText .Summary.String}}</p>
15- {{end}}{{end}}
16- </div>
17- </div>
18-</article>
19-{{else}}
20-<div class="text-center text-spot-secondary py-12">No articles found.</div>
21-{{end}}
22-{{end}}
deleted file mode 100644
@@ -1,22 +0,0 @@
1-{{define "article-list.html"}}
2-{{range .Articles}}
3-<article class="bg-spot-surface rounded-xl px-5 py-4 hover:bg-spot-hover-50 transition shadow-spot">
4- <div class="flex items-start justify-between gap-4">
5- <div class="min-w-0 flex-1">
6- <a href="/articles/{{.ID}}" class="font-bold text-spot-text hover:text-spot-green transition text-lg leading-tight">{{.Title}}</a>
7- <div class="text-sm text-spot-secondary mt-1 flex items-center gap-2">
8- {{if .FeedFaviconURL.Valid}}<img src="{{.FeedFaviconURL.String}}" class="w-4 h-4 rounded shrink-0" loading="lazy">{{end}}
9- {{if .Author.Valid}}{{if .Author.String}}<span>{{.Author.String}}</span>{{end}}{{end}}
10- {{if .Published.Valid}}<span>{{.Published.Time.Format "Jan 2, 2006 15:04"}}</span>{{end}}
11- <span class="text-spot-muted">{{if .FeedTitle}}{{.FeedTitle}}{{else}}{{.FeedURL}}{{end}}</span>
12- </div>
13- {{if .Summary.Valid}}{{if .Summary.String}}
14- <p class="text-sm text-spot-secondary mt-2 line-clamp-2">{{plainText .Summary.String}}</p>
15- {{end}}{{end}}
16- </div>
17- </div>
18-</article>
19-{{else}}
20-<div class="text-center text-spot-secondary py-12">No articles found.</div>
21-{{end}}
22-{{end}}
deleted internal/tmpl/partials/feed-list.html +0 -16
deleted file mode 100644
@@ -1,16 +0,0 @@
1-{{define "feed-list.html"}}
2-{{if .Error}}<div class="px-4 py-2 text-sm text-spot-red bg-spot-red/10 border-b border-spot-red/30">{{.Error}}</div>{{end}}
3-{{range .Subscriptions}}
4-<div class="px-5 py-4 flex items-center justify-between hover:bg-spot-hover-50 transition rounded-xl">
5- <div class="min-w-0">
6- <div class="font-bold text-spot-text">{{.FeedURL}}</div>
7- {{if .Category.Valid}}<span class="text-xs text-spot-secondary">{{.Category.String}}</span>{{end}}
8- </div>
9- <button hx-delete="/feeds/remove" hx-vals='{"url": "{{.FeedURL}}"}' hx-target="closest .px-5" hx-swap="outerHTML swap:0.3s"
10- hx-confirm="Unsubscribe from this feed?"
11- class="text-sm text-spot-secondary hover:text-spot-red transition">Unsubscribe</button>
12-</div>
13-{{else}}
14-<div class="px-4 py-8 text-center text-spot-secondary">No feeds yet.</div>
15-{{end}}
16-{{end}}
deleted file mode 100644
@@ -1,16 +0,0 @@
1-{{define "feed-list.html"}}
2-{{if .Error}}<div class="px-4 py-2 text-sm text-spot-red bg-spot-red/10 border-b border-spot-red/30">{{.Error}}</div>{{end}}
3-{{range .Subscriptions}}
4-<div class="px-5 py-4 flex items-center justify-between hover:bg-spot-hover-50 transition rounded-xl">
5- <div class="min-w-0">
6- <div class="font-bold text-spot-text">{{.FeedURL}}</div>
7- {{if .Category.Valid}}<span class="text-xs text-spot-secondary">{{.Category.String}}</span>{{end}}
8- </div>
9- <button hx-delete="/feeds/remove" hx-vals='{"url": "{{.FeedURL}}"}' hx-target="closest .px-5" hx-swap="outerHTML swap:0.3s"
10- hx-confirm="Unsubscribe from this feed?"
11- class="text-sm text-spot-secondary hover:text-spot-red transition">Unsubscribe</button>
12-</div>
13-{{else}}
14-<div class="px-4 py-8 text-center text-spot-secondary">No feeds yet.</div>
15-{{end}}
16-{{end}}
added internal/tmpl/partials/pagination.html +19 -0
new file mode 100644
@@ -0,0 +1,19 @@
1+{{define "pagination.html"}}
2+{{if or .HasPrev .HasNext}}
3+<div class="flex items-center justify-center gap-3 py-6">
4+ {{if .HasPrev}}
5+ <a href="{{paginationURL .BaseURL .PrevPage .QueryParams}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">&laquo; Prev</a>
6+ {{else}}
7+ <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">&laquo; Prev</span>
8+ {{end}}
9+
10+ <span class="text-sm text-spot-secondary">Page {{.Page}}</span>
11+
12+ {{if .HasNext}}
13+ <a href="{{paginationURL .BaseURL .NextPage .QueryParams}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">Next &raquo;</a>
14+ {{else}}
15+ <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">Next &raquo;</span>
16+ {{end}}
17+</div>
18+{{end}}
19+{{end}}
new file mode 100644
@@ -0,0 +1,19 @@
1+{{define "pagination.html"}}
2+{{if or .HasPrev .HasNext}}
3+<div class="flex items-center justify-center gap-3 py-6">
4+ {{if .HasPrev}}
5+ <a href="{{paginationURL .BaseURL .PrevPage .QueryParams}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">&laquo; Prev</a>
6+ {{else}}
7+ <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">&laquo; Prev</span>
8+ {{end}}
9+
10+ <span class="text-sm text-spot-secondary">Page {{.Page}}</span>
11+
12+ {{if .HasNext}}
13+ <a href="{{paginationURL .BaseURL .NextPage .QueryParams}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">Next &raquo;</a>
14+ {{else}}
15+ <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">Next &raquo;</span>
16+ {{end}}
17+</div>
18+{{end}}
19+{{end}}
modified internal/tmpl/partials/profile-card.html +4 -3
@@ -1,9 +1,10 @@
11 {{define "profile-card.html"}}
22 <div class="bg-spot-surface rounded-xl p-4 flex items-center gap-3 hover:bg-spot-hover-50 transition">
3+ {{if .AvatarURL}}<img src="{{.AvatarURL}}" class="w-10 h-10 rounded-full">{{end}}
34 <div class="min-w-0 flex-1">
4- <a href="/profile/{{.did}}" class="font-bold text-spot-text hover:text-spot-green transition">@{{.handle}}</a>
5- {{if .display_name}}<div class="text-sm text-spot-secondary">{{.display_name}}</div>{{end}}
5+ <a href="/profile/{{.Handle}}" class="font-bold text-spot-text hover:text-spot-green transition">@{{.Handle}}</a>
6+ {{if .DisplayName}}<div class="text-sm text-spot-secondary">{{.DisplayName}}</div>{{end}}
67 </div>
7- <span class="text-xs text-spot-secondary">{{.common_feeds}} shared feeds</span>
8+ <span class="text-xs text-spot-secondary">{{.CommonFeeds}} shared</span>
89 </div>
910 {{end}}
@@ -1,9 +1,10 @@
1 {{define "profile-card.html"}}1 {{define "profile-card.html"}}
2 <div class="bg-spot-surface rounded-xl p-4 flex items-center gap-3 hover:bg-spot-hover-50 transition">2 <div class="bg-spot-surface rounded-xl p-4 flex items-center gap-3 hover:bg-spot-hover-50 transition">
3+ {{if .AvatarURL}}<img src="{{.AvatarURL}}" class="w-10 h-10 rounded-full">{{end}}
3 <div class="min-w-0 flex-1">4 <div class="min-w-0 flex-1">
4- <a href="/profile/{{.did}}" class="font-bold text-spot-text hover:text-spot-green transition">@{{.handle}}</a>5+ <a href="/profile/{{.Handle}}" class="font-bold text-spot-text hover:text-spot-green transition">@{{.Handle}}</a>
5- {{if .display_name}}<div class="text-sm text-spot-secondary">{{.display_name}}</div>{{end}}6+ {{if .DisplayName}}<div class="text-sm text-spot-secondary">{{.DisplayName}}</div>{{end}}
6 </div>7 </div>
7- <span class="text-xs text-spot-secondary">{{.common_feeds}} shared feeds</span>8+ <span class="text-xs text-spot-secondary">{{.CommonFeeds}} shared</span>
8 </div>9 </div>
9 {{end}}10 {{end}}
modified readme.md +1 -0
@@ -44,6 +44,7 @@ Then open `http://localhost:8080`.
4444 | `GLEAN_JETSTREAM` | `wss://jetstream.glean.at` | Jetstream WebSocket URL |
4545 | `GLEAN_SYNC_INTERVAL` | `1h` | PDS sync interval (Go duration: `30m`, `2h30m`, etc.) |
4646 | `GLEAN_CLUSTER_INTERVAL` | `10m` | Cluster recomputation interval (Go duration) |
47+| `GLEAN_COLLECTION_DIR_URL` | _(empty)_ | Collection directory URL for startup backfill |
4748 | `GLEAN_PLC_URL` | `https://didplc.glean.at` | PLC directory URL for DID resolution |
4849 | `GLEAN_OAUTH_CLIENT_ID` | _(empty)_ | OAuth client metadata URL (leave empty for localhost dev) |
4950 | `GLEAN_OAUTH_REDIRECT_URL` | _(empty)_ | OAuth redirect URL (leave empty for localhost dev) |
@@ -44,6 +44,7 @@ Then open `http://localhost:8080`.
44 | `GLEAN_JETSTREAM` | `wss://jetstream.glean.at` | Jetstream WebSocket URL |44 | `GLEAN_JETSTREAM` | `wss://jetstream.glean.at` | Jetstream WebSocket URL |
45 | `GLEAN_SYNC_INTERVAL` | `1h` | PDS sync interval (Go duration: `30m`, `2h30m`, etc.) |45 | `GLEAN_SYNC_INTERVAL` | `1h` | PDS sync interval (Go duration: `30m`, `2h30m`, etc.) |
46 | `GLEAN_CLUSTER_INTERVAL` | `10m` | Cluster recomputation interval (Go duration) |46 | `GLEAN_CLUSTER_INTERVAL` | `10m` | Cluster recomputation interval (Go duration) |
47+| `GLEAN_COLLECTION_DIR_URL` | _(empty)_ | Collection directory URL for startup backfill |
47 | `GLEAN_PLC_URL` | `https://didplc.glean.at` | PLC directory URL for DID resolution |48 | `GLEAN_PLC_URL` | `https://didplc.glean.at` | PLC directory URL for DID resolution |
48 | `GLEAN_OAUTH_CLIENT_ID` | _(empty)_ | OAuth client metadata URL (leave empty for localhost dev) |49 | `GLEAN_OAUTH_CLIENT_ID` | _(empty)_ | OAuth client metadata URL (leave empty for localhost dev) |
49 | `GLEAN_OAUTH_REDIRECT_URL` | _(empty)_ | OAuth redirect URL (leave empty for localhost dev) |50 | `GLEAN_OAUTH_REDIRECT_URL` | _(empty)_ | OAuth redirect URL (leave empty for localhost dev) |