@rmdes/indiekit-endpoint-blogroll 1.0.14 → 1.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/styles.css +342 -0
- package/lib/controllers/blogs.js +3 -0
- package/lib/controllers/dashboard.js +19 -0
- package/lib/controllers/sources.js +22 -0
- package/package.json +1 -1
- package/views/blogroll-blog-edit.njk +88 -297
- package/views/blogroll-blogs.njk +25 -146
- package/views/blogroll-dashboard.njk +50 -207
- package/views/blogroll-source-edit.njk +72 -144
- package/views/blogroll-sources.njk +19 -106
- package/views/layouts/blogroll.njk +6 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
/* Blogroll endpoint styles */
|
|
2
|
+
|
|
3
|
+
/* Stats grid */
|
|
4
|
+
.blogroll-stats {
|
|
5
|
+
display: grid;
|
|
6
|
+
gap: var(--space-s);
|
|
7
|
+
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.blogroll-stat {
|
|
11
|
+
background: var(--color-background);
|
|
12
|
+
border-radius: var(--radius-s);
|
|
13
|
+
padding: var(--space-s);
|
|
14
|
+
text-align: center;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.blogroll-stat dt {
|
|
18
|
+
color: var(--color-text-secondary);
|
|
19
|
+
font-size: var(--step--1);
|
|
20
|
+
margin-block-end: var(--space-2xs);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.blogroll-stat dd {
|
|
24
|
+
font-size: var(--step-0);
|
|
25
|
+
font-weight: var(--font-weight-semibold);
|
|
26
|
+
margin: 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.blogroll-stat dd.blogroll-stat--error {
|
|
30
|
+
color: var(--color-error);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* Quick links (button row) */
|
|
34
|
+
.blogroll-actions {
|
|
35
|
+
display: flex;
|
|
36
|
+
flex-wrap: wrap;
|
|
37
|
+
gap: var(--space-s);
|
|
38
|
+
margin-block-start: var(--space-m);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* List (shared between blogs, sources, errors, dashboard lists) */
|
|
42
|
+
.blogroll-list {
|
|
43
|
+
display: flex;
|
|
44
|
+
flex-direction: column;
|
|
45
|
+
gap: var(--space-s);
|
|
46
|
+
list-style: none;
|
|
47
|
+
margin: 0;
|
|
48
|
+
padding: 0;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.blogroll-list__item {
|
|
52
|
+
align-items: flex-start;
|
|
53
|
+
background: var(--color-offset);
|
|
54
|
+
border-radius: var(--radius-m);
|
|
55
|
+
display: flex;
|
|
56
|
+
flex-wrap: wrap;
|
|
57
|
+
gap: var(--space-s);
|
|
58
|
+
justify-content: space-between;
|
|
59
|
+
padding: var(--space-m);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.blogroll-list__item--compact {
|
|
63
|
+
padding: var(--space-xs) var(--space-s);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.blogroll-list__item--pinned {
|
|
67
|
+
border-inline-start: 3px solid var(--color-accent);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.blogroll-list__item--hidden {
|
|
71
|
+
opacity: 0.6;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* Item content (left side) */
|
|
75
|
+
.blogroll-item__info {
|
|
76
|
+
flex: 1;
|
|
77
|
+
min-inline-size: 200px;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.blogroll-item__title {
|
|
81
|
+
font-size: var(--step-0);
|
|
82
|
+
font-weight: var(--font-weight-semibold);
|
|
83
|
+
margin: 0 0 var(--space-2xs);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.blogroll-item__title a {
|
|
87
|
+
color: inherit;
|
|
88
|
+
text-decoration: none;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.blogroll-item__title a:hover {
|
|
92
|
+
text-decoration: underline;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.blogroll-item__meta {
|
|
96
|
+
align-items: center;
|
|
97
|
+
color: var(--color-text-secondary);
|
|
98
|
+
display: flex;
|
|
99
|
+
flex-wrap: wrap;
|
|
100
|
+
font-size: var(--step--1);
|
|
101
|
+
gap: var(--space-xs);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.blogroll-item__url {
|
|
105
|
+
color: var(--color-accent);
|
|
106
|
+
font-family: monospace;
|
|
107
|
+
font-size: var(--step--2);
|
|
108
|
+
margin-block-start: var(--space-2xs);
|
|
109
|
+
word-break: break-all;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.blogroll-item__error {
|
|
113
|
+
color: var(--color-error);
|
|
114
|
+
font-size: var(--step--1);
|
|
115
|
+
margin-block-start: var(--space-2xs);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* Item actions (right side) */
|
|
119
|
+
.blogroll-item__actions {
|
|
120
|
+
display: flex;
|
|
121
|
+
flex-wrap: wrap;
|
|
122
|
+
gap: var(--space-xs);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* Filters */
|
|
126
|
+
.blogroll-filters {
|
|
127
|
+
align-items: center;
|
|
128
|
+
display: flex;
|
|
129
|
+
flex-wrap: wrap;
|
|
130
|
+
gap: var(--space-s);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.blogroll-filter-select {
|
|
134
|
+
appearance: none;
|
|
135
|
+
background-color: var(--color-background);
|
|
136
|
+
border: 1px solid var(--color-border);
|
|
137
|
+
border-radius: var(--radius-s);
|
|
138
|
+
font-size: var(--step--1);
|
|
139
|
+
min-inline-size: 150px;
|
|
140
|
+
padding: var(--space-2xs) var(--space-s);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* Form fields */
|
|
144
|
+
.blogroll-form {
|
|
145
|
+
max-inline-size: 600px;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.blogroll-field {
|
|
149
|
+
display: flex;
|
|
150
|
+
flex-direction: column;
|
|
151
|
+
gap: var(--space-2xs);
|
|
152
|
+
margin-block-end: var(--space-m);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.blogroll-field label {
|
|
156
|
+
font-weight: var(--font-weight-semibold);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.blogroll-field-hint {
|
|
160
|
+
color: var(--color-text-secondary);
|
|
161
|
+
font-size: var(--step--1);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.blogroll-field input,
|
|
165
|
+
.blogroll-field select,
|
|
166
|
+
.blogroll-field textarea {
|
|
167
|
+
appearance: none;
|
|
168
|
+
background-color: var(--color-background);
|
|
169
|
+
border: 1px solid var(--color-border);
|
|
170
|
+
border-radius: var(--radius-s);
|
|
171
|
+
font-size: var(--step--1);
|
|
172
|
+
padding: var(--space-2xs) var(--space-s);
|
|
173
|
+
width: 100%;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.blogroll-field textarea {
|
|
177
|
+
min-block-size: 100px;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.blogroll-field input:focus,
|
|
181
|
+
.blogroll-field select:focus,
|
|
182
|
+
.blogroll-field textarea:focus {
|
|
183
|
+
border-color: var(--color-accent);
|
|
184
|
+
outline: 2px solid var(--color-accent);
|
|
185
|
+
outline-offset: 1px;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.blogroll-field--inline {
|
|
189
|
+
align-items: center;
|
|
190
|
+
flex-direction: row;
|
|
191
|
+
gap: var(--space-s);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.blogroll-field--inline input[type="checkbox"] {
|
|
195
|
+
appearance: auto;
|
|
196
|
+
cursor: pointer;
|
|
197
|
+
width: auto;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/* API list */
|
|
201
|
+
.blogroll-api-list {
|
|
202
|
+
display: flex;
|
|
203
|
+
flex-direction: column;
|
|
204
|
+
gap: var(--space-xs);
|
|
205
|
+
list-style: none;
|
|
206
|
+
margin: 0;
|
|
207
|
+
padding: 0;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.blogroll-api-list li {
|
|
211
|
+
background: var(--color-background);
|
|
212
|
+
border-radius: var(--radius-s);
|
|
213
|
+
font-size: var(--step--1);
|
|
214
|
+
padding: var(--space-xs) var(--space-s);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.blogroll-api-list code {
|
|
218
|
+
color: var(--color-accent);
|
|
219
|
+
font-weight: var(--font-weight-semibold);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/* Feed items (inside blog-edit) */
|
|
223
|
+
.blogroll-items-list {
|
|
224
|
+
display: flex;
|
|
225
|
+
flex-direction: column;
|
|
226
|
+
gap: var(--space-xs);
|
|
227
|
+
list-style: none;
|
|
228
|
+
margin: 0;
|
|
229
|
+
padding: 0;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.blogroll-items-list li {
|
|
233
|
+
background: var(--color-offset);
|
|
234
|
+
border-radius: var(--radius-s);
|
|
235
|
+
padding: var(--space-xs) var(--space-s);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.blogroll-items-list .blogroll-item__title {
|
|
239
|
+
font-size: var(--step--1);
|
|
240
|
+
margin: 0;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.blogroll-items-list .blogroll-item__meta {
|
|
244
|
+
font-size: var(--step--2);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/* Feed discovery (blog-edit new) */
|
|
248
|
+
.blogroll-discover {
|
|
249
|
+
background: var(--color-offset);
|
|
250
|
+
border-radius: var(--radius-m);
|
|
251
|
+
margin-block-end: var(--space-m);
|
|
252
|
+
padding: var(--space-m);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.blogroll-discover .blogroll-field {
|
|
256
|
+
margin-block-end: var(--space-s);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.blogroll-discover__input {
|
|
260
|
+
display: flex;
|
|
261
|
+
gap: var(--space-s);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.blogroll-discover__input input {
|
|
265
|
+
appearance: none;
|
|
266
|
+
background-color: var(--color-background);
|
|
267
|
+
border: 1px solid var(--color-border);
|
|
268
|
+
border-radius: var(--radius-s);
|
|
269
|
+
flex: 1;
|
|
270
|
+
font-size: var(--step--1);
|
|
271
|
+
padding: var(--space-2xs) var(--space-s);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.blogroll-discover__result {
|
|
275
|
+
background: var(--color-background);
|
|
276
|
+
border-radius: var(--radius-s);
|
|
277
|
+
font-size: var(--step--1);
|
|
278
|
+
margin-block-start: var(--space-s);
|
|
279
|
+
padding: var(--space-s);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.blogroll-discover__result--error {
|
|
283
|
+
color: var(--color-error);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.blogroll-discover__result--success {
|
|
287
|
+
color: var(--color-success);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.blogroll-discover__feeds {
|
|
291
|
+
display: flex;
|
|
292
|
+
flex-direction: column;
|
|
293
|
+
gap: var(--space-xs);
|
|
294
|
+
list-style: none;
|
|
295
|
+
margin: var(--space-xs) 0 0;
|
|
296
|
+
padding: 0;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.blogroll-discover__feed {
|
|
300
|
+
align-items: center;
|
|
301
|
+
background: var(--color-offset);
|
|
302
|
+
border-radius: var(--radius-s);
|
|
303
|
+
cursor: pointer;
|
|
304
|
+
display: flex;
|
|
305
|
+
gap: var(--space-s);
|
|
306
|
+
padding: var(--space-xs);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.blogroll-discover__feed:hover {
|
|
310
|
+
opacity: 0.8;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.blogroll-discover__feed-url {
|
|
314
|
+
flex: 1;
|
|
315
|
+
font-family: monospace;
|
|
316
|
+
font-size: var(--step--2);
|
|
317
|
+
word-break: break-all;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.blogroll-discover__feed-type {
|
|
321
|
+
background: var(--color-accent);
|
|
322
|
+
border-radius: var(--radius-s);
|
|
323
|
+
color: var(--color-on-accent);
|
|
324
|
+
font-size: var(--step--2);
|
|
325
|
+
padding: var(--space-3xs) var(--space-2xs);
|
|
326
|
+
text-transform: uppercase;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/* Empty state */
|
|
330
|
+
.blogroll-empty {
|
|
331
|
+
color: var(--color-text-secondary);
|
|
332
|
+
font-size: var(--step--1);
|
|
333
|
+
padding: var(--space-m);
|
|
334
|
+
text-align: center;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/* Divider */
|
|
338
|
+
.blogroll-divider {
|
|
339
|
+
border: none;
|
|
340
|
+
border-block-start: 1px solid var(--color-border);
|
|
341
|
+
margin: var(--space-m) 0;
|
|
342
|
+
}
|
package/lib/controllers/blogs.js
CHANGED
|
@@ -43,6 +43,7 @@ async function list(request, response) {
|
|
|
43
43
|
|
|
44
44
|
response.render("blogroll-blogs", {
|
|
45
45
|
title: request.__("blogroll.blogs.title"),
|
|
46
|
+
parent: { text: request.__("blogroll.title"), href: request.baseUrl },
|
|
46
47
|
blogs: filteredBlogs,
|
|
47
48
|
categories,
|
|
48
49
|
filterCategory: category,
|
|
@@ -66,6 +67,7 @@ async function list(request, response) {
|
|
|
66
67
|
function newForm(request, response) {
|
|
67
68
|
response.render("blogroll-blog-edit", {
|
|
68
69
|
title: request.__("blogroll.blogs.new"),
|
|
70
|
+
parent: { text: request.__("blogroll.blogs.title"), href: `${request.baseUrl}/blogs` },
|
|
69
71
|
blog: null,
|
|
70
72
|
isNew: true,
|
|
71
73
|
baseUrl: request.baseUrl,
|
|
@@ -175,6 +177,7 @@ async function edit(request, response) {
|
|
|
175
177
|
|
|
176
178
|
response.render("blogroll-blog-edit", {
|
|
177
179
|
title: request.__("blogroll.blogs.edit"),
|
|
180
|
+
parent: { text: request.__("blogroll.blogs.title"), href: `${request.baseUrl}/blogs` },
|
|
178
181
|
blog,
|
|
179
182
|
items,
|
|
180
183
|
isNew: false,
|
|
@@ -38,6 +38,9 @@ async function get(request, response) {
|
|
|
38
38
|
const errorBlogs = await getBlogs(application, { includeHidden: true, limit: 100 });
|
|
39
39
|
const blogsWithErrors = errorBlogs.filter((b) => b.status === "error");
|
|
40
40
|
|
|
41
|
+
// Extract flash messages for native Indiekit notification banner
|
|
42
|
+
const flash = consumeFlashMessage(request);
|
|
43
|
+
|
|
41
44
|
response.render("blogroll-dashboard", {
|
|
42
45
|
title: request.__("blogroll.title"),
|
|
43
46
|
sources,
|
|
@@ -51,6 +54,7 @@ async function get(request, response) {
|
|
|
51
54
|
syncStatus,
|
|
52
55
|
blogsWithErrors: blogsWithErrors.slice(0, 5),
|
|
53
56
|
baseUrl: request.baseUrl,
|
|
57
|
+
...flash,
|
|
54
58
|
});
|
|
55
59
|
} catch (error) {
|
|
56
60
|
console.error("[Blogroll] Dashboard error:", error);
|
|
@@ -151,6 +155,21 @@ async function status(request, response) {
|
|
|
151
155
|
}
|
|
152
156
|
}
|
|
153
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Extract and clear flash messages from session
|
|
160
|
+
* Returns { success, error } for Indiekit's native notificationBanner
|
|
161
|
+
*/
|
|
162
|
+
function consumeFlashMessage(request) {
|
|
163
|
+
const result = {};
|
|
164
|
+
if (request.session?.messages?.length) {
|
|
165
|
+
const msg = request.session.messages[0];
|
|
166
|
+
if (msg.type === "success") result.success = msg.content;
|
|
167
|
+
else if (msg.type === "error" || msg.type === "warning") result.error = msg.content;
|
|
168
|
+
request.session.messages = null;
|
|
169
|
+
}
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
|
|
154
173
|
export const dashboardController = {
|
|
155
174
|
get,
|
|
156
175
|
sync,
|
|
@@ -37,10 +37,15 @@ async function list(request, response) {
|
|
|
37
37
|
: null,
|
|
38
38
|
}));
|
|
39
39
|
|
|
40
|
+
// Extract flash messages for native Indiekit notification banner
|
|
41
|
+
const flash = consumeFlashMessage(request);
|
|
42
|
+
|
|
40
43
|
response.render("blogroll-sources", {
|
|
41
44
|
title: request.__("blogroll.sources.title"),
|
|
45
|
+
parent: { text: request.__("blogroll.title"), href: request.baseUrl },
|
|
42
46
|
sources,
|
|
43
47
|
baseUrl: request.baseUrl,
|
|
48
|
+
...flash,
|
|
44
49
|
});
|
|
45
50
|
} catch (error) {
|
|
46
51
|
console.error("[Blogroll] Sources list error:", error);
|
|
@@ -66,6 +71,7 @@ async function newForm(request, response) {
|
|
|
66
71
|
|
|
67
72
|
response.render("blogroll-source-edit", {
|
|
68
73
|
title: request.__("blogroll.sources.new"),
|
|
74
|
+
parent: { text: request.__("blogroll.sources.title"), href: `${request.baseUrl}/sources` },
|
|
69
75
|
source: null,
|
|
70
76
|
isNew: true,
|
|
71
77
|
baseUrl: request.baseUrl,
|
|
@@ -185,6 +191,7 @@ async function edit(request, response) {
|
|
|
185
191
|
|
|
186
192
|
response.render("blogroll-source-edit", {
|
|
187
193
|
title: request.__("blogroll.sources.edit"),
|
|
194
|
+
parent: { text: request.__("blogroll.sources.title"), href: `${request.baseUrl}/sources` },
|
|
188
195
|
source,
|
|
189
196
|
isNew: false,
|
|
190
197
|
baseUrl: request.baseUrl,
|
|
@@ -336,6 +343,21 @@ async function sync(request, response) {
|
|
|
336
343
|
}
|
|
337
344
|
}
|
|
338
345
|
|
|
346
|
+
/**
|
|
347
|
+
* Extract and clear flash messages from session
|
|
348
|
+
* Returns { success, error } for Indiekit's native notificationBanner
|
|
349
|
+
*/
|
|
350
|
+
function consumeFlashMessage(request) {
|
|
351
|
+
const result = {};
|
|
352
|
+
if (request.session?.messages?.length) {
|
|
353
|
+
const msg = request.session.messages[0];
|
|
354
|
+
if (msg.type === "success") result.success = msg.content;
|
|
355
|
+
else if (msg.type === "error" || msg.type === "warning") result.error = msg.content;
|
|
356
|
+
request.session.messages = null;
|
|
357
|
+
}
|
|
358
|
+
return result;
|
|
359
|
+
}
|
|
360
|
+
|
|
339
361
|
export const sourcesController = {
|
|
340
362
|
list,
|
|
341
363
|
newForm,
|