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