@fullstackdatasolutions/articles 0.6.0 → 0.7.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 +71 -12
- package/dist/index.cjs +68 -51
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +66 -50
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +70 -18
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +3 -1
- package/dist/server.d.ts +3 -1
- package/dist/server.js +69 -17
- package/dist/server.js.map +1 -1
- package/package.json +34 -20
- package/src/ArticleBackLink.tsx +32 -0
- package/src/ArticleContent.tsx +4 -7
- package/src/ArticleSocialShare.tsx +2 -7
- package/src/__tests__/ArticleBackLink.test.tsx +89 -0
- package/src/__tests__/ArticleContent.test.tsx +72 -28
- package/src/__tests__/ArticleSocialShare.test.tsx +7 -12
- package/src/__tests__/articlesConfig.test.ts +57 -0
- package/src/__tests__/markdown.test.ts +278 -0
- package/src/__tests__/renderMdx.test.tsx +280 -0
- package/src/__tests__/server-articles.test.ts +310 -1
- package/src/articlesConfig.ts +2 -0
- package/src/index.ts +1 -0
- package/src/markdown.ts +8 -6
- package/src/renderMdx.tsx +47 -0
- package/src/server-articles.ts +1 -2
package/dist/server.cjs
CHANGED
|
@@ -22,6 +22,18 @@ var __spreadValues = (a, b) => {
|
|
|
22
22
|
return a;
|
|
23
23
|
};
|
|
24
24
|
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
25
|
+
var __objRest = (source, exclude) => {
|
|
26
|
+
var target = {};
|
|
27
|
+
for (var prop in source)
|
|
28
|
+
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
|
|
29
|
+
target[prop] = source[prop];
|
|
30
|
+
if (source != null && __getOwnPropSymbols)
|
|
31
|
+
for (var prop of __getOwnPropSymbols(source)) {
|
|
32
|
+
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
|
|
33
|
+
target[prop] = source[prop];
|
|
34
|
+
}
|
|
35
|
+
return target;
|
|
36
|
+
};
|
|
25
37
|
var __export = (target, all) => {
|
|
26
38
|
for (var name in all)
|
|
27
39
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -97,7 +109,6 @@ var import_node_path = __toESM(require("path"), 1);
|
|
|
97
109
|
var import_reading_time = __toESM(require("reading-time"), 1);
|
|
98
110
|
|
|
99
111
|
// src/markdown.ts
|
|
100
|
-
var import_rehype_autolink_headings = __toESM(require("rehype-autolink-headings"), 1);
|
|
101
112
|
var import_rehype_prism_plus = __toESM(require("rehype-prism-plus"), 1);
|
|
102
113
|
var import_rehype_sanitize = __toESM(require("rehype-sanitize"), 1);
|
|
103
114
|
var import_rehype_slug = __toESM(require("rehype-slug"), 1);
|
|
@@ -238,11 +249,15 @@ var customRenderer = () => {
|
|
|
238
249
|
case "p":
|
|
239
250
|
props.className = "text-muted-foreground leading-relaxed mb-4";
|
|
240
251
|
break;
|
|
241
|
-
case "a":
|
|
252
|
+
case "a": {
|
|
242
253
|
props.className = "text-primary hover:underline transition-colors duration-200";
|
|
243
|
-
props.
|
|
244
|
-
|
|
254
|
+
const href = typeof props.href === "string" ? props.href : "";
|
|
255
|
+
if (!href.startsWith("#")) {
|
|
256
|
+
props.target = "_blank";
|
|
257
|
+
props.rel = "noopener noreferrer";
|
|
258
|
+
}
|
|
245
259
|
break;
|
|
260
|
+
}
|
|
246
261
|
case "ul":
|
|
247
262
|
props.className = "list-disc list-inside mb-4 space-y-2 ml-4";
|
|
248
263
|
break;
|
|
@@ -309,7 +324,7 @@ var rehypeProcessImages = (options = {}) => {
|
|
|
309
324
|
function markdownToHtml(markdown, articleSlug) {
|
|
310
325
|
return __async(this, null, function* () {
|
|
311
326
|
try {
|
|
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(
|
|
327
|
+
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_prism_plus.default).use(import_rehype_sanitize.default, {
|
|
313
328
|
attributes: {
|
|
314
329
|
"*": ["className", "class", "id"],
|
|
315
330
|
a: ["href", "target", "rel", "id"],
|
|
@@ -423,9 +438,8 @@ function walkArticleDir(dir, baseSlug) {
|
|
|
423
438
|
const slug = baseSlug ? `${baseSlug}/${item.name}` : item.name;
|
|
424
439
|
if (findArticleFile(slug) !== null) {
|
|
425
440
|
slugs.push(slug);
|
|
426
|
-
} else {
|
|
427
|
-
slugs.push(...walkArticleDir(import_node_path.default.join(dir, item.name), slug));
|
|
428
441
|
}
|
|
442
|
+
slugs.push(...walkArticleDir(import_node_path.default.join(dir, item.name), slug));
|
|
429
443
|
}
|
|
430
444
|
} catch (e) {
|
|
431
445
|
}
|
|
@@ -785,28 +799,66 @@ function getArticleSitemapEntries(baseUrlOrConfig) {
|
|
|
785
799
|
});
|
|
786
800
|
}
|
|
787
801
|
|
|
788
|
-
// src/
|
|
789
|
-
var
|
|
802
|
+
// src/renderMdx.tsx
|
|
803
|
+
var import_react2 = __toESM(require("react"), 1);
|
|
804
|
+
var devRuntime = __toESM(require("react/jsx-dev-runtime"), 1);
|
|
805
|
+
var runtime = __toESM(require("react/jsx-runtime"), 1);
|
|
806
|
+
var import_mdx = require("@mdx-js/mdx");
|
|
807
|
+
var import_rehype_prism_plus2 = __toESM(require("rehype-prism-plus"), 1);
|
|
808
|
+
var import_rehype_slug2 = __toESM(require("rehype-slug"), 1);
|
|
809
|
+
var import_remark_gfm2 = __toESM(require("remark-gfm"), 1);
|
|
810
|
+
var import_remark_github_blockquote_alert2 = __toESM(require("remark-github-blockquote-alert"), 1);
|
|
790
811
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
791
|
-
function
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
812
|
+
function makeImgComponent(basePath) {
|
|
813
|
+
return function MdxImage(_a) {
|
|
814
|
+
var _b = _a, { src, alt } = _b, props = __objRest(_b, ["src", "alt"]);
|
|
815
|
+
const resolvedSrc = src && !src.startsWith("http") && !src.startsWith("/") ? `${basePath}/${src}` : src;
|
|
816
|
+
return import_react2.default.createElement("img", __spreadValues({ src: resolvedSrc, alt }, props));
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
function renderMdxSource(source, basePath) {
|
|
820
|
+
return __async(this, null, function* () {
|
|
821
|
+
const isDevelopment = process.env.NODE_ENV === "development";
|
|
822
|
+
const mdxModule = yield (0, import_mdx.evaluate)(source, __spreadProps(__spreadValues({}, isDevelopment ? devRuntime : runtime), {
|
|
823
|
+
development: isDevelopment,
|
|
824
|
+
remarkPlugins: [import_remark_gfm2.default, import_remark_github_blockquote_alert2.default],
|
|
825
|
+
rehypePlugins: [
|
|
826
|
+
customRenderer,
|
|
827
|
+
import_rehype_slug2.default,
|
|
828
|
+
// @ts-ignore
|
|
829
|
+
import_rehype_prism_plus2.default
|
|
830
|
+
]
|
|
831
|
+
}));
|
|
832
|
+
const Content = mdxModule.default;
|
|
833
|
+
const components = basePath ? { img: makeImgComponent(basePath) } : void 0;
|
|
834
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Content, { components });
|
|
835
|
+
});
|
|
796
836
|
}
|
|
797
837
|
|
|
798
|
-
// src/
|
|
838
|
+
// src/ArticleContent.tsx
|
|
799
839
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
840
|
+
function ArticleContent(_0) {
|
|
841
|
+
return __async(this, arguments, function* ({ article, className }) {
|
|
842
|
+
if (article.contentType === "mdx" && article.mdxSource) {
|
|
843
|
+
const content = yield renderMdxSource(article.mdxSource, `/articles/${article.slug}`);
|
|
844
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className, children: content });
|
|
845
|
+
}
|
|
846
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className, dangerouslySetInnerHTML: { __html: article.htmlContent || "" } });
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// src/ArticleTOC.tsx
|
|
851
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
800
852
|
function ArticleTOC({ toc, className }) {
|
|
801
853
|
if (!toc.length) return null;
|
|
802
|
-
return /* @__PURE__ */ (0,
|
|
854
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
803
855
|
"nav",
|
|
804
856
|
{
|
|
805
857
|
"aria-label": "Table of contents",
|
|
806
858
|
className: `mb-8 rounded-lg border border-border bg-muted/40 px-6 py-4 ${className != null ? className : ""}`,
|
|
807
859
|
children: [
|
|
808
|
-
/* @__PURE__ */ (0,
|
|
809
|
-
/* @__PURE__ */ (0,
|
|
860
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: "On this page" }),
|
|
861
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ul", { className: "space-y-1 text-sm", children: toc.map((item) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("li", { style: { paddingLeft: `${Math.max(0, item.depth - 2) * 1}rem` }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
810
862
|
"a",
|
|
811
863
|
{
|
|
812
864
|
href: `#${item.id}`,
|
package/dist/server.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts","../src/server-articles.ts","../src/markdown.ts","../src/seoUtils.ts","../src/ArticleContent.tsx","../src/ArticleTOC.tsx"],"sourcesContent":["// Server-only exports — uses fs/path; never import this in a client bundle\nexport {\n getAllArticles,\n getArticleMetadata,\n getAvailableArticleSlugs,\n getAdjacentArticles,\n searchArticles,\n getAllCategories,\n getArticlesByCategory,\n categoryToSlug,\n sanitizeImagePath,\n} from './server-articles'\n\nexport {\n generateArticleStaticParams,\n generateCategoryStaticParams,\n generateArticlesIndexMetadata,\n generateArticleMetadata,\n generateCategoryMetadata,\n getArticleSitemapEntries,\n} from './seoUtils'\n\nexport { markdownToHtml, extractToc } from './markdown'\nexport { ArticleContent } from './ArticleContent'\nexport { ArticleTOC } from './ArticleTOC'\n\nexport type { Article, CategoryInfo, TocItem } from './articleTypes'\nexport type { ArticlesConfig } from './articlesConfig'\n","import { cache } from 'react'\nimport matter from 'gray-matter'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport readingTime from 'reading-time'\nimport { markdownToHtml, extractToc } from './markdown'\nimport type { Article, CategoryInfo, FaqItem, HowToStep } from './articleTypes'\n\nconst articlesDirectory = path.join(process.cwd(), 'public/articles')\n\nfunction getReadingTime(content: string): string {\n return readingTime(content).text\n}\n\nfunction findArticleImage(slug: string): string | null {\n try {\n const articleDir = path.join(articlesDirectory, slug)\n const entries: (fs.Dirent | string)[] = fs.readdirSync(articleDir, { withFileTypes: true })\n const names = entries\n .map((entry) => {\n if (typeof entry === 'string') return entry\n if (entry && typeof entry.name === 'string') return entry.name\n return null\n })\n .filter((name): name is string => Boolean(name))\n const imageExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.webp']\n const imageFileName = names.find((name) =>\n imageExtensions.some((ext) => name.toLowerCase().endsWith(ext))\n )\n return imageFileName ? sanitizeImagePath(imageFileName, slug) : null\n } catch {\n return null\n }\n}\n\nfunction sanitizeImagePath(rawPath: string, articleSlug: string): string | null {\n if (!rawPath || typeof rawPath !== 'string') return null\n const cleanPath = rawPath.replaceAll(/[\\x00-\\x1f\\x7f-\\x9f]/g, '')\n if (cleanPath.startsWith('http://') || cleanPath.startsWith('https://')) return cleanPath\n if (cleanPath.includes('..') || cleanPath.includes('\\\\') || cleanPath.startsWith('/')) {\n console.warn(`Potentially unsafe image path detected: ${cleanPath}`)\n return null\n }\n if (!/^[a-zA-Z0-9._/-]+$/.test(cleanPath)) {\n console.warn(`Invalid characters in image path: ${cleanPath}`)\n return null\n }\n if (cleanPath.includes('/')) {\n const normalizedPath = path.normalize(cleanPath)\n if (normalizedPath.startsWith('..') || normalizedPath.includes('../')) {\n console.warn(`Path traversal attempt detected: ${cleanPath}`)\n return null\n }\n return `/articles/${articleSlug}/${cleanPath}`\n }\n return `/articles/${articleSlug}/${cleanPath}`\n}\n\nfunction findArticleFile(slug: string): { filePath: string; contentType: 'md' | 'mdx' } | null {\n const mdPath = path.join(articlesDirectory, slug, 'article.md')\n const mdxPath = path.join(articlesDirectory, slug, 'article.mdx')\n if (fs.existsSync(mdPath)) return { filePath: mdPath, contentType: 'md' }\n if (fs.existsSync(mdxPath)) return { filePath: mdxPath, contentType: 'mdx' }\n return null\n}\n\nexport function getAvailableArticleSlugs(): string[] {\n try {\n if (!fs.existsSync(articlesDirectory)) return []\n return walkArticleDir(articlesDirectory, '')\n } catch (error) {\n console.error('Error reading articles directory:', error)\n return []\n }\n}\n\nfunction walkArticleDir(dir: string, baseSlug: string): string[] {\n const slugs: string[] = []\n try {\n const items = fs.readdirSync(dir, { withFileTypes: true })\n for (const item of items) {\n if (!item.isDirectory()) continue\n const slug = baseSlug ? `${baseSlug}/${item.name}` : item.name\n if (findArticleFile(slug) !== null) {\n slugs.push(slug)\n } else {\n slugs.push(...walkArticleDir(path.join(dir, item.name), slug))\n }\n }\n } catch {\n // ignore unreadable directories\n }\n return slugs\n}\n\nfunction parseDateField(rawDate: unknown): string | undefined {\n if (!rawDate) return undefined\n try {\n const parsed = new Date(rawDate as string)\n if (!Number.isNaN(parsed.getTime())) return parsed.toISOString().split('T')[0]\n } catch {\n // ignore invalid dates\n }\n return undefined\n}\n\nfunction resolveFeaturedImage(rawImage: unknown, slug: string): string {\n const img = typeof rawImage === 'string' ? rawImage : ''\n if (img && !img.startsWith('http')) return sanitizeImagePath(img, slug) || '/placeholder-logo.png'\n if (!img) return findArticleImage(slug) || '/placeholder-logo.png'\n return img\n}\n\nfunction parseFaqItems(raw: unknown): FaqItem[] | undefined {\n if (!Array.isArray(raw)) return undefined\n const items = raw.filter(\n (item): item is FaqItem =>\n typeof item === 'object' &&\n item !== null &&\n typeof (item as FaqItem).question === 'string' &&\n typeof (item as FaqItem).answer === 'string'\n )\n return items.length ? items : undefined\n}\n\nfunction parseHowToSteps(raw: unknown): HowToStep[] | undefined {\n if (!Array.isArray(raw)) return undefined\n const steps = raw.filter(\n (item): item is HowToStep =>\n typeof item === 'object' &&\n item !== null &&\n typeof (item as HowToStep).name === 'string' &&\n typeof (item as HowToStep).text === 'string'\n )\n return steps.length ? steps : undefined\n}\n\nasync function getArticleSummary(slug: string): Promise<Article | null> {\n try {\n const found = findArticleFile(slug)\n if (!found) return null\n const fileContent = fs.readFileSync(found.filePath, 'utf8')\n const { data, content: markdownContent } = matter(fileContent)\n const readTime = getReadingTime(markdownContent)\n const allTags: string[] = Array.isArray(data.tags)\n ? data.tags.filter((t: unknown) => typeof t === 'string' && String(t).trim())\n : []\n const categories: string[] =\n allTags.length > 0 ? allTags.map((t: string) => t.replaceAll('-', ' ').trim()) : ['Campaigns']\n return {\n slug,\n title: data.title || slug.replaceAll('-', ' '),\n excerpt: data.excerpt || '',\n date: parseDateField(data.date),\n lastmod: parseDateField(data.lastmod),\n author: data.author || 'Andrew Blase',\n category: categories[0],\n categories,\n readTime,\n featuredImage: resolveFeaturedImage(data.featuredImage, slug),\n tags: data.tags || [],\n contentType: found.contentType,\n draft: data.draft === true,\n faq: parseFaqItems(data.faq),\n howTo: parseHowToSteps(data.howTo),\n canonicalUrl: typeof data.canonicalUrl === 'string' ? data.canonicalUrl : undefined,\n articleType: typeof data.articleType === 'string' ? data.articleType : undefined,\n series: typeof data.series === 'string' ? data.series : undefined,\n }\n } catch (error) {\n console.error(`Error loading article ${slug}:`, error)\n return null\n }\n}\n\nexport const getArticleMetadata = cache(async (slug: string): Promise<Article | null> => {\n try {\n const summary = await getArticleSummary(slug)\n if (!summary) return null\n const found = findArticleFile(slug)\n if (!found) return null\n const fileContent = fs.readFileSync(found.filePath, 'utf8')\n const { content: markdownContent } = matter(fileContent)\n const toc = await extractToc(markdownContent)\n let htmlContent: string | undefined\n let mdxSource: string | undefined\n if (found.contentType === 'mdx') {\n mdxSource = markdownContent\n } else {\n htmlContent = await markdownToHtml(markdownContent, slug)\n }\n return { ...summary, content: markdownContent, htmlContent, mdxSource, toc }\n } catch (error) {\n console.error(`Error loading article ${slug}:`, error)\n return null\n }\n})\n\nexport const getAllArticles = cache(async (): Promise<Article[]> => {\n const slugs = getAvailableArticleSlugs()\n const articles = await Promise.all(slugs.map((slug) => getArticleSummary(slug)))\n const currentDate = new Date().toISOString().split('T')[0]\n return articles\n .filter((article): article is Article => article !== null)\n .filter((article) => !article.date || article.date <= currentDate)\n .filter((article) => !(article.draft && process.env.NODE_ENV === 'production'))\n .sort((a, b) => {\n if (!a.date && !b.date) return 0\n if (!a.date) return 1\n if (!b.date) return -1\n return new Date(b.date).getTime() - new Date(a.date).getTime()\n })\n})\n\nexport async function getAdjacentArticles(\n currentSlug: string\n): Promise<{ previous: Article | null; next: Article | null }> {\n const allArticles = await getAllArticles()\n const currentIndex = allArticles.findIndex((article) => article.slug === currentSlug)\n if (currentIndex === -1) return { previous: null, next: null }\n const previous = currentIndex < allArticles.length - 1 ? allArticles[currentIndex + 1] : null\n const next = currentIndex > 0 ? allArticles[currentIndex - 1] : null\n return { previous, next }\n}\n\nexport async function searchArticles(query: string): Promise<Article[]> {\n if (!query?.trim()) return getAllArticles()\n const articles = await getAllArticles()\n const searchTerm = query.toLowerCase().trim()\n return articles.filter((article) => {\n const matchesTitle = article.title.toLowerCase().includes(searchTerm)\n const matchesExcerpt = article.excerpt.toLowerCase().includes(searchTerm)\n const matchesAuthor = article.author.toLowerCase().includes(searchTerm)\n const matchesCategory = article.categories.some((cat) => cat.toLowerCase().includes(searchTerm))\n const matchesTags = article.tags?.some((tag) => tag.toLowerCase().includes(searchTerm))\n return (\n matchesTitle || matchesExcerpt || matchesAuthor || matchesCategory || Boolean(matchesTags)\n )\n })\n}\n\nexport function categoryToSlug(category: string): string {\n return category\n .toLowerCase()\n .replaceAll(/\\s+/g, '-')\n .replaceAll(/[^a-z0-9-]/g, '')\n}\n\nexport async function getAllCategories(): Promise<CategoryInfo[]> {\n const articles = await getAllArticles()\n const categoryMap = new Map<string, { count: number; featuredImage: string }>()\n for (const article of articles) {\n for (const cat of article.categories) {\n if (!categoryMap.has(cat)) {\n categoryMap.set(cat, { count: 0, featuredImage: article.featuredImage })\n }\n categoryMap.get(cat)!.count++\n }\n }\n return Array.from(categoryMap.entries())\n .map(([name, { count, featuredImage }]) => ({\n name,\n slug: categoryToSlug(name),\n count,\n featuredImage,\n }))\n .sort((a, b) => b.count - a.count)\n}\n\nexport async function getArticlesByCategory(categorySlug: string): Promise<Article[]> {\n const articles = await getAllArticles()\n return articles.filter((article) =>\n article.categories.some((cat) => categoryToSlug(cat) === categorySlug)\n )\n}\n\nexport { sanitizeImagePath }\n","import type { Element, Root, ElementContent } from 'hast'\nimport rehypeAutolinkHeadings from 'rehype-autolink-headings'\nimport rehypePrism from 'rehype-prism-plus'\nimport rehypeSanitize from 'rehype-sanitize'\nimport rehypeSlug from 'rehype-slug'\nimport rehypeStringify from 'rehype-stringify'\nimport { remark } from 'remark'\nimport remarkGfm from 'remark-gfm'\nimport remarkGithubBlockquoteAlert from 'remark-github-blockquote-alert'\nimport remarkParse from 'remark-parse'\nimport remarkRehype from 'remark-rehype'\nimport { Plugin } from 'unified'\nimport { visit } from 'unist-util-visit'\nimport type { TocItem } from './articleTypes'\n\n// Import the sanitizeImagePath function\nfunction sanitizeImagePath(rawPath: string, articleSlug: string): string | null {\n if (!rawPath || typeof rawPath !== 'string') {\n return null\n }\n\n // Remove any null bytes or control characters\n const cleanPath = rawPath.replaceAll(/[\\x00-\\x1f\\x7f-\\x9f]/g, '')\n\n // Check for absolute URLs (http/https)\n if (cleanPath.startsWith('http://') || cleanPath.startsWith('https://')) {\n // For external URLs, just return as-is (they're safe)\n return cleanPath\n }\n\n // For relative paths, ensure they don't contain dangerous patterns\n if (cleanPath.includes('..') || cleanPath.includes('\\\\') || cleanPath.startsWith('/')) {\n console.warn(`Potentially unsafe image path detected: ${cleanPath}`)\n return null\n }\n\n // Only allow alphanumeric characters, hyphens, underscores, dots, and forward slashes\n if (!/^[a-zA-Z0-9._/-]+$/.test(cleanPath)) {\n console.warn(`Invalid characters in image path: ${cleanPath}`)\n return null\n }\n\n // Construct safe path within articles directory\n if (cleanPath.includes('/')) {\n // Relative path, ensure it's within the article directory\n const normalizedPath = cleanPath.replaceAll('\\\\', '/')\n if (normalizedPath.startsWith('..') || normalizedPath.includes('../')) {\n console.warn(`Path traversal attempt detected: ${cleanPath}`)\n return null\n }\n return `/articles/${articleSlug}/${normalizedPath}`\n } else {\n // Just a filename, construct full path\n return `/articles/${articleSlug}/${cleanPath}`\n }\n}\n\nfunction styleFootnoteLinks(nodes: ElementContent[]): void {\n nodes.forEach((n) => {\n if (n.type !== 'element') return\n const el = n as Element\n if (el.tagName === 'a') {\n const isBackRef =\n el.properties?.['dataFootnoteBackref'] !== undefined ||\n (el.children[0]?.type === 'text' && el.children[0].value === '↩')\n if (isBackRef) {\n el.properties.className = 'text-primary hover:underline ml-1'\n if (typeof el.properties.href === 'string') {\n el.properties.href = el.properties.href.replaceAll('#user-content-fnref-', '#ref-')\n }\n } else {\n el.properties.className = 'text-primary hover:underline break-all'\n }\n }\n if (el.children) styleFootnoteLinks(el.children)\n })\n}\n\nfunction processFootnoteRef(node: Element): void {\n if (\n node.tagName !== 'sup' ||\n node.children?.[0]?.type !== 'element' ||\n (node.children[0] as Element).tagName !== 'a'\n )\n return\n\n const link = node.children[0] as Element\n const href = link.properties?.href\n if (typeof href !== 'string' || !href.startsWith('#user-content-fn-')) return\n\n delete link.properties.target\n delete link.properties.rel\n link.properties.className = 'text-primary hover:underline'\n link.properties.href = href.replaceAll('#user-content-fn-', '#footnote-')\n\n if (typeof node.properties?.id === 'string') {\n link.properties.id = node.properties.id.replaceAll('user-content-fnref-', 'ref-')\n delete node.properties.id\n }\n\n if (link.children?.[0]?.type === 'text') {\n link.children[0].value = `[${link.children[0].value}]`\n }\n}\n\nfunction processFootnotesSection(node: Element): void {\n const cls = node.properties?.className\n const isFootnotes =\n node.tagName === 'section' &&\n (Array.isArray(cls) ? cls.includes('footnotes') : cls === 'footnotes')\n if (!isFootnotes) return\n\n const olCandidate = node.children.find(\n (child) => child.type === 'element' && (child as Element).tagName === 'ol'\n )\n if (olCandidate?.type !== 'element') return\n\n const ol = olCandidate as Element\n ol.properties.className = 'list-decimal ml-6 space-y-2 text-sm text-muted-foreground'\n\n ol.children.forEach((li) => {\n if (li.type !== 'element' || li.tagName !== 'li') return\n const liEl = li as Element\n\n if (liEl.properties) {\n liEl.properties.className = 'pl-2'\n if (typeof liEl.properties.id === 'string') {\n liEl.properties.id = liEl.properties.id.replaceAll('user-content-fn-', 'footnote-')\n }\n }\n\n const pIndex = liEl.children.findIndex(\n (child) => child.type === 'element' && (child as Element).tagName === 'p'\n )\n if (pIndex !== -1) {\n const p = liEl.children[pIndex] as Element\n liEl.children.splice(pIndex, 1, ...p.children)\n }\n\n styleFootnoteLinks(liEl.children)\n })\n\n const hr: Element = {\n type: 'element',\n tagName: 'hr',\n properties: { className: 'my-8 border-border' },\n children: [],\n }\n const h3: Element = {\n type: 'element',\n tagName: 'h3',\n properties: { className: 'text-lg font-semibold mb-4' },\n children: [{ type: 'text', value: 'References' }],\n }\n\n node.children = [hr, h3, ol]\n if (node.properties) node.properties.className = undefined\n}\n\nconst customRenderer: Plugin<[], Root> = () => {\n return (tree: Root) => {\n // First pass: Apply general styles\n visit(tree, 'element', (node: Element) => {\n if (node.tagName) {\n const props = node.properties || {}\n\n switch (node.tagName) {\n case 'h1':\n props.className = 'text-3xl font-bold text-foreground mt-8 mb-4 scroll-mt-20'\n break\n case 'h2':\n props.className = 'text-2xl font-semibold text-foreground mt-6 mb-3 scroll-mt-20'\n break\n case 'h3':\n props.className = 'text-xl font-semibold text-foreground mt-4 mb-2 scroll-mt-20'\n break\n case 'h4':\n props.className = 'text-lg font-semibold text-foreground mt-3 mb-2 scroll-mt-20'\n break\n case 'p':\n props.className = 'text-muted-foreground leading-relaxed mb-4'\n break\n case 'a':\n props.className = 'text-primary hover:underline transition-colors duration-200'\n props.target = '_blank'\n props.rel = 'noopener noreferrer'\n break\n case 'ul':\n props.className = 'list-disc list-inside mb-4 space-y-2 ml-4'\n break\n case 'ol':\n props.className = 'list-decimal list-inside mb-4 space-y-2 ml-4'\n break\n case 'li':\n props.className = 'mb-1'\n break\n case 'blockquote':\n props.className = 'border-l-4 border-primary pl-4 italic my-4 text-muted-foreground'\n break\n case 'code':\n props.className =\n (props.className ? props.className + ' ' : '') +\n 'bg-muted px-1 py-0.5 rounded text-sm font-mono'\n break\n case 'pre':\n props.className = 'bg-muted rounded-lg p-4 overflow-x-auto my-4'\n break\n case 'img':\n props.className = 'rounded-lg my-6 w-full max-w-2xl mx-auto'\n break\n case 'table':\n props.className = 'border-collapse border border-border my-4 w-full'\n break\n case 'th':\n props.className = 'border border-border px-2 py-1 bg-muted font-semibold'\n break\n case 'td':\n props.className = 'border border-border px-2 py-1'\n break\n case 'hr':\n props.className = 'my-8 border-border'\n break\n default:\n break\n }\n\n node.properties = props\n }\n })\n\n // Second pass: Fix footnotes and references\n visit(tree, 'element', (node: Element) => {\n processFootnoteRef(node)\n processFootnotesSection(node)\n })\n }\n}\n\n// Custom rehype plugin to process image URLs\nconst rehypeProcessImages: Plugin<[{ articleSlug?: string }], Root> = (options = {}) => {\n return (tree: Root) => {\n visit(tree, 'element', (node: Element) => {\n if (node.tagName === 'img' && node.properties) {\n const src = node.properties.src\n if (src && typeof src === 'string' && options.articleSlug) {\n // Sanitize the image path\n const sanitizedSrc = sanitizeImagePath(src, options.articleSlug)\n if (sanitizedSrc) {\n node.properties.src = sanitizedSrc\n } else {\n // If sanitization fails, use a placeholder\n console.warn(`Using placeholder for unsafe image path: ${src}`)\n node.properties.src = '/placeholder-logo.png'\n }\n }\n }\n })\n }\n}\n\nexport async function markdownToHtml(markdown: string, articleSlug?: string) {\n try {\n // Start building the remark processor\n let processor = remark()\n .use(remarkParse)\n .use(remarkGfm)\n .use(remarkGithubBlockquoteAlert)\n .use(remarkRehype)\n .use(customRenderer)\n .use(rehypeSlug)\n .use(rehypeAutolinkHeadings, { behavior: 'wrap' })\n // @ts-ignore\n .use(rehypePrism)\n .use(rehypeSanitize, {\n attributes: {\n '*': ['className', 'class', 'id'],\n a: ['href', 'target', 'rel', 'id'],\n img: ['src', 'alt'],\n },\n })\n\n // Add image processing plugin if articleSlug is provided\n if (articleSlug) {\n processor = processor.use(rehypeProcessImages, { articleSlug })\n }\n\n const result = await processor.use(rehypeStringify).process(markdown)\n\n return result.toString()\n } catch (error) {\n console.error('Markdown conversion error:', error)\n // Return the original markdown as fallback\n return markdown\n }\n}\n\nfunction nodeTextValue(c: ElementContent): string {\n return c.type === 'text' ? (c as { value: string }).value : ''\n}\n\nfunction extractHeadingItem(node: Element): TocItem | null {\n const match = /^h([1-6])$/.exec(node.tagName)\n if (!match) return null\n const id = typeof node.properties?.id === 'string' ? node.properties.id : ''\n const text = node.children.map(nodeTextValue).join('')\n if (!id || !text) return null\n return { id, depth: Number.parseInt(match[1], 10), text }\n}\n\nexport async function extractToc(markdown: string): Promise<TocItem[]> {\n const headings: TocItem[] = []\n const collectHeadings: Plugin<[], Root> = () => (tree: Root) => {\n visit(tree, 'element', (node: Element) => {\n const item = extractHeadingItem(node)\n if (item) headings.push(item)\n })\n }\n await remark()\n .use(remarkParse)\n .use(remarkGfm)\n .use(remarkRehype)\n .use(rehypeSlug)\n .use(collectHeadings)\n .use(rehypeStringify)\n .process(markdown)\n return headings\n}\n","import type { Metadata, MetadataRoute } from 'next'\nimport {\n getArticleMetadata,\n getAllArticles,\n getAllCategories,\n getArticlesByCategory,\n getAvailableArticleSlugs,\n} from './server-articles'\nimport { ArticlesConfig } from './articlesConfig'\n\nexport function generateArticleStaticParams(): { slug: string }[] {\n return getAvailableArticleSlugs().map((slug) => ({ slug }))\n}\n\nexport async function generateCategoryStaticParams(): Promise<{ category: string }[]> {\n const categories = await getAllCategories()\n return categories.map((cat) => ({ category: cat.slug }))\n}\n\nfunction resolveImageUrl(featuredImage: string, siteUrl: string): string {\n const base = siteUrl.replace(/\\/$/, '')\n if (featuredImage.startsWith('http://') || featuredImage.startsWith('https://')) {\n return featuredImage\n }\n return `${base}/${featuredImage.replace(/^\\/+/, '')}`\n}\n\nexport async function generateArticleMetadata(\n slug: string,\n config: ArticlesConfig\n): Promise<Metadata> {\n const article = await getArticleMetadata(slug)\n\n if (!article) {\n return {\n title: 'Article Not Found',\n description: 'The requested article could not be found.',\n }\n }\n\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n const articleUrl = `${siteUrl}/articles/${slug}`\n const canonicalUrl = article.canonicalUrl ?? articleUrl\n const imageUrl = article.featuredImage\n ? resolveImageUrl(article.featuredImage, siteUrl)\n : `${siteUrl}/placeholder-logo.png`\n const description = article.excerpt ?? `Read ${article.title} on ${config.siteName}.`\n\n return {\n title: `${article.title} | ${config.siteName}`,\n description,\n keywords: [...(article.tags ?? []).map((tag) => tag.toLowerCase())].join(', '),\n openGraph: {\n title: article.title,\n description,\n url: articleUrl,\n siteName: config.siteName,\n images: [{ url: imageUrl, width: 1200, height: 630, alt: article.title }],\n locale: 'en_US',\n type: 'article',\n ...(article.date && { publishedTime: article.date }),\n ...(article.lastmod && { modifiedTime: new Date(article.lastmod).toISOString() }),\n authors: [article.author],\n tags: article.tags ?? [],\n },\n twitter: {\n card: 'summary_large_image',\n title: article.title,\n description,\n images: [imageUrl],\n },\n alternates: {\n canonical: canonicalUrl,\n },\n robots: {\n index: true,\n follow: true,\n googleBot: {\n index: true,\n follow: true,\n 'max-video-preview': -1,\n 'max-image-preview': 'large',\n 'max-snippet': -1,\n },\n },\n other: {\n 'article:author': article.author,\n ...(article.date && {\n 'article:published_time': new Date(article.date).toISOString(),\n }),\n ...(article.lastmod && {\n 'article:modified_time': new Date(article.lastmod).toISOString(),\n }),\n 'article:section': article.category,\n 'article:tag': article.tags?.join(',') ?? '',\n 'linkedin:owner': process.env.NEXT_PUBLIC_LINKEDIN_COMPANY_ID ?? '',\n },\n }\n}\n\nexport function generateArticlesIndexMetadata(config: ArticlesConfig): Metadata {\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n const indexUrl = `${siteUrl}/articles`\n const title = `Articles | ${config.siteName}`\n const description =\n config.hero?.description ?? `Expert analysis and insights from ${config.siteName}.`\n return {\n title,\n description,\n openGraph: {\n title,\n description,\n url: indexUrl,\n siteName: config.siteName,\n type: 'website',\n locale: 'en_US',\n },\n twitter: {\n card: 'summary_large_image',\n title,\n description,\n },\n alternates: {\n canonical: indexUrl,\n types: {\n 'application/rss+xml': `${siteUrl}/articles/feed.xml`,\n },\n },\n robots: {\n index: true,\n follow: true,\n googleBot: {\n index: true,\n follow: true,\n 'max-video-preview': -1,\n 'max-image-preview': 'large',\n 'max-snippet': -1,\n },\n },\n }\n}\n\nexport async function generateCategoryMetadata(\n categorySlug: string,\n config: ArticlesConfig\n): Promise<Metadata> {\n const articles = await getArticlesByCategory(categorySlug)\n\n if (articles.length === 0) return { title: 'Category Not Found' }\n\n const categoryName = articles[0].category\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n const categoryUrl = `${siteUrl}/articles/category/${categorySlug}`\n const raw = config.categoryDescriptions?.[categorySlug]\n const fallback = `Browse ${articles.length} article${articles.length === 1 ? '' : 's'} in the ${categoryName} category.`\n const description = typeof raw === 'string' ? raw : (raw?.short ?? fallback)\n\n const title = `${categoryName} Articles | ${config.siteName}`\n return {\n title,\n description,\n openGraph: {\n title: `${categoryName} Articles`,\n description,\n url: categoryUrl,\n siteName: config.siteName,\n images: [{ url: articles[0].featuredImage }],\n type: 'website',\n locale: 'en_US',\n },\n twitter: {\n card: 'summary_large_image',\n title: `${categoryName} Articles`,\n description,\n },\n alternates: {\n canonical: categoryUrl,\n },\n robots: {\n index: true,\n follow: true,\n googleBot: {\n index: true,\n follow: true,\n 'max-video-preview': -1,\n 'max-image-preview': 'large',\n 'max-snippet': -1,\n },\n },\n }\n}\n\nexport async function getArticleSitemapEntries(\n baseUrlOrConfig: string | ArticlesConfig\n): Promise<MetadataRoute.Sitemap> {\n const baseUrl = (\n typeof baseUrlOrConfig === 'string' ? baseUrlOrConfig : baseUrlOrConfig.siteUrl\n ).replace(/\\/$/, '')\n\n try {\n const [articles, categories] = await Promise.all([getAllArticles(), getAllCategories()])\n\n const articleEntries: MetadataRoute.Sitemap = articles.map((article) => {\n const dateStr = article.lastmod ?? article.date\n const lastModified = dateStr ? new Date(dateStr) : undefined\n return {\n url: `${baseUrl}/articles/${article.slug}`,\n lastModified,\n changeFrequency: 'weekly' as const,\n priority: 0.8,\n }\n })\n\n const categoryEntries: MetadataRoute.Sitemap = categories.map((cat) => ({\n url: `${baseUrl}/articles/category/${cat.slug}`,\n lastModified: new Date(),\n changeFrequency: 'weekly' as const,\n priority: 0.7,\n }))\n\n return [...articleEntries, ...categoryEntries]\n } catch {\n return []\n }\n}\n","import { MDXRemote } from 'next-mdx-remote/rsc'\nimport type { Article } from './articleTypes'\n\ntype ArticleContentProps = Readonly<{ article: Article; className?: string }>\n\nexport function ArticleContent({ article, className }: ArticleContentProps) {\n if (article.contentType === 'mdx' && article.mdxSource) {\n return (\n <div className={className}>\n <MDXRemote source={article.mdxSource} />\n </div>\n )\n }\n return (\n <div className={className} dangerouslySetInnerHTML={{ __html: article.htmlContent || '' }} />\n )\n}\n","import type { TocItem } from './articleTypes'\n\ntype ArticleTOCProps = Readonly<{ toc: TocItem[]; className?: string }>\n\nexport function ArticleTOC({ toc, className }: ArticleTOCProps) {\n if (!toc.length) return null\n return (\n <nav\n aria-label=\"Table of contents\"\n className={`mb-8 rounded-lg border border-border bg-muted/40 px-6 py-4 ${className ?? ''}`}\n >\n <p className=\"mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground\">\n On this page\n </p>\n <ul className=\"space-y-1 text-sm\">\n {toc.map((item) => (\n <li key={item.id} style={{ paddingLeft: `${Math.max(0, item.depth - 2) * 1}rem` }}>\n <a\n href={`#${item.id}`}\n className=\"text-muted-foreground hover:text-foreground transition-colors\"\n >\n {item.text}\n </a>\n </li>\n ))}\n </ul>\n </nav>\n )\n}\n"],"mappings":"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","names":["sanitizeImagePath","remarkParse","remarkGfm","remarkGithubBlockquoteAlert","remarkRehype","rehypeSlug","rehypeAutolinkHeadings","rehypePrism","rehypeSanitize","rehypeStringify","path","readingTime","fs","sanitizeImagePath","matter","import_jsx_runtime"]}
|
|
1
|
+
{"version":3,"sources":["../src/server.ts","../src/server-articles.ts","../src/markdown.ts","../src/seoUtils.ts","../src/renderMdx.tsx","../src/ArticleContent.tsx","../src/ArticleTOC.tsx"],"sourcesContent":["// Server-only exports — uses fs/path; never import this in a client bundle\nexport {\n getAllArticles,\n getArticleMetadata,\n getAvailableArticleSlugs,\n getAdjacentArticles,\n searchArticles,\n getAllCategories,\n getArticlesByCategory,\n categoryToSlug,\n sanitizeImagePath,\n} from './server-articles'\n\nexport {\n generateArticleStaticParams,\n generateCategoryStaticParams,\n generateArticlesIndexMetadata,\n generateArticleMetadata,\n generateCategoryMetadata,\n getArticleSitemapEntries,\n} from './seoUtils'\n\nexport { markdownToHtml, extractToc } from './markdown'\nexport { ArticleContent } from './ArticleContent'\nexport { ArticleTOC } from './ArticleTOC'\n\nexport type { Article, CategoryInfo, TocItem } from './articleTypes'\nexport type { ArticlesConfig } from './articlesConfig'\n","import { cache } from 'react'\nimport matter from 'gray-matter'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport readingTime from 'reading-time'\nimport { markdownToHtml, extractToc } from './markdown'\nimport type { Article, CategoryInfo, FaqItem, HowToStep } from './articleTypes'\n\nconst articlesDirectory = path.join(process.cwd(), 'public/articles')\n\nfunction getReadingTime(content: string): string {\n return readingTime(content).text\n}\n\nfunction findArticleImage(slug: string): string | null {\n try {\n const articleDir = path.join(articlesDirectory, slug)\n const entries: (fs.Dirent | string)[] = fs.readdirSync(articleDir, { withFileTypes: true })\n const names = entries\n .map((entry) => {\n if (typeof entry === 'string') return entry\n if (entry && typeof entry.name === 'string') return entry.name\n return null\n })\n .filter((name): name is string => Boolean(name))\n const imageExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.webp']\n const imageFileName = names.find((name) =>\n imageExtensions.some((ext) => name.toLowerCase().endsWith(ext))\n )\n return imageFileName ? sanitizeImagePath(imageFileName, slug) : null\n } catch {\n return null\n }\n}\n\nfunction sanitizeImagePath(rawPath: string, articleSlug: string): string | null {\n if (!rawPath || typeof rawPath !== 'string') return null\n const cleanPath = rawPath.replaceAll(/[\\x00-\\x1f\\x7f-\\x9f]/g, '')\n if (cleanPath.startsWith('http://') || cleanPath.startsWith('https://')) return cleanPath\n if (cleanPath.includes('..') || cleanPath.includes('\\\\') || cleanPath.startsWith('/')) {\n console.warn(`Potentially unsafe image path detected: ${cleanPath}`)\n return null\n }\n if (!/^[a-zA-Z0-9._/-]+$/.test(cleanPath)) {\n console.warn(`Invalid characters in image path: ${cleanPath}`)\n return null\n }\n if (cleanPath.includes('/')) {\n const normalizedPath = path.normalize(cleanPath)\n if (normalizedPath.startsWith('..') || normalizedPath.includes('../')) {\n console.warn(`Path traversal attempt detected: ${cleanPath}`)\n return null\n }\n return `/articles/${articleSlug}/${cleanPath}`\n }\n return `/articles/${articleSlug}/${cleanPath}`\n}\n\nfunction findArticleFile(slug: string): { filePath: string; contentType: 'md' | 'mdx' } | null {\n const mdPath = path.join(articlesDirectory, slug, 'article.md')\n const mdxPath = path.join(articlesDirectory, slug, 'article.mdx')\n if (fs.existsSync(mdPath)) return { filePath: mdPath, contentType: 'md' }\n if (fs.existsSync(mdxPath)) return { filePath: mdxPath, contentType: 'mdx' }\n return null\n}\n\nexport function getAvailableArticleSlugs(): string[] {\n try {\n if (!fs.existsSync(articlesDirectory)) return []\n return walkArticleDir(articlesDirectory, '')\n } catch (error) {\n console.error('Error reading articles directory:', error)\n return []\n }\n}\n\nfunction walkArticleDir(dir: string, baseSlug: string): string[] {\n const slugs: string[] = []\n try {\n const items = fs.readdirSync(dir, { withFileTypes: true })\n for (const item of items) {\n if (!item.isDirectory()) continue\n const slug = baseSlug ? `${baseSlug}/${item.name}` : item.name\n if (findArticleFile(slug) !== null) {\n slugs.push(slug)\n }\n slugs.push(...walkArticleDir(path.join(dir, item.name), slug))\n }\n } catch {\n // ignore unreadable directories\n }\n return slugs\n}\n\nfunction parseDateField(rawDate: unknown): string | undefined {\n if (!rawDate) return undefined\n try {\n const parsed = new Date(rawDate as string)\n if (!Number.isNaN(parsed.getTime())) return parsed.toISOString().split('T')[0]\n } catch {\n // ignore invalid dates\n }\n return undefined\n}\n\nfunction resolveFeaturedImage(rawImage: unknown, slug: string): string {\n const img = typeof rawImage === 'string' ? rawImage : ''\n if (img && !img.startsWith('http')) return sanitizeImagePath(img, slug) || '/placeholder-logo.png'\n if (!img) return findArticleImage(slug) || '/placeholder-logo.png'\n return img\n}\n\nfunction parseFaqItems(raw: unknown): FaqItem[] | undefined {\n if (!Array.isArray(raw)) return undefined\n const items = raw.filter(\n (item): item is FaqItem =>\n typeof item === 'object' &&\n item !== null &&\n typeof (item as FaqItem).question === 'string' &&\n typeof (item as FaqItem).answer === 'string'\n )\n return items.length ? items : undefined\n}\n\nfunction parseHowToSteps(raw: unknown): HowToStep[] | undefined {\n if (!Array.isArray(raw)) return undefined\n const steps = raw.filter(\n (item): item is HowToStep =>\n typeof item === 'object' &&\n item !== null &&\n typeof (item as HowToStep).name === 'string' &&\n typeof (item as HowToStep).text === 'string'\n )\n return steps.length ? steps : undefined\n}\n\nasync function getArticleSummary(slug: string): Promise<Article | null> {\n try {\n const found = findArticleFile(slug)\n if (!found) return null\n const fileContent = fs.readFileSync(found.filePath, 'utf8')\n const { data, content: markdownContent } = matter(fileContent)\n const readTime = getReadingTime(markdownContent)\n const allTags: string[] = Array.isArray(data.tags)\n ? data.tags.filter((t: unknown) => typeof t === 'string' && String(t).trim())\n : []\n const categories: string[] =\n allTags.length > 0 ? allTags.map((t: string) => t.replaceAll('-', ' ').trim()) : ['Campaigns']\n return {\n slug,\n title: data.title || slug.replaceAll('-', ' '),\n excerpt: data.excerpt || '',\n date: parseDateField(data.date),\n lastmod: parseDateField(data.lastmod),\n author: data.author || 'Andrew Blase',\n category: categories[0],\n categories,\n readTime,\n featuredImage: resolveFeaturedImage(data.featuredImage, slug),\n tags: data.tags || [],\n contentType: found.contentType,\n draft: data.draft === true,\n faq: parseFaqItems(data.faq),\n howTo: parseHowToSteps(data.howTo),\n canonicalUrl: typeof data.canonicalUrl === 'string' ? data.canonicalUrl : undefined,\n articleType: typeof data.articleType === 'string' ? data.articleType : undefined,\n series: typeof data.series === 'string' ? data.series : undefined,\n }\n } catch (error) {\n console.error(`Error loading article ${slug}:`, error)\n return null\n }\n}\n\nexport const getArticleMetadata = cache(async (slug: string): Promise<Article | null> => {\n try {\n const summary = await getArticleSummary(slug)\n if (!summary) return null\n const found = findArticleFile(slug)\n if (!found) return null\n const fileContent = fs.readFileSync(found.filePath, 'utf8')\n const { content: markdownContent } = matter(fileContent)\n const toc = await extractToc(markdownContent)\n let htmlContent: string | undefined\n let mdxSource: string | undefined\n if (found.contentType === 'mdx') {\n mdxSource = markdownContent\n } else {\n htmlContent = await markdownToHtml(markdownContent, slug)\n }\n return { ...summary, content: markdownContent, htmlContent, mdxSource, toc }\n } catch (error) {\n console.error(`Error loading article ${slug}:`, error)\n return null\n }\n})\n\nexport const getAllArticles = cache(async (): Promise<Article[]> => {\n const slugs = getAvailableArticleSlugs()\n const articles = await Promise.all(slugs.map((slug) => getArticleSummary(slug)))\n const currentDate = new Date().toISOString().split('T')[0]\n return articles\n .filter((article): article is Article => article !== null)\n .filter((article) => !article.date || article.date <= currentDate)\n .filter((article) => !(article.draft && process.env.NODE_ENV === 'production'))\n .sort((a, b) => {\n if (!a.date && !b.date) return 0\n if (!a.date) return 1\n if (!b.date) return -1\n return new Date(b.date).getTime() - new Date(a.date).getTime()\n })\n})\n\nexport async function getAdjacentArticles(\n currentSlug: string\n): Promise<{ previous: Article | null; next: Article | null }> {\n const allArticles = await getAllArticles()\n const currentIndex = allArticles.findIndex((article) => article.slug === currentSlug)\n if (currentIndex === -1) return { previous: null, next: null }\n const previous = currentIndex < allArticles.length - 1 ? allArticles[currentIndex + 1] : null\n const next = currentIndex > 0 ? allArticles[currentIndex - 1] : null\n return { previous, next }\n}\n\nexport async function searchArticles(query: string): Promise<Article[]> {\n if (!query?.trim()) return getAllArticles()\n const articles = await getAllArticles()\n const searchTerm = query.toLowerCase().trim()\n return articles.filter((article) => {\n const matchesTitle = article.title.toLowerCase().includes(searchTerm)\n const matchesExcerpt = article.excerpt.toLowerCase().includes(searchTerm)\n const matchesAuthor = article.author.toLowerCase().includes(searchTerm)\n const matchesCategory = article.categories.some((cat) => cat.toLowerCase().includes(searchTerm))\n const matchesTags = article.tags?.some((tag) => tag.toLowerCase().includes(searchTerm))\n return (\n matchesTitle || matchesExcerpt || matchesAuthor || matchesCategory || Boolean(matchesTags)\n )\n })\n}\n\nexport function categoryToSlug(category: string): string {\n return category\n .toLowerCase()\n .replaceAll(/\\s+/g, '-')\n .replaceAll(/[^a-z0-9-]/g, '')\n}\n\nexport async function getAllCategories(): Promise<CategoryInfo[]> {\n const articles = await getAllArticles()\n const categoryMap = new Map<string, { count: number; featuredImage: string }>()\n for (const article of articles) {\n for (const cat of article.categories) {\n if (!categoryMap.has(cat)) {\n categoryMap.set(cat, { count: 0, featuredImage: article.featuredImage })\n }\n categoryMap.get(cat)!.count++\n }\n }\n return Array.from(categoryMap.entries())\n .map(([name, { count, featuredImage }]) => ({\n name,\n slug: categoryToSlug(name),\n count,\n featuredImage,\n }))\n .sort((a, b) => b.count - a.count)\n}\n\nexport async function getArticlesByCategory(categorySlug: string): Promise<Article[]> {\n const articles = await getAllArticles()\n return articles.filter((article) =>\n article.categories.some((cat) => categoryToSlug(cat) === categorySlug)\n )\n}\n\nexport { sanitizeImagePath }\n","import type { Element, Root, ElementContent } from 'hast'\nimport rehypePrism from 'rehype-prism-plus'\nimport rehypeSanitize from 'rehype-sanitize'\nimport rehypeSlug from 'rehype-slug'\nimport rehypeStringify from 'rehype-stringify'\nimport { remark } from 'remark'\nimport remarkGfm from 'remark-gfm'\nimport remarkGithubBlockquoteAlert from 'remark-github-blockquote-alert'\nimport remarkParse from 'remark-parse'\nimport remarkRehype from 'remark-rehype'\nimport { Plugin } from 'unified'\nimport { visit } from 'unist-util-visit'\nimport type { TocItem } from './articleTypes'\n\n// Import the sanitizeImagePath function\nfunction sanitizeImagePath(rawPath: string, articleSlug: string): string | null {\n if (!rawPath || typeof rawPath !== 'string') {\n return null\n }\n\n // Remove any null bytes or control characters\n const cleanPath = rawPath.replaceAll(/[\\x00-\\x1f\\x7f-\\x9f]/g, '')\n\n // Check for absolute URLs (http/https)\n if (cleanPath.startsWith('http://') || cleanPath.startsWith('https://')) {\n // For external URLs, just return as-is (they're safe)\n return cleanPath\n }\n\n // For relative paths, ensure they don't contain dangerous patterns\n if (cleanPath.includes('..') || cleanPath.includes('\\\\') || cleanPath.startsWith('/')) {\n console.warn(`Potentially unsafe image path detected: ${cleanPath}`)\n return null\n }\n\n // Only allow alphanumeric characters, hyphens, underscores, dots, and forward slashes\n if (!/^[a-zA-Z0-9._/-]+$/.test(cleanPath)) {\n console.warn(`Invalid characters in image path: ${cleanPath}`)\n return null\n }\n\n // Construct safe path within articles directory\n if (cleanPath.includes('/')) {\n // Relative path, ensure it's within the article directory\n const normalizedPath = cleanPath.replaceAll('\\\\', '/')\n if (normalizedPath.startsWith('..') || normalizedPath.includes('../')) {\n console.warn(`Path traversal attempt detected: ${cleanPath}`)\n return null\n }\n return `/articles/${articleSlug}/${normalizedPath}`\n } else {\n // Just a filename, construct full path\n return `/articles/${articleSlug}/${cleanPath}`\n }\n}\n\nfunction styleFootnoteLinks(nodes: ElementContent[]): void {\n nodes.forEach((n) => {\n if (n.type !== 'element') return\n const el = n as Element\n if (el.tagName === 'a') {\n const isBackRef =\n el.properties?.['dataFootnoteBackref'] !== undefined ||\n (el.children[0]?.type === 'text' && el.children[0].value === '↩')\n if (isBackRef) {\n el.properties.className = 'text-primary hover:underline ml-1'\n if (typeof el.properties.href === 'string') {\n el.properties.href = el.properties.href.replaceAll('#user-content-fnref-', '#ref-')\n }\n } else {\n el.properties.className = 'text-primary hover:underline break-all'\n }\n }\n if (el.children) styleFootnoteLinks(el.children)\n })\n}\n\nfunction processFootnoteRef(node: Element): void {\n if (\n node.tagName !== 'sup' ||\n node.children?.[0]?.type !== 'element' ||\n (node.children[0] as Element).tagName !== 'a'\n )\n return\n\n const link = node.children[0] as Element\n const href = link.properties?.href\n if (typeof href !== 'string' || !href.startsWith('#user-content-fn-')) return\n\n delete link.properties.target\n delete link.properties.rel\n link.properties.className = 'text-primary hover:underline'\n link.properties.href = href.replaceAll('#user-content-fn-', '#footnote-')\n\n if (typeof node.properties?.id === 'string') {\n link.properties.id = node.properties.id.replaceAll('user-content-fnref-', 'ref-')\n delete node.properties.id\n }\n\n if (link.children?.[0]?.type === 'text') {\n link.children[0].value = `[${link.children[0].value}]`\n }\n}\n\nfunction processFootnotesSection(node: Element): void {\n const cls = node.properties?.className\n const isFootnotes =\n node.tagName === 'section' &&\n (Array.isArray(cls) ? cls.includes('footnotes') : cls === 'footnotes')\n if (!isFootnotes) return\n\n const olCandidate = node.children.find(\n (child) => child.type === 'element' && (child as Element).tagName === 'ol'\n )\n if (olCandidate?.type !== 'element') return\n\n const ol = olCandidate as Element\n ol.properties.className = 'list-decimal ml-6 space-y-2 text-sm text-muted-foreground'\n\n ol.children.forEach((li) => {\n if (li.type !== 'element' || li.tagName !== 'li') return\n const liEl = li as Element\n\n if (liEl.properties) {\n liEl.properties.className = 'pl-2'\n if (typeof liEl.properties.id === 'string') {\n liEl.properties.id = liEl.properties.id.replaceAll('user-content-fn-', 'footnote-')\n }\n }\n\n const pIndex = liEl.children.findIndex(\n (child) => child.type === 'element' && (child as Element).tagName === 'p'\n )\n if (pIndex !== -1) {\n const p = liEl.children[pIndex] as Element\n liEl.children.splice(pIndex, 1, ...p.children)\n }\n\n styleFootnoteLinks(liEl.children)\n })\n\n const hr: Element = {\n type: 'element',\n tagName: 'hr',\n properties: { className: 'my-8 border-border' },\n children: [],\n }\n const h3: Element = {\n type: 'element',\n tagName: 'h3',\n properties: { className: 'text-lg font-semibold mb-4' },\n children: [{ type: 'text', value: 'References' }],\n }\n\n node.children = [hr, h3, ol]\n if (node.properties) node.properties.className = undefined\n}\n\nexport const customRenderer: Plugin<[], Root> = () => {\n return (tree: Root) => {\n // First pass: Apply general styles\n visit(tree, 'element', (node: Element) => {\n if (node.tagName) {\n const props = node.properties || {}\n\n switch (node.tagName) {\n case 'h1':\n props.className = 'text-3xl font-bold text-foreground mt-8 mb-4 scroll-mt-20'\n break\n case 'h2':\n props.className = 'text-2xl font-semibold text-foreground mt-6 mb-3 scroll-mt-20'\n break\n case 'h3':\n props.className = 'text-xl font-semibold text-foreground mt-4 mb-2 scroll-mt-20'\n break\n case 'h4':\n props.className = 'text-lg font-semibold text-foreground mt-3 mb-2 scroll-mt-20'\n break\n case 'p':\n props.className = 'text-muted-foreground leading-relaxed mb-4'\n break\n case 'a': {\n props.className = 'text-primary hover:underline transition-colors duration-200'\n const href = typeof props.href === 'string' ? props.href : ''\n if (!href.startsWith('#')) {\n props.target = '_blank'\n props.rel = 'noopener noreferrer'\n }\n break\n }\n case 'ul':\n props.className = 'list-disc list-inside mb-4 space-y-2 ml-4'\n break\n case 'ol':\n props.className = 'list-decimal list-inside mb-4 space-y-2 ml-4'\n break\n case 'li':\n props.className = 'mb-1'\n break\n case 'blockquote':\n props.className = 'border-l-4 border-primary pl-4 italic my-4 text-muted-foreground'\n break\n case 'code':\n props.className =\n (props.className ? props.className + ' ' : '') +\n 'bg-muted px-1 py-0.5 rounded text-sm font-mono'\n break\n case 'pre':\n props.className = 'bg-muted rounded-lg p-4 overflow-x-auto my-4'\n break\n case 'img':\n props.className = 'rounded-lg my-6 w-full max-w-2xl mx-auto'\n break\n case 'table':\n props.className = 'border-collapse border border-border my-4 w-full'\n break\n case 'th':\n props.className = 'border border-border px-2 py-1 bg-muted font-semibold'\n break\n case 'td':\n props.className = 'border border-border px-2 py-1'\n break\n case 'hr':\n props.className = 'my-8 border-border'\n break\n default:\n break\n }\n\n node.properties = props\n }\n })\n\n // Second pass: Fix footnotes and references\n visit(tree, 'element', (node: Element) => {\n processFootnoteRef(node)\n processFootnotesSection(node)\n })\n }\n}\n\n// Custom rehype plugin to process image URLs\nconst rehypeProcessImages: Plugin<[{ articleSlug?: string }], Root> = (options = {}) => {\n return (tree: Root) => {\n visit(tree, 'element', (node: Element) => {\n if (node.tagName === 'img' && node.properties) {\n const src = node.properties.src\n if (src && typeof src === 'string' && options.articleSlug) {\n // Sanitize the image path\n const sanitizedSrc = sanitizeImagePath(src, options.articleSlug)\n if (sanitizedSrc) {\n node.properties.src = sanitizedSrc\n } else {\n // If sanitization fails, use a placeholder\n console.warn(`Using placeholder for unsafe image path: ${src}`)\n node.properties.src = '/placeholder-logo.png'\n }\n }\n }\n })\n }\n}\n\nexport async function markdownToHtml(markdown: string, articleSlug?: string) {\n try {\n // Start building the remark processor\n let processor = remark()\n .use(remarkParse)\n .use(remarkGfm)\n .use(remarkGithubBlockquoteAlert)\n .use(remarkRehype)\n .use(customRenderer)\n .use(rehypeSlug)\n // @ts-ignore\n .use(rehypePrism)\n .use(rehypeSanitize, {\n attributes: {\n '*': ['className', 'class', 'id'],\n a: ['href', 'target', 'rel', 'id'],\n img: ['src', 'alt'],\n },\n })\n\n // Add image processing plugin if articleSlug is provided\n if (articleSlug) {\n processor = processor.use(rehypeProcessImages, { articleSlug })\n }\n\n const result = await processor.use(rehypeStringify).process(markdown)\n\n return result.toString()\n } catch (error) {\n console.error('Markdown conversion error:', error)\n // Return the original markdown as fallback\n return markdown\n }\n}\n\nfunction nodeTextValue(c: ElementContent): string {\n return c.type === 'text' ? (c as { value: string }).value : ''\n}\n\nfunction extractHeadingItem(node: Element): TocItem | null {\n const match = /^h([1-6])$/.exec(node.tagName)\n if (!match) return null\n const id = typeof node.properties?.id === 'string' ? node.properties.id : ''\n const text = node.children.map(nodeTextValue).join('')\n if (!id || !text) return null\n return { id, depth: Number.parseInt(match[1], 10), text }\n}\n\nexport async function extractToc(markdown: string): Promise<TocItem[]> {\n const headings: TocItem[] = []\n const collectHeadings: Plugin<[], Root> = () => (tree: Root) => {\n visit(tree, 'element', (node: Element) => {\n const item = extractHeadingItem(node)\n if (item) headings.push(item)\n })\n }\n await remark()\n .use(remarkParse)\n .use(remarkGfm)\n .use(remarkRehype)\n .use(rehypeSlug)\n .use(collectHeadings)\n .use(rehypeStringify)\n .process(markdown)\n return headings\n}\n","import type { Metadata, MetadataRoute } from 'next'\nimport {\n getArticleMetadata,\n getAllArticles,\n getAllCategories,\n getArticlesByCategory,\n getAvailableArticleSlugs,\n} from './server-articles'\nimport { ArticlesConfig } from './articlesConfig'\n\nexport function generateArticleStaticParams(): { slug: string }[] {\n return getAvailableArticleSlugs().map((slug) => ({ slug }))\n}\n\nexport async function generateCategoryStaticParams(): Promise<{ category: string }[]> {\n const categories = await getAllCategories()\n return categories.map((cat) => ({ category: cat.slug }))\n}\n\nfunction resolveImageUrl(featuredImage: string, siteUrl: string): string {\n const base = siteUrl.replace(/\\/$/, '')\n if (featuredImage.startsWith('http://') || featuredImage.startsWith('https://')) {\n return featuredImage\n }\n return `${base}/${featuredImage.replace(/^\\/+/, '')}`\n}\n\nexport async function generateArticleMetadata(\n slug: string,\n config: ArticlesConfig\n): Promise<Metadata> {\n const article = await getArticleMetadata(slug)\n\n if (!article) {\n return {\n title: 'Article Not Found',\n description: 'The requested article could not be found.',\n }\n }\n\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n const articleUrl = `${siteUrl}/articles/${slug}`\n const canonicalUrl = article.canonicalUrl ?? articleUrl\n const imageUrl = article.featuredImage\n ? resolveImageUrl(article.featuredImage, siteUrl)\n : `${siteUrl}/placeholder-logo.png`\n const description = article.excerpt ?? `Read ${article.title} on ${config.siteName}.`\n\n return {\n title: `${article.title} | ${config.siteName}`,\n description,\n keywords: [...(article.tags ?? []).map((tag) => tag.toLowerCase())].join(', '),\n openGraph: {\n title: article.title,\n description,\n url: articleUrl,\n siteName: config.siteName,\n images: [{ url: imageUrl, width: 1200, height: 630, alt: article.title }],\n locale: 'en_US',\n type: 'article',\n ...(article.date && { publishedTime: article.date }),\n ...(article.lastmod && { modifiedTime: new Date(article.lastmod).toISOString() }),\n authors: [article.author],\n tags: article.tags ?? [],\n },\n twitter: {\n card: 'summary_large_image',\n title: article.title,\n description,\n images: [imageUrl],\n },\n alternates: {\n canonical: canonicalUrl,\n },\n robots: {\n index: true,\n follow: true,\n googleBot: {\n index: true,\n follow: true,\n 'max-video-preview': -1,\n 'max-image-preview': 'large',\n 'max-snippet': -1,\n },\n },\n other: {\n 'article:author': article.author,\n ...(article.date && {\n 'article:published_time': new Date(article.date).toISOString(),\n }),\n ...(article.lastmod && {\n 'article:modified_time': new Date(article.lastmod).toISOString(),\n }),\n 'article:section': article.category,\n 'article:tag': article.tags?.join(',') ?? '',\n 'linkedin:owner': process.env.NEXT_PUBLIC_LINKEDIN_COMPANY_ID ?? '',\n },\n }\n}\n\nexport function generateArticlesIndexMetadata(config: ArticlesConfig): Metadata {\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n const indexUrl = `${siteUrl}/articles`\n const title = `Articles | ${config.siteName}`\n const description =\n config.hero?.description ?? `Expert analysis and insights from ${config.siteName}.`\n return {\n title,\n description,\n openGraph: {\n title,\n description,\n url: indexUrl,\n siteName: config.siteName,\n type: 'website',\n locale: 'en_US',\n },\n twitter: {\n card: 'summary_large_image',\n title,\n description,\n },\n alternates: {\n canonical: indexUrl,\n types: {\n 'application/rss+xml': `${siteUrl}/articles/feed.xml`,\n },\n },\n robots: {\n index: true,\n follow: true,\n googleBot: {\n index: true,\n follow: true,\n 'max-video-preview': -1,\n 'max-image-preview': 'large',\n 'max-snippet': -1,\n },\n },\n }\n}\n\nexport async function generateCategoryMetadata(\n categorySlug: string,\n config: ArticlesConfig\n): Promise<Metadata> {\n const articles = await getArticlesByCategory(categorySlug)\n\n if (articles.length === 0) return { title: 'Category Not Found' }\n\n const categoryName = articles[0].category\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n const categoryUrl = `${siteUrl}/articles/category/${categorySlug}`\n const raw = config.categoryDescriptions?.[categorySlug]\n const fallback = `Browse ${articles.length} article${articles.length === 1 ? '' : 's'} in the ${categoryName} category.`\n const description = typeof raw === 'string' ? raw : (raw?.short ?? fallback)\n\n const title = `${categoryName} Articles | ${config.siteName}`\n return {\n title,\n description,\n openGraph: {\n title: `${categoryName} Articles`,\n description,\n url: categoryUrl,\n siteName: config.siteName,\n images: [{ url: articles[0].featuredImage }],\n type: 'website',\n locale: 'en_US',\n },\n twitter: {\n card: 'summary_large_image',\n title: `${categoryName} Articles`,\n description,\n },\n alternates: {\n canonical: categoryUrl,\n },\n robots: {\n index: true,\n follow: true,\n googleBot: {\n index: true,\n follow: true,\n 'max-video-preview': -1,\n 'max-image-preview': 'large',\n 'max-snippet': -1,\n },\n },\n }\n}\n\nexport async function getArticleSitemapEntries(\n baseUrlOrConfig: string | ArticlesConfig\n): Promise<MetadataRoute.Sitemap> {\n const baseUrl = (\n typeof baseUrlOrConfig === 'string' ? baseUrlOrConfig : baseUrlOrConfig.siteUrl\n ).replace(/\\/$/, '')\n\n try {\n const [articles, categories] = await Promise.all([getAllArticles(), getAllCategories()])\n\n const articleEntries: MetadataRoute.Sitemap = articles.map((article) => {\n const dateStr = article.lastmod ?? article.date\n const lastModified = dateStr ? new Date(dateStr) : undefined\n return {\n url: `${baseUrl}/articles/${article.slug}`,\n lastModified,\n changeFrequency: 'weekly' as const,\n priority: 0.8,\n }\n })\n\n const categoryEntries: MetadataRoute.Sitemap = categories.map((cat) => ({\n url: `${baseUrl}/articles/category/${cat.slug}`,\n lastModified: new Date(),\n changeFrequency: 'weekly' as const,\n priority: 0.7,\n }))\n\n return [...articleEntries, ...categoryEntries]\n } catch {\n return []\n }\n}\n","// MDX files must use JSX prop syntax:\n// Correct: <span style={{ color: '#3b82f6' }}>Text</span>\n// Incorrect: <span style=\"color: #3b82f6\">Text</span>\n// Correct: className=\"...\"\n// Incorrect: class=\"...\"\nimport React from 'react'\nimport type { ComponentType, ImgHTMLAttributes } from 'react'\nimport * as devRuntime from 'react/jsx-dev-runtime'\nimport * as runtime from 'react/jsx-runtime'\nimport { evaluate } from '@mdx-js/mdx'\nimport rehypePrism from 'rehype-prism-plus'\nimport rehypeSlug from 'rehype-slug'\nimport remarkGfm from 'remark-gfm'\nimport remarkGithubBlockquoteAlert from 'remark-github-blockquote-alert'\nimport { customRenderer } from './markdown'\n\ntype MdxContent = ComponentType<{\n components?: Record<string, ComponentType<unknown>>\n}>\n\nfunction makeImgComponent(basePath: string) {\n return function MdxImage({ src, alt, ...props }: ImgHTMLAttributes<HTMLImageElement>) {\n const resolvedSrc =\n src && !src.startsWith('http') && !src.startsWith('/') ? `${basePath}/${src}` : src\n return React.createElement('img', { src: resolvedSrc, alt, ...props })\n }\n}\n\nexport async function renderMdxSource(source: string, basePath?: string) {\n const isDevelopment = process.env.NODE_ENV === 'development'\n\n const mdxModule = await evaluate(source, {\n ...(isDevelopment ? devRuntime : runtime),\n development: isDevelopment,\n remarkPlugins: [remarkGfm, remarkGithubBlockquoteAlert],\n rehypePlugins: [\n customRenderer,\n rehypeSlug,\n // @ts-ignore\n rehypePrism,\n ],\n })\n\n const Content = mdxModule.default as MdxContent\n const components = basePath ? { img: makeImgComponent(basePath) } : undefined\n return <Content components={components as Record<string, ComponentType<unknown>>} />\n}\n","import { renderMdxSource } from './renderMdx'\nimport type { Article } from './articleTypes'\n\ntype ArticleContentProps = Readonly<{ article: Article; className?: string }>\n\nexport async function ArticleContent({ article, className }: ArticleContentProps) {\n if (article.contentType === 'mdx' && article.mdxSource) {\n const content = await renderMdxSource(article.mdxSource, `/articles/${article.slug}`)\n return <div className={className}>{content}</div>\n }\n return (\n <div className={className} dangerouslySetInnerHTML={{ __html: article.htmlContent || '' }} />\n )\n}\n","import type { TocItem } from './articleTypes'\n\ntype ArticleTOCProps = Readonly<{ toc: TocItem[]; className?: string }>\n\nexport function ArticleTOC({ toc, className }: ArticleTOCProps) {\n if (!toc.length) return null\n return (\n <nav\n aria-label=\"Table of contents\"\n className={`mb-8 rounded-lg border border-border bg-muted/40 px-6 py-4 ${className ?? ''}`}\n >\n <p className=\"mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground\">\n On this page\n </p>\n <ul className=\"space-y-1 text-sm\">\n {toc.map((item) => (\n <li key={item.id} style={{ paddingLeft: `${Math.max(0, item.depth - 2) * 1}rem` }}>\n <a\n href={`#${item.id}`}\n className=\"text-muted-foreground hover:text-foreground transition-colors\"\n >\n {item.text}\n </a>\n </li>\n ))}\n </ul>\n </nav>\n )\n}\n"],"mappings":"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","names":["sanitizeImagePath","remarkParse","remarkGfm","remarkGithubBlockquoteAlert","remarkRehype","rehypeSlug","rehypePrism","rehypeSanitize","rehypeStringify","path","readingTime","fs","sanitizeImagePath","matter","import_react","import_rehype_prism_plus","import_rehype_slug","import_remark_gfm","import_remark_github_blockquote_alert","React","remarkGfm","remarkGithubBlockquoteAlert","rehypeSlug","rehypePrism","import_jsx_runtime","import_jsx_runtime"]}
|
package/dist/server.d.cts
CHANGED
|
@@ -147,6 +147,8 @@ interface ArticlesConfig {
|
|
|
147
147
|
description?: string;
|
|
148
148
|
/** Set to false to hide the table of contents on article detail pages. Default: true. */
|
|
149
149
|
showToc?: boolean;
|
|
150
|
+
/** Set to false to hide the "Back to Articles" navigation link on article detail pages. Default: true. */
|
|
151
|
+
showBackToArticles?: boolean;
|
|
150
152
|
}
|
|
151
153
|
|
|
152
154
|
declare function generateArticleStaticParams(): {
|
|
@@ -167,7 +169,7 @@ type ArticleContentProps = Readonly<{
|
|
|
167
169
|
article: Article;
|
|
168
170
|
className?: string;
|
|
169
171
|
}>;
|
|
170
|
-
declare function ArticleContent({ article, className }: ArticleContentProps): react_jsx_runtime.JSX.Element
|
|
172
|
+
declare function ArticleContent({ article, className }: ArticleContentProps): Promise<react_jsx_runtime.JSX.Element>;
|
|
171
173
|
|
|
172
174
|
type ArticleTOCProps = Readonly<{
|
|
173
175
|
toc: TocItem[];
|
package/dist/server.d.ts
CHANGED
|
@@ -147,6 +147,8 @@ interface ArticlesConfig {
|
|
|
147
147
|
description?: string;
|
|
148
148
|
/** Set to false to hide the table of contents on article detail pages. Default: true. */
|
|
149
149
|
showToc?: boolean;
|
|
150
|
+
/** Set to false to hide the "Back to Articles" navigation link on article detail pages. Default: true. */
|
|
151
|
+
showBackToArticles?: boolean;
|
|
150
152
|
}
|
|
151
153
|
|
|
152
154
|
declare function generateArticleStaticParams(): {
|
|
@@ -167,7 +169,7 @@ type ArticleContentProps = Readonly<{
|
|
|
167
169
|
article: Article;
|
|
168
170
|
className?: string;
|
|
169
171
|
}>;
|
|
170
|
-
declare function ArticleContent({ article, className }: ArticleContentProps): react_jsx_runtime.JSX.Element
|
|
172
|
+
declare function ArticleContent({ article, className }: ArticleContentProps): Promise<react_jsx_runtime.JSX.Element>;
|
|
171
173
|
|
|
172
174
|
type ArticleTOCProps = Readonly<{
|
|
173
175
|
toc: TocItem[];
|
package/dist/server.js
CHANGED
|
@@ -17,6 +17,18 @@ var __spreadValues = (a, b) => {
|
|
|
17
17
|
return a;
|
|
18
18
|
};
|
|
19
19
|
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
20
|
+
var __objRest = (source, exclude) => {
|
|
21
|
+
var target = {};
|
|
22
|
+
for (var prop in source)
|
|
23
|
+
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
|
|
24
|
+
target[prop] = source[prop];
|
|
25
|
+
if (source != null && __getOwnPropSymbols)
|
|
26
|
+
for (var prop of __getOwnPropSymbols(source)) {
|
|
27
|
+
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
|
|
28
|
+
target[prop] = source[prop];
|
|
29
|
+
}
|
|
30
|
+
return target;
|
|
31
|
+
};
|
|
20
32
|
var __async = (__this, __arguments, generator) => {
|
|
21
33
|
return new Promise((resolve, reject) => {
|
|
22
34
|
var fulfilled = (value) => {
|
|
@@ -46,7 +58,6 @@ import path from "node:path";
|
|
|
46
58
|
import readingTime from "reading-time";
|
|
47
59
|
|
|
48
60
|
// src/markdown.ts
|
|
49
|
-
import rehypeAutolinkHeadings from "rehype-autolink-headings";
|
|
50
61
|
import rehypePrism from "rehype-prism-plus";
|
|
51
62
|
import rehypeSanitize from "rehype-sanitize";
|
|
52
63
|
import rehypeSlug from "rehype-slug";
|
|
@@ -187,11 +198,15 @@ var customRenderer = () => {
|
|
|
187
198
|
case "p":
|
|
188
199
|
props.className = "text-muted-foreground leading-relaxed mb-4";
|
|
189
200
|
break;
|
|
190
|
-
case "a":
|
|
201
|
+
case "a": {
|
|
191
202
|
props.className = "text-primary hover:underline transition-colors duration-200";
|
|
192
|
-
props.
|
|
193
|
-
|
|
203
|
+
const href = typeof props.href === "string" ? props.href : "";
|
|
204
|
+
if (!href.startsWith("#")) {
|
|
205
|
+
props.target = "_blank";
|
|
206
|
+
props.rel = "noopener noreferrer";
|
|
207
|
+
}
|
|
194
208
|
break;
|
|
209
|
+
}
|
|
195
210
|
case "ul":
|
|
196
211
|
props.className = "list-disc list-inside mb-4 space-y-2 ml-4";
|
|
197
212
|
break;
|
|
@@ -258,7 +273,7 @@ var rehypeProcessImages = (options = {}) => {
|
|
|
258
273
|
function markdownToHtml(markdown, articleSlug) {
|
|
259
274
|
return __async(this, null, function* () {
|
|
260
275
|
try {
|
|
261
|
-
let processor = remark().use(remarkParse).use(remarkGfm).use(remarkGithubBlockquoteAlert).use(remarkRehype).use(customRenderer).use(rehypeSlug).use(
|
|
276
|
+
let processor = remark().use(remarkParse).use(remarkGfm).use(remarkGithubBlockquoteAlert).use(remarkRehype).use(customRenderer).use(rehypeSlug).use(rehypePrism).use(rehypeSanitize, {
|
|
262
277
|
attributes: {
|
|
263
278
|
"*": ["className", "class", "id"],
|
|
264
279
|
a: ["href", "target", "rel", "id"],
|
|
@@ -372,9 +387,8 @@ function walkArticleDir(dir, baseSlug) {
|
|
|
372
387
|
const slug = baseSlug ? `${baseSlug}/${item.name}` : item.name;
|
|
373
388
|
if (findArticleFile(slug) !== null) {
|
|
374
389
|
slugs.push(slug);
|
|
375
|
-
} else {
|
|
376
|
-
slugs.push(...walkArticleDir(path.join(dir, item.name), slug));
|
|
377
390
|
}
|
|
391
|
+
slugs.push(...walkArticleDir(path.join(dir, item.name), slug));
|
|
378
392
|
}
|
|
379
393
|
} catch (e) {
|
|
380
394
|
}
|
|
@@ -734,18 +748,56 @@ function getArticleSitemapEntries(baseUrlOrConfig) {
|
|
|
734
748
|
});
|
|
735
749
|
}
|
|
736
750
|
|
|
737
|
-
// src/
|
|
738
|
-
import
|
|
751
|
+
// src/renderMdx.tsx
|
|
752
|
+
import React from "react";
|
|
753
|
+
import * as devRuntime from "react/jsx-dev-runtime";
|
|
754
|
+
import * as runtime from "react/jsx-runtime";
|
|
755
|
+
import { evaluate } from "@mdx-js/mdx";
|
|
756
|
+
import rehypePrism2 from "rehype-prism-plus";
|
|
757
|
+
import rehypeSlug2 from "rehype-slug";
|
|
758
|
+
import remarkGfm2 from "remark-gfm";
|
|
759
|
+
import remarkGithubBlockquoteAlert2 from "remark-github-blockquote-alert";
|
|
739
760
|
import { jsx } from "react/jsx-runtime";
|
|
740
|
-
function
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
761
|
+
function makeImgComponent(basePath) {
|
|
762
|
+
return function MdxImage(_a) {
|
|
763
|
+
var _b = _a, { src, alt } = _b, props = __objRest(_b, ["src", "alt"]);
|
|
764
|
+
const resolvedSrc = src && !src.startsWith("http") && !src.startsWith("/") ? `${basePath}/${src}` : src;
|
|
765
|
+
return React.createElement("img", __spreadValues({ src: resolvedSrc, alt }, props));
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
function renderMdxSource(source, basePath) {
|
|
769
|
+
return __async(this, null, function* () {
|
|
770
|
+
const isDevelopment = process.env.NODE_ENV === "development";
|
|
771
|
+
const mdxModule = yield evaluate(source, __spreadProps(__spreadValues({}, isDevelopment ? devRuntime : runtime), {
|
|
772
|
+
development: isDevelopment,
|
|
773
|
+
remarkPlugins: [remarkGfm2, remarkGithubBlockquoteAlert2],
|
|
774
|
+
rehypePlugins: [
|
|
775
|
+
customRenderer,
|
|
776
|
+
rehypeSlug2,
|
|
777
|
+
// @ts-ignore
|
|
778
|
+
rehypePrism2
|
|
779
|
+
]
|
|
780
|
+
}));
|
|
781
|
+
const Content = mdxModule.default;
|
|
782
|
+
const components = basePath ? { img: makeImgComponent(basePath) } : void 0;
|
|
783
|
+
return /* @__PURE__ */ jsx(Content, { components });
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// src/ArticleContent.tsx
|
|
788
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
789
|
+
function ArticleContent(_0) {
|
|
790
|
+
return __async(this, arguments, function* ({ article, className }) {
|
|
791
|
+
if (article.contentType === "mdx" && article.mdxSource) {
|
|
792
|
+
const content = yield renderMdxSource(article.mdxSource, `/articles/${article.slug}`);
|
|
793
|
+
return /* @__PURE__ */ jsx2("div", { className, children: content });
|
|
794
|
+
}
|
|
795
|
+
return /* @__PURE__ */ jsx2("div", { className, dangerouslySetInnerHTML: { __html: article.htmlContent || "" } });
|
|
796
|
+
});
|
|
745
797
|
}
|
|
746
798
|
|
|
747
799
|
// src/ArticleTOC.tsx
|
|
748
|
-
import { jsx as
|
|
800
|
+
import { jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
749
801
|
function ArticleTOC({ toc, className }) {
|
|
750
802
|
if (!toc.length) return null;
|
|
751
803
|
return /* @__PURE__ */ jsxs(
|
|
@@ -754,8 +806,8 @@ function ArticleTOC({ toc, className }) {
|
|
|
754
806
|
"aria-label": "Table of contents",
|
|
755
807
|
className: `mb-8 rounded-lg border border-border bg-muted/40 px-6 py-4 ${className != null ? className : ""}`,
|
|
756
808
|
children: [
|
|
757
|
-
/* @__PURE__ */
|
|
758
|
-
/* @__PURE__ */
|
|
809
|
+
/* @__PURE__ */ jsx3("p", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: "On this page" }),
|
|
810
|
+
/* @__PURE__ */ jsx3("ul", { className: "space-y-1 text-sm", children: toc.map((item) => /* @__PURE__ */ jsx3("li", { style: { paddingLeft: `${Math.max(0, item.depth - 2) * 1}rem` }, children: /* @__PURE__ */ jsx3(
|
|
759
811
|
"a",
|
|
760
812
|
{
|
|
761
813
|
href: `#${item.id}`,
|