@fullstackdatasolutions/articles 0.4.3 → 0.5.1
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/README.md +208 -19
- package/dist/index.cjs +182 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +50 -1
- package/dist/index.d.ts +50 -1
- package/dist/index.js +241 -110
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +247 -188
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +42 -1
- package/dist/server.d.ts +42 -1
- package/dist/server.js +244 -188
- package/dist/server.js.map +1 -1
- package/package.json +5 -1
- package/src/ArticleContent.tsx +17 -0
- package/src/ArticleSchemas.tsx +97 -1
- package/src/ArticleSocialShare.tsx +1 -1
- package/src/ArticleTOC.tsx +29 -0
- package/src/ScrollToTop.tsx +25 -0
- package/src/__tests__/ArticleContent.test.tsx +91 -0
- package/src/__tests__/ArticleSchemas.test.tsx +212 -1
- package/src/__tests__/ArticleSocialShare.test.tsx +7 -7
- package/src/__tests__/ArticleTOC.test.tsx +75 -0
- package/src/__tests__/ScrollToTop.test.tsx +87 -0
- package/src/__tests__/seoUtils.test.ts +266 -3
- package/src/__tests__/server-articles.test.ts +98 -0
- package/src/articleTypes.ts +26 -0
- package/src/articlesConfig.ts +4 -0
- package/src/index.ts +10 -2
- package/src/markdown.ts +143 -130
- package/src/seoUtils.ts +20 -14
- package/src/server-articles.ts +81 -78
- package/src/server.ts +4 -2
package/dist/server.cjs
CHANGED
|
@@ -67,7 +67,10 @@ var __async = (__this, __arguments, generator) => {
|
|
|
67
67
|
// src/server.ts
|
|
68
68
|
var server_exports = {};
|
|
69
69
|
__export(server_exports, {
|
|
70
|
+
ArticleContent: () => ArticleContent,
|
|
71
|
+
ArticleTOC: () => ArticleTOC,
|
|
70
72
|
categoryToSlug: () => categoryToSlug,
|
|
73
|
+
extractToc: () => extractToc,
|
|
71
74
|
generateArticleMetadata: () => generateArticleMetadata,
|
|
72
75
|
generateArticleStaticParams: () => generateArticleStaticParams,
|
|
73
76
|
generateArticlesIndexMetadata: () => generateArticlesIndexMetadata,
|
|
@@ -94,11 +97,14 @@ var import_node_path = __toESM(require("path"), 1);
|
|
|
94
97
|
var import_reading_time = __toESM(require("reading-time"), 1);
|
|
95
98
|
|
|
96
99
|
// src/markdown.ts
|
|
100
|
+
var import_rehype_autolink_headings = __toESM(require("rehype-autolink-headings"), 1);
|
|
97
101
|
var import_rehype_prism_plus = __toESM(require("rehype-prism-plus"), 1);
|
|
98
102
|
var import_rehype_sanitize = __toESM(require("rehype-sanitize"), 1);
|
|
103
|
+
var import_rehype_slug = __toESM(require("rehype-slug"), 1);
|
|
99
104
|
var import_rehype_stringify = __toESM(require("rehype-stringify"), 1);
|
|
100
105
|
var import_remark = require("remark");
|
|
101
106
|
var import_remark_gfm = __toESM(require("remark-gfm"), 1);
|
|
107
|
+
var import_remark_github_blockquote_alert = __toESM(require("remark-github-blockquote-alert"), 1);
|
|
102
108
|
var import_remark_parse = __toESM(require("remark-parse"), 1);
|
|
103
109
|
var import_remark_rehype = __toESM(require("remark-rehype"), 1);
|
|
104
110
|
var import_unist_util_visit = require("unist-util-visit");
|
|
@@ -129,6 +135,88 @@ function sanitizeImagePath(rawPath, articleSlug) {
|
|
|
129
135
|
return `/articles/${articleSlug}/${cleanPath}`;
|
|
130
136
|
}
|
|
131
137
|
}
|
|
138
|
+
function styleFootnoteLinks(nodes) {
|
|
139
|
+
nodes.forEach((n) => {
|
|
140
|
+
var _a, _b;
|
|
141
|
+
if (n.type !== "element") return;
|
|
142
|
+
const el = n;
|
|
143
|
+
if (el.tagName === "a") {
|
|
144
|
+
const isBackRef = ((_a = el.properties) == null ? void 0 : _a["dataFootnoteBackref"]) !== void 0 || ((_b = el.children[0]) == null ? void 0 : _b.type) === "text" && el.children[0].value === "\u21A9";
|
|
145
|
+
if (isBackRef) {
|
|
146
|
+
el.properties.className = "text-primary hover:underline ml-1";
|
|
147
|
+
if (typeof el.properties.href === "string") {
|
|
148
|
+
el.properties.href = el.properties.href.replaceAll("#user-content-fnref-", "#ref-");
|
|
149
|
+
}
|
|
150
|
+
} else {
|
|
151
|
+
el.properties.className = "text-primary hover:underline break-all";
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (el.children) styleFootnoteLinks(el.children);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
function processFootnoteRef(node) {
|
|
158
|
+
var _a, _b, _c, _d, _e, _f;
|
|
159
|
+
if (node.tagName !== "sup" || ((_b = (_a = node.children) == null ? void 0 : _a[0]) == null ? void 0 : _b.type) !== "element" || node.children[0].tagName !== "a")
|
|
160
|
+
return;
|
|
161
|
+
const link = node.children[0];
|
|
162
|
+
const href = (_c = link.properties) == null ? void 0 : _c.href;
|
|
163
|
+
if (typeof href !== "string" || !href.startsWith("#user-content-fn-")) return;
|
|
164
|
+
delete link.properties.target;
|
|
165
|
+
delete link.properties.rel;
|
|
166
|
+
link.properties.className = "text-primary hover:underline";
|
|
167
|
+
link.properties.href = href.replaceAll("#user-content-fn-", "#footnote-");
|
|
168
|
+
if (typeof ((_d = node.properties) == null ? void 0 : _d.id) === "string") {
|
|
169
|
+
link.properties.id = node.properties.id.replaceAll("user-content-fnref-", "ref-");
|
|
170
|
+
delete node.properties.id;
|
|
171
|
+
}
|
|
172
|
+
if (((_f = (_e = link.children) == null ? void 0 : _e[0]) == null ? void 0 : _f.type) === "text") {
|
|
173
|
+
link.children[0].value = `[${link.children[0].value}]`;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function processFootnotesSection(node) {
|
|
177
|
+
var _a;
|
|
178
|
+
const cls = (_a = node.properties) == null ? void 0 : _a.className;
|
|
179
|
+
const isFootnotes = node.tagName === "section" && (Array.isArray(cls) ? cls.includes("footnotes") : cls === "footnotes");
|
|
180
|
+
if (!isFootnotes) return;
|
|
181
|
+
const olCandidate = node.children.find(
|
|
182
|
+
(child) => child.type === "element" && child.tagName === "ol"
|
|
183
|
+
);
|
|
184
|
+
if ((olCandidate == null ? void 0 : olCandidate.type) !== "element") return;
|
|
185
|
+
const ol = olCandidate;
|
|
186
|
+
ol.properties.className = "list-decimal ml-6 space-y-2 text-sm text-muted-foreground";
|
|
187
|
+
ol.children.forEach((li) => {
|
|
188
|
+
if (li.type !== "element" || li.tagName !== "li") return;
|
|
189
|
+
const liEl = li;
|
|
190
|
+
if (liEl.properties) {
|
|
191
|
+
liEl.properties.className = "pl-2";
|
|
192
|
+
if (typeof liEl.properties.id === "string") {
|
|
193
|
+
liEl.properties.id = liEl.properties.id.replaceAll("user-content-fn-", "footnote-");
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const pIndex = liEl.children.findIndex(
|
|
197
|
+
(child) => child.type === "element" && child.tagName === "p"
|
|
198
|
+
);
|
|
199
|
+
if (pIndex !== -1) {
|
|
200
|
+
const p = liEl.children[pIndex];
|
|
201
|
+
liEl.children.splice(pIndex, 1, ...p.children);
|
|
202
|
+
}
|
|
203
|
+
styleFootnoteLinks(liEl.children);
|
|
204
|
+
});
|
|
205
|
+
const hr = {
|
|
206
|
+
type: "element",
|
|
207
|
+
tagName: "hr",
|
|
208
|
+
properties: { className: "my-8 border-border" },
|
|
209
|
+
children: []
|
|
210
|
+
};
|
|
211
|
+
const h3 = {
|
|
212
|
+
type: "element",
|
|
213
|
+
tagName: "h3",
|
|
214
|
+
properties: { className: "text-lg font-semibold mb-4" },
|
|
215
|
+
children: [{ type: "text", value: "References" }]
|
|
216
|
+
};
|
|
217
|
+
node.children = [hr, h3, ol];
|
|
218
|
+
if (node.properties) node.properties.className = void 0;
|
|
219
|
+
}
|
|
132
220
|
var customRenderer = () => {
|
|
133
221
|
return (tree) => {
|
|
134
222
|
(0, import_unist_util_visit.visit)(tree, "element", (node) => {
|
|
@@ -195,95 +283,8 @@ var customRenderer = () => {
|
|
|
195
283
|
}
|
|
196
284
|
});
|
|
197
285
|
(0, import_unist_util_visit.visit)(tree, "element", (node) => {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const link = node.children[0];
|
|
201
|
-
if (((_c = link.properties) == null ? void 0 : _c.href) && typeof link.properties.href === "string" && link.properties.href.startsWith("#user-content-fn-")) {
|
|
202
|
-
if (link.properties) {
|
|
203
|
-
delete link.properties.target;
|
|
204
|
-
delete link.properties.rel;
|
|
205
|
-
link.properties.className = "text-primary hover:underline";
|
|
206
|
-
}
|
|
207
|
-
link.properties.href = link.properties.href.replaceAll("#user-content-fn-", "#footnote-");
|
|
208
|
-
if (((_d = node.properties) == null ? void 0 : _d.id) && typeof node.properties.id === "string") {
|
|
209
|
-
const newId = node.properties.id.replaceAll("user-content-fnref-", "ref-");
|
|
210
|
-
link.properties.id = newId;
|
|
211
|
-
delete node.properties.id;
|
|
212
|
-
}
|
|
213
|
-
if (((_f = (_e = link.children) == null ? void 0 : _e[0]) == null ? void 0 : _f.type) === "text") {
|
|
214
|
-
link.children[0].value = `[${link.children[0].value}]`;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
if (node.tagName === "section" && (Array.isArray((_g = node.properties) == null ? void 0 : _g.className) ? node.properties.className.includes("footnotes") : ((_h = node.properties) == null ? void 0 : _h.className) === "footnotes")) {
|
|
219
|
-
const olCandidate = node.children.find(
|
|
220
|
-
(child) => child.type === "element" && child.tagName === "ol"
|
|
221
|
-
);
|
|
222
|
-
if ((olCandidate == null ? void 0 : olCandidate.type) === "element") {
|
|
223
|
-
const ol = olCandidate;
|
|
224
|
-
ol.properties.className = "list-decimal ml-6 space-y-2 text-sm text-muted-foreground";
|
|
225
|
-
ol.children.forEach((li) => {
|
|
226
|
-
if (li.type === "element" && li.tagName === "li") {
|
|
227
|
-
const liEl = li;
|
|
228
|
-
if (liEl.properties) {
|
|
229
|
-
liEl.properties.className = "pl-2";
|
|
230
|
-
if (typeof liEl.properties.id === "string") {
|
|
231
|
-
liEl.properties.id = liEl.properties.id.replaceAll(
|
|
232
|
-
"user-content-fn-",
|
|
233
|
-
"footnote-"
|
|
234
|
-
);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
const pIndex = liEl.children.findIndex(
|
|
238
|
-
(child) => child.type === "element" && child.tagName === "p"
|
|
239
|
-
);
|
|
240
|
-
if (pIndex !== -1) {
|
|
241
|
-
const p = liEl.children[pIndex];
|
|
242
|
-
liEl.children.splice(pIndex, 1, ...p.children);
|
|
243
|
-
}
|
|
244
|
-
const styleLinks = (nodes) => {
|
|
245
|
-
nodes.forEach((n) => {
|
|
246
|
-
var _a2, _b2;
|
|
247
|
-
if (n.type !== "element") return;
|
|
248
|
-
const el = n;
|
|
249
|
-
if (el.tagName === "a") {
|
|
250
|
-
const isBackRef = ((_a2 = el.properties) == null ? void 0 : _a2["dataFootnoteBackref"]) !== void 0 || ((_b2 = el.children[0]) == null ? void 0 : _b2.type) === "text" && el.children[0].value === "\u21A9";
|
|
251
|
-
if (isBackRef) {
|
|
252
|
-
el.properties.className = "text-primary hover:underline ml-1";
|
|
253
|
-
if (typeof el.properties.href === "string") {
|
|
254
|
-
el.properties.href = el.properties.href.replaceAll(
|
|
255
|
-
"#user-content-fnref-",
|
|
256
|
-
"#ref-"
|
|
257
|
-
);
|
|
258
|
-
}
|
|
259
|
-
} else {
|
|
260
|
-
el.properties.className = "text-primary hover:underline break-all";
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
if (el.children) styleLinks(el.children);
|
|
264
|
-
});
|
|
265
|
-
};
|
|
266
|
-
styleLinks(liEl.children);
|
|
267
|
-
}
|
|
268
|
-
});
|
|
269
|
-
const hr = {
|
|
270
|
-
type: "element",
|
|
271
|
-
tagName: "hr",
|
|
272
|
-
properties: { className: "my-8 border-border" },
|
|
273
|
-
children: []
|
|
274
|
-
};
|
|
275
|
-
const h3 = {
|
|
276
|
-
type: "element",
|
|
277
|
-
tagName: "h3",
|
|
278
|
-
properties: { className: "text-lg font-semibold mb-4" },
|
|
279
|
-
children: [{ type: "text", value: "References" }]
|
|
280
|
-
};
|
|
281
|
-
node.children = [hr, h3, ol];
|
|
282
|
-
if (node.properties) {
|
|
283
|
-
node.properties.className = void 0;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
286
|
+
processFootnoteRef(node);
|
|
287
|
+
processFootnotesSection(node);
|
|
287
288
|
});
|
|
288
289
|
};
|
|
289
290
|
};
|
|
@@ -308,7 +309,7 @@ var rehypeProcessImages = (options = {}) => {
|
|
|
308
309
|
function markdownToHtml(markdown, articleSlug) {
|
|
309
310
|
return __async(this, null, function* () {
|
|
310
311
|
try {
|
|
311
|
-
let processor = (0, import_remark.remark)().use(import_remark_parse.default).use(import_remark_gfm.default).use(import_remark_rehype.default).use(customRenderer).use(import_rehype_prism_plus.default).use(import_rehype_sanitize.default, {
|
|
312
|
+
let processor = (0, import_remark.remark)().use(import_remark_parse.default).use(import_remark_gfm.default).use(import_remark_github_blockquote_alert.default).use(import_remark_rehype.default).use(customRenderer).use(import_rehype_slug.default).use(import_rehype_autolink_headings.default, { behavior: "wrap" }).use(import_rehype_prism_plus.default).use(import_rehype_sanitize.default, {
|
|
312
313
|
attributes: {
|
|
313
314
|
"*": ["className", "class", "id"],
|
|
314
315
|
a: ["href", "target", "rel", "id"],
|
|
@@ -326,6 +327,31 @@ function markdownToHtml(markdown, articleSlug) {
|
|
|
326
327
|
}
|
|
327
328
|
});
|
|
328
329
|
}
|
|
330
|
+
function nodeTextValue(c) {
|
|
331
|
+
return c.type === "text" ? c.value : "";
|
|
332
|
+
}
|
|
333
|
+
function extractHeadingItem(node) {
|
|
334
|
+
var _a;
|
|
335
|
+
const match = /^h([1-6])$/.exec(node.tagName);
|
|
336
|
+
if (!match) return null;
|
|
337
|
+
const id = typeof ((_a = node.properties) == null ? void 0 : _a.id) === "string" ? node.properties.id : "";
|
|
338
|
+
const text = node.children.map(nodeTextValue).join("");
|
|
339
|
+
if (!id || !text) return null;
|
|
340
|
+
return { id, depth: Number.parseInt(match[1], 10), text };
|
|
341
|
+
}
|
|
342
|
+
function extractToc(markdown) {
|
|
343
|
+
return __async(this, null, function* () {
|
|
344
|
+
const headings = [];
|
|
345
|
+
const collectHeadings = () => (tree) => {
|
|
346
|
+
(0, import_unist_util_visit.visit)(tree, "element", (node) => {
|
|
347
|
+
const item = extractHeadingItem(node);
|
|
348
|
+
if (item) headings.push(item);
|
|
349
|
+
});
|
|
350
|
+
};
|
|
351
|
+
yield (0, import_remark.remark)().use(import_remark_parse.default).use(import_remark_gfm.default).use(import_remark_rehype.default).use(import_rehype_slug.default).use(collectHeadings).use(import_rehype_stringify.default).process(markdown);
|
|
352
|
+
return headings;
|
|
353
|
+
});
|
|
354
|
+
}
|
|
329
355
|
|
|
330
356
|
// src/server-articles.ts
|
|
331
357
|
var articlesDirectory = import_node_path.default.join(process.cwd(), "public/articles");
|
|
@@ -372,23 +398,20 @@ function sanitizeImagePath2(rawPath, articleSlug) {
|
|
|
372
398
|
}
|
|
373
399
|
return `/articles/${articleSlug}/${cleanPath}`;
|
|
374
400
|
}
|
|
401
|
+
function findArticleFile(slug) {
|
|
402
|
+
const mdPath = import_node_path.default.join(articlesDirectory, slug, "article.md");
|
|
403
|
+
const mdxPath = import_node_path.default.join(articlesDirectory, slug, "article.mdx");
|
|
404
|
+
if (import_node_fs.default.existsSync(mdPath)) return { filePath: mdPath, contentType: "md" };
|
|
405
|
+
if (import_node_fs.default.existsSync(mdxPath)) return { filePath: mdxPath, contentType: "mdx" };
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
375
408
|
function getAvailableArticleSlugs() {
|
|
376
409
|
try {
|
|
377
410
|
if (!import_node_fs.default.existsSync(articlesDirectory)) return [];
|
|
378
411
|
const items = import_node_fs.default.readdirSync(articlesDirectory, { withFileTypes: true });
|
|
379
412
|
return items.filter((item) => item.isDirectory()).filter((dir) => {
|
|
380
|
-
const articleDir = import_node_path.default.join(articlesDirectory, dir.name);
|
|
381
413
|
try {
|
|
382
|
-
|
|
383
|
-
withFileTypes: true
|
|
384
|
-
});
|
|
385
|
-
return entries.some((entry) => {
|
|
386
|
-
const name = typeof entry === "string" ? entry : entry == null ? void 0 : entry.name;
|
|
387
|
-
if (name !== "article.md") return false;
|
|
388
|
-
if (typeof entry === "string") return true;
|
|
389
|
-
if (typeof (entry == null ? void 0 : entry.isFile) === "function") return entry.isFile();
|
|
390
|
-
return true;
|
|
391
|
-
});
|
|
414
|
+
return findArticleFile(dir.name) !== null;
|
|
392
415
|
} catch (e) {
|
|
393
416
|
return false;
|
|
394
417
|
}
|
|
@@ -398,45 +421,64 @@ function getAvailableArticleSlugs() {
|
|
|
398
421
|
return [];
|
|
399
422
|
}
|
|
400
423
|
}
|
|
424
|
+
function parseDateField(rawDate) {
|
|
425
|
+
if (!rawDate) return void 0;
|
|
426
|
+
try {
|
|
427
|
+
const parsed = new Date(rawDate);
|
|
428
|
+
if (!Number.isNaN(parsed.getTime())) return parsed.toISOString().split("T")[0];
|
|
429
|
+
} catch (e) {
|
|
430
|
+
}
|
|
431
|
+
return void 0;
|
|
432
|
+
}
|
|
433
|
+
function resolveFeaturedImage(rawImage, slug) {
|
|
434
|
+
const img = typeof rawImage === "string" ? rawImage : "";
|
|
435
|
+
if (img && !img.startsWith("http")) return sanitizeImagePath2(img, slug) || "/placeholder-logo.png";
|
|
436
|
+
if (!img) return findArticleImage(slug) || "/placeholder-logo.png";
|
|
437
|
+
return img;
|
|
438
|
+
}
|
|
439
|
+
function parseFaqItems(raw) {
|
|
440
|
+
if (!Array.isArray(raw)) return void 0;
|
|
441
|
+
const items = raw.filter(
|
|
442
|
+
(item) => typeof item === "object" && item !== null && typeof item.question === "string" && typeof item.answer === "string"
|
|
443
|
+
);
|
|
444
|
+
return items.length ? items : void 0;
|
|
445
|
+
}
|
|
446
|
+
function parseHowToSteps(raw) {
|
|
447
|
+
if (!Array.isArray(raw)) return void 0;
|
|
448
|
+
const steps = raw.filter(
|
|
449
|
+
(item) => typeof item === "object" && item !== null && typeof item.name === "string" && typeof item.text === "string"
|
|
450
|
+
);
|
|
451
|
+
return steps.length ? steps : void 0;
|
|
452
|
+
}
|
|
401
453
|
function getArticleSummary(slug) {
|
|
402
454
|
return __async(this, null, function* () {
|
|
403
455
|
try {
|
|
404
|
-
const
|
|
405
|
-
if (!
|
|
406
|
-
const fileContent = import_node_fs.default.readFileSync(
|
|
456
|
+
const found = findArticleFile(slug);
|
|
457
|
+
if (!found) return null;
|
|
458
|
+
const fileContent = import_node_fs.default.readFileSync(found.filePath, "utf8");
|
|
407
459
|
const { data, content: markdownContent } = (0, import_gray_matter.default)(fileContent);
|
|
408
460
|
const readTime = getReadingTime(markdownContent);
|
|
409
|
-
let featuredImage = data.featuredImage || "";
|
|
410
|
-
if (featuredImage && !featuredImage.startsWith("http")) {
|
|
411
|
-
const sanitizedPath = sanitizeImagePath2(featuredImage, slug);
|
|
412
|
-
featuredImage = sanitizedPath || "/placeholder-logo.png";
|
|
413
|
-
} else if (!featuredImage) {
|
|
414
|
-
featuredImage = findArticleImage(slug) || "/placeholder-logo.png";
|
|
415
|
-
}
|
|
416
|
-
let articleDate;
|
|
417
|
-
if (data.date) {
|
|
418
|
-
try {
|
|
419
|
-
const parsedDate = new Date(data.date);
|
|
420
|
-
if (!Number.isNaN(parsedDate.getTime())) {
|
|
421
|
-
articleDate = parsedDate.toISOString().split("T")[0];
|
|
422
|
-
}
|
|
423
|
-
} catch (e) {
|
|
424
|
-
console.warn(`Invalid date for article ${slug}: ${data.date}`);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
461
|
const allTags = Array.isArray(data.tags) ? data.tags.filter((t) => typeof t === "string" && String(t).trim()) : [];
|
|
428
462
|
const categories = allTags.length > 0 ? allTags.map((t) => t.replaceAll("-", " ").trim()) : ["Campaigns"];
|
|
429
463
|
return {
|
|
430
464
|
slug,
|
|
431
465
|
title: data.title || slug.replaceAll("-", " "),
|
|
432
466
|
excerpt: data.excerpt || "",
|
|
433
|
-
date:
|
|
467
|
+
date: parseDateField(data.date),
|
|
468
|
+
lastmod: parseDateField(data.lastmod),
|
|
434
469
|
author: data.author || "Andrew Blase",
|
|
435
470
|
category: categories[0],
|
|
436
471
|
categories,
|
|
437
472
|
readTime,
|
|
438
|
-
featuredImage,
|
|
439
|
-
tags: data.tags || []
|
|
473
|
+
featuredImage: resolveFeaturedImage(data.featuredImage, slug),
|
|
474
|
+
tags: data.tags || [],
|
|
475
|
+
contentType: found.contentType,
|
|
476
|
+
draft: data.draft === true,
|
|
477
|
+
faq: parseFaqItems(data.faq),
|
|
478
|
+
howTo: parseHowToSteps(data.howTo),
|
|
479
|
+
canonicalUrl: typeof data.canonicalUrl === "string" ? data.canonicalUrl : void 0,
|
|
480
|
+
articleType: typeof data.articleType === "string" ? data.articleType : void 0,
|
|
481
|
+
series: typeof data.series === "string" ? data.series : void 0
|
|
440
482
|
};
|
|
441
483
|
} catch (error) {
|
|
442
484
|
console.error(`Error loading article ${slug}:`, error);
|
|
@@ -446,46 +488,21 @@ function getArticleSummary(slug) {
|
|
|
446
488
|
}
|
|
447
489
|
var getArticleMetadata = (0, import_react.cache)((slug) => __async(void 0, null, function* () {
|
|
448
490
|
try {
|
|
449
|
-
const
|
|
450
|
-
if (!
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
const
|
|
454
|
-
const
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
491
|
+
const summary = yield getArticleSummary(slug);
|
|
492
|
+
if (!summary) return null;
|
|
493
|
+
const found = findArticleFile(slug);
|
|
494
|
+
if (!found) return null;
|
|
495
|
+
const fileContent = import_node_fs.default.readFileSync(found.filePath, "utf8");
|
|
496
|
+
const { content: markdownContent } = (0, import_gray_matter.default)(fileContent);
|
|
497
|
+
const toc = yield extractToc(markdownContent);
|
|
498
|
+
let htmlContent;
|
|
499
|
+
let mdxSource;
|
|
500
|
+
if (found.contentType === "mdx") {
|
|
501
|
+
mdxSource = markdownContent;
|
|
502
|
+
} else {
|
|
503
|
+
htmlContent = yield markdownToHtml(markdownContent, slug);
|
|
461
504
|
}
|
|
462
|
-
|
|
463
|
-
if (data.date) {
|
|
464
|
-
try {
|
|
465
|
-
const parsedDate = new Date(data.date);
|
|
466
|
-
if (!Number.isNaN(parsedDate.getTime())) {
|
|
467
|
-
articleDate = parsedDate.toISOString().split("T")[0];
|
|
468
|
-
}
|
|
469
|
-
} catch (e) {
|
|
470
|
-
console.warn(`Invalid date for article ${slug}: ${data.date}`);
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
const allTags = Array.isArray(data.tags) ? data.tags.filter((t) => typeof t === "string" && String(t).trim()) : [];
|
|
474
|
-
const categories = allTags.length > 0 ? allTags.map((t) => t.replaceAll("-", " ").trim()) : ["Campaigns"];
|
|
475
|
-
return {
|
|
476
|
-
slug,
|
|
477
|
-
title: data.title || slug.replaceAll("-", " "),
|
|
478
|
-
excerpt: data.excerpt || "",
|
|
479
|
-
date: articleDate,
|
|
480
|
-
author: data.author || "Andrew Blase",
|
|
481
|
-
category: categories[0],
|
|
482
|
-
categories,
|
|
483
|
-
readTime,
|
|
484
|
-
featuredImage,
|
|
485
|
-
tags: data.tags || [],
|
|
486
|
-
content: markdownContent,
|
|
487
|
-
htmlContent
|
|
488
|
-
};
|
|
505
|
+
return __spreadProps(__spreadValues({}, summary), { content: markdownContent, htmlContent, mdxSource, toc });
|
|
489
506
|
} catch (error) {
|
|
490
507
|
console.error(`Error loading article ${slug}:`, error);
|
|
491
508
|
return null;
|
|
@@ -495,7 +512,7 @@ var getAllArticles = (0, import_react.cache)(() => __async(void 0, null, functio
|
|
|
495
512
|
const slugs = getAvailableArticleSlugs();
|
|
496
513
|
const articles = yield Promise.all(slugs.map((slug) => getArticleSummary(slug)));
|
|
497
514
|
const currentDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
498
|
-
return articles.filter((article) => article !== null).filter((article) => !article.date || article.date <= currentDate).sort((a, b) => {
|
|
515
|
+
return articles.filter((article) => article !== null).filter((article) => !article.date || article.date <= currentDate).filter((article) => !(article.draft && process.env.NODE_ENV === "production")).sort((a, b) => {
|
|
499
516
|
if (!a.date && !b.date) return 0;
|
|
500
517
|
if (!a.date) return 1;
|
|
501
518
|
if (!b.date) return -1;
|
|
@@ -579,7 +596,7 @@ function resolveImageUrl(featuredImage, siteUrl) {
|
|
|
579
596
|
}
|
|
580
597
|
function generateArticleMetadata(slug, config) {
|
|
581
598
|
return __async(this, null, function* () {
|
|
582
|
-
var _a, _b, _c, _d, _e, _f;
|
|
599
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
583
600
|
const article = yield getArticleMetadata(slug);
|
|
584
601
|
if (!article) {
|
|
585
602
|
return {
|
|
@@ -589,19 +606,14 @@ function generateArticleMetadata(slug, config) {
|
|
|
589
606
|
}
|
|
590
607
|
const siteUrl = config.siteUrl.replace(/\/$/, "");
|
|
591
608
|
const articleUrl = `${siteUrl}/articles/${slug}`;
|
|
609
|
+
const canonicalUrl = (_a = article.canonicalUrl) != null ? _a : articleUrl;
|
|
592
610
|
const imageUrl = article.featuredImage ? resolveImageUrl(article.featuredImage, siteUrl) : `${siteUrl}/placeholder-logo.png`;
|
|
593
|
-
const description = (
|
|
611
|
+
const description = (_b = article.excerpt) != null ? _b : `Read ${article.title} on ${config.siteName}.`;
|
|
594
612
|
return {
|
|
595
613
|
title: `${article.title} | ${config.siteName}`,
|
|
596
614
|
description,
|
|
597
|
-
keywords: [
|
|
598
|
-
|
|
599
|
-
"political campaign software",
|
|
600
|
-
"campaign operations",
|
|
601
|
-
"civic tech",
|
|
602
|
-
...((_b = article.tags) != null ? _b : []).map((tag) => tag.toLowerCase())
|
|
603
|
-
].join(", "),
|
|
604
|
-
openGraph: __spreadProps(__spreadValues({
|
|
615
|
+
keywords: [...((_c = article.tags) != null ? _c : []).map((tag) => tag.toLowerCase())].join(", "),
|
|
616
|
+
openGraph: __spreadProps(__spreadValues(__spreadValues({
|
|
605
617
|
title: article.title,
|
|
606
618
|
description,
|
|
607
619
|
url: articleUrl,
|
|
@@ -609,9 +621,9 @@ function generateArticleMetadata(slug, config) {
|
|
|
609
621
|
images: [{ url: imageUrl, width: 1200, height: 630, alt: article.title }],
|
|
610
622
|
locale: "en_US",
|
|
611
623
|
type: "article"
|
|
612
|
-
}, article.date && { publishedTime: article.date }), {
|
|
624
|
+
}, article.date && { publishedTime: article.date }), article.lastmod && { modifiedTime: new Date(article.lastmod).toISOString() }), {
|
|
613
625
|
authors: [article.author],
|
|
614
|
-
tags: (
|
|
626
|
+
tags: (_d = article.tags) != null ? _d : []
|
|
615
627
|
}),
|
|
616
628
|
twitter: {
|
|
617
629
|
card: "summary_large_image",
|
|
@@ -620,7 +632,7 @@ function generateArticleMetadata(slug, config) {
|
|
|
620
632
|
images: [imageUrl]
|
|
621
633
|
},
|
|
622
634
|
alternates: {
|
|
623
|
-
canonical:
|
|
635
|
+
canonical: canonicalUrl
|
|
624
636
|
},
|
|
625
637
|
robots: {
|
|
626
638
|
index: true,
|
|
@@ -633,14 +645,16 @@ function generateArticleMetadata(slug, config) {
|
|
|
633
645
|
"max-snippet": -1
|
|
634
646
|
}
|
|
635
647
|
},
|
|
636
|
-
other: __spreadProps(__spreadValues({
|
|
648
|
+
other: __spreadProps(__spreadValues(__spreadValues({
|
|
637
649
|
"article:author": article.author
|
|
638
650
|
}, article.date && {
|
|
639
651
|
"article:published_time": new Date(article.date).toISOString()
|
|
652
|
+
}), article.lastmod && {
|
|
653
|
+
"article:modified_time": new Date(article.lastmod).toISOString()
|
|
640
654
|
}), {
|
|
641
655
|
"article:section": article.category,
|
|
642
|
-
"article:tag": (
|
|
643
|
-
"linkedin:owner": (
|
|
656
|
+
"article:tag": (_f = (_e = article.tags) == null ? void 0 : _e.join(",")) != null ? _f : "",
|
|
657
|
+
"linkedin:owner": (_g = process.env.NEXT_PUBLIC_LINKEDIN_COMPANY_ID) != null ? _g : ""
|
|
644
658
|
})
|
|
645
659
|
};
|
|
646
660
|
});
|
|
@@ -668,7 +682,10 @@ function generateArticlesIndexMetadata(config) {
|
|
|
668
682
|
description
|
|
669
683
|
},
|
|
670
684
|
alternates: {
|
|
671
|
-
canonical: indexUrl
|
|
685
|
+
canonical: indexUrl,
|
|
686
|
+
types: {
|
|
687
|
+
"application/rss+xml": `${siteUrl}/articles/feed.xml`
|
|
688
|
+
}
|
|
672
689
|
},
|
|
673
690
|
robots: {
|
|
674
691
|
index: true,
|
|
@@ -734,12 +751,17 @@ function getArticleSitemapEntries(baseUrlOrConfig) {
|
|
|
734
751
|
const baseUrl = (typeof baseUrlOrConfig === "string" ? baseUrlOrConfig : baseUrlOrConfig.siteUrl).replace(/\/$/, "");
|
|
735
752
|
try {
|
|
736
753
|
const [articles, categories] = yield Promise.all([getAllArticles(), getAllCategories()]);
|
|
737
|
-
const articleEntries = articles.map((article) =>
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
754
|
+
const articleEntries = articles.map((article) => {
|
|
755
|
+
var _a;
|
|
756
|
+
const dateStr = (_a = article.lastmod) != null ? _a : article.date;
|
|
757
|
+
const lastModified = dateStr ? new Date(dateStr) : void 0;
|
|
758
|
+
return {
|
|
759
|
+
url: `${baseUrl}/articles/${article.slug}`,
|
|
760
|
+
lastModified,
|
|
761
|
+
changeFrequency: "weekly",
|
|
762
|
+
priority: 0.8
|
|
763
|
+
};
|
|
764
|
+
});
|
|
743
765
|
const categoryEntries = categories.map((cat) => ({
|
|
744
766
|
url: `${baseUrl}/articles/category/${cat.slug}`,
|
|
745
767
|
lastModified: /* @__PURE__ */ new Date(),
|
|
@@ -752,9 +774,46 @@ function getArticleSitemapEntries(baseUrlOrConfig) {
|
|
|
752
774
|
}
|
|
753
775
|
});
|
|
754
776
|
}
|
|
777
|
+
|
|
778
|
+
// src/ArticleContent.tsx
|
|
779
|
+
var import_rsc = require("next-mdx-remote/rsc");
|
|
780
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
781
|
+
function ArticleContent({ article, className }) {
|
|
782
|
+
if (article.contentType === "mdx" && article.mdxSource) {
|
|
783
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_rsc.MDXRemote, { source: article.mdxSource }) });
|
|
784
|
+
}
|
|
785
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, dangerouslySetInnerHTML: { __html: article.htmlContent || "" } });
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// src/ArticleTOC.tsx
|
|
789
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
790
|
+
function ArticleTOC({ toc, className }) {
|
|
791
|
+
if (!toc.length) return null;
|
|
792
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
793
|
+
"nav",
|
|
794
|
+
{
|
|
795
|
+
"aria-label": "Table of contents",
|
|
796
|
+
className: `mb-8 rounded-lg border border-border bg-muted/40 px-6 py-4 ${className != null ? className : ""}`,
|
|
797
|
+
children: [
|
|
798
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: "On this page" }),
|
|
799
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("ul", { className: "space-y-1 text-sm", children: toc.map((item) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("li", { style: { paddingLeft: `${Math.max(0, item.depth - 2) * 1}rem` }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
800
|
+
"a",
|
|
801
|
+
{
|
|
802
|
+
href: `#${item.id}`,
|
|
803
|
+
className: "text-muted-foreground hover:text-foreground transition-colors",
|
|
804
|
+
children: item.text
|
|
805
|
+
}
|
|
806
|
+
) }, item.id)) })
|
|
807
|
+
]
|
|
808
|
+
}
|
|
809
|
+
);
|
|
810
|
+
}
|
|
755
811
|
// Annotate the CommonJS export names for ESM import in node:
|
|
756
812
|
0 && (module.exports = {
|
|
813
|
+
ArticleContent,
|
|
814
|
+
ArticleTOC,
|
|
757
815
|
categoryToSlug,
|
|
816
|
+
extractToc,
|
|
758
817
|
generateArticleMetadata,
|
|
759
818
|
generateArticleStaticParams,
|
|
760
819
|
generateArticlesIndexMetadata,
|