@fullstackdatasolutions/articles 0.4.3 → 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 +119 -2
- 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 +221 -188
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +36 -1
- package/dist/server.d.ts +36 -1
- package/dist/server.js +219 -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 +256 -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 +20 -14
- package/src/server-articles.ts +81 -78
- package/src/server.ts +3 -2
package/dist/server.cjs
CHANGED
|
@@ -67,7 +67,9 @@ 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,
|
|
73
75
|
generateArticlesIndexMetadata: () => generateArticlesIndexMetadata,
|
|
@@ -94,11 +96,14 @@ var import_node_path = __toESM(require("path"), 1);
|
|
|
94
96
|
var import_reading_time = __toESM(require("reading-time"), 1);
|
|
95
97
|
|
|
96
98
|
// src/markdown.ts
|
|
99
|
+
var import_rehype_autolink_headings = __toESM(require("rehype-autolink-headings"), 1);
|
|
97
100
|
var import_rehype_prism_plus = __toESM(require("rehype-prism-plus"), 1);
|
|
98
101
|
var import_rehype_sanitize = __toESM(require("rehype-sanitize"), 1);
|
|
102
|
+
var import_rehype_slug = __toESM(require("rehype-slug"), 1);
|
|
99
103
|
var import_rehype_stringify = __toESM(require("rehype-stringify"), 1);
|
|
100
104
|
var import_remark = require("remark");
|
|
101
105
|
var import_remark_gfm = __toESM(require("remark-gfm"), 1);
|
|
106
|
+
var import_remark_github_blockquote_alert = __toESM(require("remark-github-blockquote-alert"), 1);
|
|
102
107
|
var import_remark_parse = __toESM(require("remark-parse"), 1);
|
|
103
108
|
var import_remark_rehype = __toESM(require("remark-rehype"), 1);
|
|
104
109
|
var import_unist_util_visit = require("unist-util-visit");
|
|
@@ -129,6 +134,88 @@ function sanitizeImagePath(rawPath, articleSlug) {
|
|
|
129
134
|
return `/articles/${articleSlug}/${cleanPath}`;
|
|
130
135
|
}
|
|
131
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
|
+
}
|
|
132
219
|
var customRenderer = () => {
|
|
133
220
|
return (tree) => {
|
|
134
221
|
(0, import_unist_util_visit.visit)(tree, "element", (node) => {
|
|
@@ -195,95 +282,8 @@ var customRenderer = () => {
|
|
|
195
282
|
}
|
|
196
283
|
});
|
|
197
284
|
(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
|
-
}
|
|
285
|
+
processFootnoteRef(node);
|
|
286
|
+
processFootnotesSection(node);
|
|
287
287
|
});
|
|
288
288
|
};
|
|
289
289
|
};
|
|
@@ -308,7 +308,7 @@ var rehypeProcessImages = (options = {}) => {
|
|
|
308
308
|
function markdownToHtml(markdown, articleSlug) {
|
|
309
309
|
return __async(this, null, function* () {
|
|
310
310
|
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, {
|
|
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, {
|
|
312
312
|
attributes: {
|
|
313
313
|
"*": ["className", "class", "id"],
|
|
314
314
|
a: ["href", "target", "rel", "id"],
|
|
@@ -326,6 +326,31 @@ function markdownToHtml(markdown, articleSlug) {
|
|
|
326
326
|
}
|
|
327
327
|
});
|
|
328
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
|
+
}
|
|
329
354
|
|
|
330
355
|
// src/server-articles.ts
|
|
331
356
|
var articlesDirectory = import_node_path.default.join(process.cwd(), "public/articles");
|
|
@@ -372,23 +397,20 @@ function sanitizeImagePath2(rawPath, articleSlug) {
|
|
|
372
397
|
}
|
|
373
398
|
return `/articles/${articleSlug}/${cleanPath}`;
|
|
374
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
|
+
}
|
|
375
407
|
function getAvailableArticleSlugs() {
|
|
376
408
|
try {
|
|
377
409
|
if (!import_node_fs.default.existsSync(articlesDirectory)) return [];
|
|
378
410
|
const items = import_node_fs.default.readdirSync(articlesDirectory, { withFileTypes: true });
|
|
379
411
|
return items.filter((item) => item.isDirectory()).filter((dir) => {
|
|
380
|
-
const articleDir = import_node_path.default.join(articlesDirectory, dir.name);
|
|
381
412
|
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
|
-
});
|
|
413
|
+
return findArticleFile(dir.name) !== null;
|
|
392
414
|
} catch (e) {
|
|
393
415
|
return false;
|
|
394
416
|
}
|
|
@@ -398,45 +420,64 @@ function getAvailableArticleSlugs() {
|
|
|
398
420
|
return [];
|
|
399
421
|
}
|
|
400
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
|
+
}
|
|
401
452
|
function getArticleSummary(slug) {
|
|
402
453
|
return __async(this, null, function* () {
|
|
403
454
|
try {
|
|
404
|
-
const
|
|
405
|
-
if (!
|
|
406
|
-
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");
|
|
407
458
|
const { data, content: markdownContent } = (0, import_gray_matter.default)(fileContent);
|
|
408
459
|
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
460
|
const allTags = Array.isArray(data.tags) ? data.tags.filter((t) => typeof t === "string" && String(t).trim()) : [];
|
|
428
461
|
const categories = allTags.length > 0 ? allTags.map((t) => t.replaceAll("-", " ").trim()) : ["Campaigns"];
|
|
429
462
|
return {
|
|
430
463
|
slug,
|
|
431
464
|
title: data.title || slug.replaceAll("-", " "),
|
|
432
465
|
excerpt: data.excerpt || "",
|
|
433
|
-
date:
|
|
466
|
+
date: parseDateField(data.date),
|
|
467
|
+
lastmod: parseDateField(data.lastmod),
|
|
434
468
|
author: data.author || "Andrew Blase",
|
|
435
469
|
category: categories[0],
|
|
436
470
|
categories,
|
|
437
471
|
readTime,
|
|
438
|
-
featuredImage,
|
|
439
|
-
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
|
|
440
481
|
};
|
|
441
482
|
} catch (error) {
|
|
442
483
|
console.error(`Error loading article ${slug}:`, error);
|
|
@@ -446,46 +487,21 @@ function getArticleSummary(slug) {
|
|
|
446
487
|
}
|
|
447
488
|
var getArticleMetadata = (0, import_react.cache)((slug) => __async(void 0, null, function* () {
|
|
448
489
|
try {
|
|
449
|
-
const
|
|
450
|
-
if (!
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
const
|
|
454
|
-
const
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
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);
|
|
461
503
|
}
|
|
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
|
-
};
|
|
504
|
+
return __spreadProps(__spreadValues({}, summary), { content: markdownContent, htmlContent, mdxSource, toc });
|
|
489
505
|
} catch (error) {
|
|
490
506
|
console.error(`Error loading article ${slug}:`, error);
|
|
491
507
|
return null;
|
|
@@ -495,7 +511,7 @@ var getAllArticles = (0, import_react.cache)(() => __async(void 0, null, functio
|
|
|
495
511
|
const slugs = getAvailableArticleSlugs();
|
|
496
512
|
const articles = yield Promise.all(slugs.map((slug) => getArticleSummary(slug)));
|
|
497
513
|
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) => {
|
|
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) => {
|
|
499
515
|
if (!a.date && !b.date) return 0;
|
|
500
516
|
if (!a.date) return 1;
|
|
501
517
|
if (!b.date) return -1;
|
|
@@ -579,7 +595,7 @@ function resolveImageUrl(featuredImage, siteUrl) {
|
|
|
579
595
|
}
|
|
580
596
|
function generateArticleMetadata(slug, config) {
|
|
581
597
|
return __async(this, null, function* () {
|
|
582
|
-
var _a, _b, _c, _d, _e, _f;
|
|
598
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
583
599
|
const article = yield getArticleMetadata(slug);
|
|
584
600
|
if (!article) {
|
|
585
601
|
return {
|
|
@@ -589,19 +605,14 @@ function generateArticleMetadata(slug, config) {
|
|
|
589
605
|
}
|
|
590
606
|
const siteUrl = config.siteUrl.replace(/\/$/, "");
|
|
591
607
|
const articleUrl = `${siteUrl}/articles/${slug}`;
|
|
608
|
+
const canonicalUrl = (_a = article.canonicalUrl) != null ? _a : articleUrl;
|
|
592
609
|
const imageUrl = article.featuredImage ? resolveImageUrl(article.featuredImage, siteUrl) : `${siteUrl}/placeholder-logo.png`;
|
|
593
|
-
const description = (
|
|
610
|
+
const description = (_b = article.excerpt) != null ? _b : `Read ${article.title} on ${config.siteName}.`;
|
|
594
611
|
return {
|
|
595
612
|
title: `${article.title} | ${config.siteName}`,
|
|
596
613
|
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({
|
|
614
|
+
keywords: [...((_c = article.tags) != null ? _c : []).map((tag) => tag.toLowerCase())].join(", "),
|
|
615
|
+
openGraph: __spreadProps(__spreadValues(__spreadValues({
|
|
605
616
|
title: article.title,
|
|
606
617
|
description,
|
|
607
618
|
url: articleUrl,
|
|
@@ -609,9 +620,9 @@ function generateArticleMetadata(slug, config) {
|
|
|
609
620
|
images: [{ url: imageUrl, width: 1200, height: 630, alt: article.title }],
|
|
610
621
|
locale: "en_US",
|
|
611
622
|
type: "article"
|
|
612
|
-
}, article.date && { publishedTime: article.date }), {
|
|
623
|
+
}, article.date && { publishedTime: article.date }), article.lastmod && { modifiedTime: new Date(article.lastmod).toISOString() }), {
|
|
613
624
|
authors: [article.author],
|
|
614
|
-
tags: (
|
|
625
|
+
tags: (_d = article.tags) != null ? _d : []
|
|
615
626
|
}),
|
|
616
627
|
twitter: {
|
|
617
628
|
card: "summary_large_image",
|
|
@@ -620,7 +631,7 @@ function generateArticleMetadata(slug, config) {
|
|
|
620
631
|
images: [imageUrl]
|
|
621
632
|
},
|
|
622
633
|
alternates: {
|
|
623
|
-
canonical:
|
|
634
|
+
canonical: canonicalUrl
|
|
624
635
|
},
|
|
625
636
|
robots: {
|
|
626
637
|
index: true,
|
|
@@ -633,14 +644,16 @@ function generateArticleMetadata(slug, config) {
|
|
|
633
644
|
"max-snippet": -1
|
|
634
645
|
}
|
|
635
646
|
},
|
|
636
|
-
other: __spreadProps(__spreadValues({
|
|
647
|
+
other: __spreadProps(__spreadValues(__spreadValues({
|
|
637
648
|
"article:author": article.author
|
|
638
649
|
}, article.date && {
|
|
639
650
|
"article:published_time": new Date(article.date).toISOString()
|
|
651
|
+
}), article.lastmod && {
|
|
652
|
+
"article:modified_time": new Date(article.lastmod).toISOString()
|
|
640
653
|
}), {
|
|
641
654
|
"article:section": article.category,
|
|
642
|
-
"article:tag": (
|
|
643
|
-
"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 : ""
|
|
644
657
|
})
|
|
645
658
|
};
|
|
646
659
|
});
|
|
@@ -668,7 +681,10 @@ function generateArticlesIndexMetadata(config) {
|
|
|
668
681
|
description
|
|
669
682
|
},
|
|
670
683
|
alternates: {
|
|
671
|
-
canonical: indexUrl
|
|
684
|
+
canonical: indexUrl,
|
|
685
|
+
types: {
|
|
686
|
+
"application/rss+xml": `${siteUrl}/articles/feed.xml`
|
|
687
|
+
}
|
|
672
688
|
},
|
|
673
689
|
robots: {
|
|
674
690
|
index: true,
|
|
@@ -734,12 +750,17 @@ function getArticleSitemapEntries(baseUrlOrConfig) {
|
|
|
734
750
|
const baseUrl = (typeof baseUrlOrConfig === "string" ? baseUrlOrConfig : baseUrlOrConfig.siteUrl).replace(/\/$/, "");
|
|
735
751
|
try {
|
|
736
752
|
const [articles, categories] = yield Promise.all([getAllArticles(), getAllCategories()]);
|
|
737
|
-
const articleEntries = articles.map((article) =>
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
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
|
+
});
|
|
743
764
|
const categoryEntries = categories.map((cat) => ({
|
|
744
765
|
url: `${baseUrl}/articles/category/${cat.slug}`,
|
|
745
766
|
lastModified: /* @__PURE__ */ new Date(),
|
|
@@ -752,9 +773,21 @@ function getArticleSitemapEntries(baseUrlOrConfig) {
|
|
|
752
773
|
}
|
|
753
774
|
});
|
|
754
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
|
+
}
|
|
755
786
|
// Annotate the CommonJS export names for ESM import in node:
|
|
756
787
|
0 && (module.exports = {
|
|
788
|
+
ArticleContent,
|
|
757
789
|
categoryToSlug,
|
|
790
|
+
extractToc,
|
|
758
791
|
generateArticleMetadata,
|
|
759
792
|
generateArticleStaticParams,
|
|
760
793
|
generateArticlesIndexMetadata,
|