@rmdes/indiekit-endpoint-homepage 1.0.17 → 1.0.19
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/index.js +8 -0
- package/lib/controllers/dashboard.js +203 -8
- package/locales/en.json +43 -0
- package/package.json +1 -1
- package/views/homepage-blog-sidebar.njk +587 -0
- package/views/homepage-dashboard.njk +3 -146
- package/views/homepage-identity.njk +543 -0
- package/views/partials/tab-nav.njk +40 -0
package/index.js
CHANGED
|
@@ -372,6 +372,14 @@ export default class HomepageEndpoint {
|
|
|
372
372
|
// Get current config
|
|
373
373
|
protectedRouter.get("/api/config", apiController.getConfig);
|
|
374
374
|
|
|
375
|
+
// Blog sidebar tab
|
|
376
|
+
protectedRouter.get("/blog-sidebar", dashboardController.getBlogSidebar);
|
|
377
|
+
protectedRouter.post("/save-blog-sidebar", dashboardController.saveBlogSidebar);
|
|
378
|
+
|
|
379
|
+
// Identity tab
|
|
380
|
+
protectedRouter.get("/identity", dashboardController.getIdentity);
|
|
381
|
+
protectedRouter.post("/save-identity", dashboardController.saveIdentity);
|
|
382
|
+
|
|
375
383
|
return protectedRouter;
|
|
376
384
|
}
|
|
377
385
|
|
|
@@ -5,6 +5,26 @@
|
|
|
5
5
|
|
|
6
6
|
import { getConfig, saveConfig, getDefaultConfig } from "../storage/config.js";
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Parse social links from form body.
|
|
10
|
+
* Express parses social[0][name], social[0][url] etc. into nested objects.
|
|
11
|
+
*/
|
|
12
|
+
function parseSocialLinks(body) {
|
|
13
|
+
const social = [];
|
|
14
|
+
if (!body.social) return social;
|
|
15
|
+
const entries = Array.isArray(body.social) ? body.social : Object.values(body.social);
|
|
16
|
+
for (const entry of entries) {
|
|
17
|
+
if (!entry || (!entry.name && !entry.url)) continue;
|
|
18
|
+
social.push({
|
|
19
|
+
name: entry.name || "",
|
|
20
|
+
url: entry.url || "",
|
|
21
|
+
rel: entry.rel || "me",
|
|
22
|
+
icon: entry.icon || "",
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
return social;
|
|
26
|
+
}
|
|
27
|
+
|
|
8
28
|
/**
|
|
9
29
|
* Detect which preset matches the current config (if any)
|
|
10
30
|
*/
|
|
@@ -60,6 +80,7 @@ export const dashboardController = {
|
|
|
60
80
|
|
|
61
81
|
response.render("homepage-dashboard", {
|
|
62
82
|
title: "Homepage Builder",
|
|
83
|
+
activeTab: "builder",
|
|
63
84
|
config,
|
|
64
85
|
sections,
|
|
65
86
|
widgets,
|
|
@@ -91,11 +112,10 @@ export const dashboardController = {
|
|
|
91
112
|
const { application } = request.app.locals;
|
|
92
113
|
|
|
93
114
|
try {
|
|
94
|
-
const {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
} = request.body;
|
|
115
|
+
const { layout, hero, sections, sidebar, footer } = request.body;
|
|
116
|
+
|
|
117
|
+
// Get current config to preserve fields from other tabs
|
|
118
|
+
const currentConfig = await getConfig(application);
|
|
99
119
|
|
|
100
120
|
// Parse JSON strings if needed
|
|
101
121
|
const config = {
|
|
@@ -103,10 +123,10 @@ export const dashboardController = {
|
|
|
103
123
|
hero: typeof hero === "string" ? JSON.parse(hero) : hero,
|
|
104
124
|
sections: typeof sections === "string" ? JSON.parse(sections) : sections,
|
|
105
125
|
sidebar: typeof sidebar === "string" ? JSON.parse(sidebar) : sidebar,
|
|
106
|
-
blogListingSidebar:
|
|
107
|
-
blogPostSidebar:
|
|
126
|
+
blogListingSidebar: currentConfig?.blogListingSidebar || [],
|
|
127
|
+
blogPostSidebar: currentConfig?.blogPostSidebar || [],
|
|
108
128
|
footer: typeof footer === "string" ? JSON.parse(footer) : footer,
|
|
109
|
-
identity:
|
|
129
|
+
identity: currentConfig?.identity || null,
|
|
110
130
|
};
|
|
111
131
|
|
|
112
132
|
await saveConfig(application, config);
|
|
@@ -174,4 +194,179 @@ export const dashboardController = {
|
|
|
174
194
|
});
|
|
175
195
|
}
|
|
176
196
|
},
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* GET /blog-sidebar - Blog sidebar tab
|
|
200
|
+
*/
|
|
201
|
+
async getBlogSidebar(request, response) {
|
|
202
|
+
const { application } = request.app.locals;
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
let config = await getConfig(application);
|
|
206
|
+
if (!config) {
|
|
207
|
+
config = getDefaultConfig();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const widgets = application.discoveredWidgets || [];
|
|
211
|
+
const blogPostWidgets = application.discoveredBlogPostWidgets || [];
|
|
212
|
+
|
|
213
|
+
response.render("homepage-blog-sidebar", {
|
|
214
|
+
title: "Homepage Builder",
|
|
215
|
+
activeTab: "blog-sidebar",
|
|
216
|
+
config,
|
|
217
|
+
widgets,
|
|
218
|
+
blogPostWidgets,
|
|
219
|
+
homepageEndpoint: application.homepageEndpoint,
|
|
220
|
+
});
|
|
221
|
+
} catch (error) {
|
|
222
|
+
console.error("[Homepage] Blog sidebar error:", error);
|
|
223
|
+
response.status(500).render("error", {
|
|
224
|
+
title: "Error",
|
|
225
|
+
message: "Failed to load blog sidebar configuration",
|
|
226
|
+
error: error.message,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* POST /save-blog-sidebar - Save blog sidebar configuration
|
|
233
|
+
*/
|
|
234
|
+
async saveBlogSidebar(request, response) {
|
|
235
|
+
const { application } = request.app.locals;
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
const { blogListingSidebar, blogPostSidebar } = request.body;
|
|
239
|
+
|
|
240
|
+
// Get current config to preserve fields from other tabs
|
|
241
|
+
const currentConfig = await getConfig(application);
|
|
242
|
+
|
|
243
|
+
const config = {
|
|
244
|
+
layout: currentConfig?.layout || "single-column",
|
|
245
|
+
hero: currentConfig?.hero || { enabled: true, showSocial: true },
|
|
246
|
+
sections: currentConfig?.sections || [],
|
|
247
|
+
sidebar: currentConfig?.sidebar || [],
|
|
248
|
+
blogListingSidebar: typeof blogListingSidebar === "string" ? JSON.parse(blogListingSidebar) : (blogListingSidebar || []),
|
|
249
|
+
blogPostSidebar: typeof blogPostSidebar === "string" ? JSON.parse(blogPostSidebar) : (blogPostSidebar || []),
|
|
250
|
+
footer: currentConfig?.footer || [],
|
|
251
|
+
identity: currentConfig?.identity || null,
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
await saveConfig(application, config);
|
|
255
|
+
|
|
256
|
+
if (request.headers.accept?.includes("application/json")) {
|
|
257
|
+
response.json({ success: true, message: "Blog sidebar saved" });
|
|
258
|
+
} else {
|
|
259
|
+
response.redirect(application.homepageEndpoint + "/blog-sidebar?saved=1");
|
|
260
|
+
}
|
|
261
|
+
} catch (error) {
|
|
262
|
+
console.error("[Homepage] Save blog sidebar error:", error);
|
|
263
|
+
|
|
264
|
+
if (request.headers.accept?.includes("application/json")) {
|
|
265
|
+
response.status(500).json({ success: false, error: error.message });
|
|
266
|
+
} else {
|
|
267
|
+
response.status(500).render("error", {
|
|
268
|
+
title: "Error",
|
|
269
|
+
message: "Failed to save blog sidebar configuration",
|
|
270
|
+
error: error.message,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* GET /identity - Identity editor tab
|
|
278
|
+
*/
|
|
279
|
+
async getIdentity(request, response) {
|
|
280
|
+
const { application } = request.app.locals;
|
|
281
|
+
|
|
282
|
+
try {
|
|
283
|
+
let config = await getConfig(application);
|
|
284
|
+
if (!config) {
|
|
285
|
+
config = getDefaultConfig();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const identity = config.identity || {};
|
|
289
|
+
|
|
290
|
+
response.render("homepage-identity", {
|
|
291
|
+
title: "Homepage Builder",
|
|
292
|
+
activeTab: "identity",
|
|
293
|
+
identity,
|
|
294
|
+
homepageEndpoint: application.homepageEndpoint,
|
|
295
|
+
});
|
|
296
|
+
} catch (error) {
|
|
297
|
+
console.error("[Homepage] Identity error:", error);
|
|
298
|
+
response.status(500).render("error", {
|
|
299
|
+
title: "Error",
|
|
300
|
+
message: "Failed to load identity configuration",
|
|
301
|
+
error: error.message,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* POST /save-identity - Save identity configuration
|
|
308
|
+
*/
|
|
309
|
+
async saveIdentity(request, response) {
|
|
310
|
+
const { application } = request.app.locals;
|
|
311
|
+
|
|
312
|
+
try {
|
|
313
|
+
const body = request.body;
|
|
314
|
+
|
|
315
|
+
// Build identity object from form fields
|
|
316
|
+
const identity = {
|
|
317
|
+
name: body["identity-name"] || "",
|
|
318
|
+
avatar: body["identity-avatar"] || "",
|
|
319
|
+
title: body["identity-title"] || "",
|
|
320
|
+
pronoun: body["identity-pronoun"] || "",
|
|
321
|
+
bio: body["identity-bio"] || "",
|
|
322
|
+
description: body["identity-description"] || "",
|
|
323
|
+
locality: body["identity-locality"] || "",
|
|
324
|
+
country: body["identity-country"] || "",
|
|
325
|
+
org: body["identity-org"] || "",
|
|
326
|
+
url: body["identity-url"] || "",
|
|
327
|
+
email: body["identity-email"] || "",
|
|
328
|
+
keyUrl: body["identity-keyUrl"] || "",
|
|
329
|
+
categories: body["identity-categories"]
|
|
330
|
+
? (typeof body["identity-categories"] === "string"
|
|
331
|
+
? body["identity-categories"].split(",").map(s => s.trim()).filter(Boolean)
|
|
332
|
+
: body["identity-categories"])
|
|
333
|
+
: [],
|
|
334
|
+
social: parseSocialLinks(body),
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
// Get current config to preserve fields from other tabs
|
|
338
|
+
const currentConfig = await getConfig(application);
|
|
339
|
+
|
|
340
|
+
const config = {
|
|
341
|
+
layout: currentConfig?.layout || "single-column",
|
|
342
|
+
hero: currentConfig?.hero || { enabled: true, showSocial: true },
|
|
343
|
+
sections: currentConfig?.sections || [],
|
|
344
|
+
sidebar: currentConfig?.sidebar || [],
|
|
345
|
+
blogListingSidebar: currentConfig?.blogListingSidebar || [],
|
|
346
|
+
blogPostSidebar: currentConfig?.blogPostSidebar || [],
|
|
347
|
+
footer: currentConfig?.footer || [],
|
|
348
|
+
identity,
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
await saveConfig(application, config);
|
|
352
|
+
|
|
353
|
+
if (request.headers.accept?.includes("application/json")) {
|
|
354
|
+
response.json({ success: true, message: "Identity saved" });
|
|
355
|
+
} else {
|
|
356
|
+
response.redirect(application.homepageEndpoint + "/identity?saved=1");
|
|
357
|
+
}
|
|
358
|
+
} catch (error) {
|
|
359
|
+
console.error("[Homepage] Save identity error:", error);
|
|
360
|
+
|
|
361
|
+
if (request.headers.accept?.includes("application/json")) {
|
|
362
|
+
response.status(500).json({ success: false, error: error.message });
|
|
363
|
+
} else {
|
|
364
|
+
response.status(500).render("error", {
|
|
365
|
+
title: "Error",
|
|
366
|
+
message: "Failed to save identity configuration",
|
|
367
|
+
error: error.message,
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
},
|
|
177
372
|
};
|
package/locales/en.json
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
"homepageBuilder": {
|
|
3
3
|
"title": "Homepage Builder",
|
|
4
4
|
"description": "Configure your homepage layout, sections, sidebar widgets, and footer.",
|
|
5
|
+
"tabs": {
|
|
6
|
+
"builder": "Homepage",
|
|
7
|
+
"blogSidebar": "Blog Sidebar",
|
|
8
|
+
"identity": "Identity"
|
|
9
|
+
},
|
|
5
10
|
"presets": {
|
|
6
11
|
"title": "Quick Start",
|
|
7
12
|
"description": "Choose a preset to quickly configure your homepage. You can customize it further below.",
|
|
@@ -60,6 +65,44 @@
|
|
|
60
65
|
"contentLabel": "Content (HTML or text)",
|
|
61
66
|
"save": "Apply",
|
|
62
67
|
"cancel": "Cancel"
|
|
68
|
+
},
|
|
69
|
+
"identity": {
|
|
70
|
+
"title": "Identity",
|
|
71
|
+
"description": "Configure your author profile, contact details, and social links. These override environment variable defaults.",
|
|
72
|
+
"saved": "Identity saved successfully. Refresh your site to see changes.",
|
|
73
|
+
"profile": {
|
|
74
|
+
"legend": "Profile",
|
|
75
|
+
"name": { "label": "Name", "hint": "Your display name" },
|
|
76
|
+
"avatar": { "label": "Avatar URL", "hint": "URL to your avatar image" },
|
|
77
|
+
"title": { "label": "Title", "hint": "Job title or subtitle" },
|
|
78
|
+
"pronoun": { "label": "Pronoun", "hint": "e.g. he/him, she/her, they/them" },
|
|
79
|
+
"bio": { "label": "Bio", "hint": "Short biography" },
|
|
80
|
+
"description": { "label": "Site Description", "hint": "Description shown in the hero section" }
|
|
81
|
+
},
|
|
82
|
+
"location": {
|
|
83
|
+
"legend": "Location",
|
|
84
|
+
"locality": { "label": "City", "hint": "City or locality" },
|
|
85
|
+
"country": { "label": "Country" },
|
|
86
|
+
"org": { "label": "Organization", "hint": "Company or organization" }
|
|
87
|
+
},
|
|
88
|
+
"contact": {
|
|
89
|
+
"legend": "Contact",
|
|
90
|
+
"url": { "label": "URL", "hint": "Your personal website URL" },
|
|
91
|
+
"email": { "label": "Email" },
|
|
92
|
+
"keyUrl": { "label": "PGP Key URL", "hint": "URL to your public PGP key" }
|
|
93
|
+
},
|
|
94
|
+
"categories": {
|
|
95
|
+
"legend": "Site Categories",
|
|
96
|
+
"tags": { "label": "Categories", "hint": "Comma-separated tags for your site (rendered as p-category in your h-card)" }
|
|
97
|
+
},
|
|
98
|
+
"social": {
|
|
99
|
+
"legend": "Social Links",
|
|
100
|
+
"description": "Add links to your social profiles. These appear in the hero section and h-card.",
|
|
101
|
+
"name": { "label": "Name" },
|
|
102
|
+
"url": { "label": "URL" },
|
|
103
|
+
"rel": { "label": "Rel" },
|
|
104
|
+
"icon": { "label": "Icon" }
|
|
105
|
+
}
|
|
63
106
|
}
|
|
64
107
|
}
|
|
65
108
|
}
|
package/package.json
CHANGED