reallysimpledocs 1.0.0-beta.2 → 1.0.0-beta.4
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/CHANGELOG.md +35 -0
- package/README.md +11 -11
- package/package.json +10 -2
- package/src/astro/index.js +94 -3
- package/src/components/index.js +0 -2
- package/src/css/custom.css +45 -205
- package/src/runtime/components/CommandDialog.astro +20 -3
- package/src/runtime/components/DefaultContentHeader.astro +19 -0
- package/src/runtime/components/DefaultSidebarFooter.astro +2 -0
- package/src/runtime/components/DocsLayout.astro +100 -81
- package/src/runtime/components/Sidebar.astro +4 -0
- package/src/runtime/components/mdx/Callout.astro +36 -26
- package/src/runtime/components/mdx/Card.astro +8 -7
- package/src/runtime/components/mdx/CardGrid.astro +1 -1
- package/src/runtime/components/mdx/Code.astro +33 -13
- package/src/runtime/components/mdx/CodeGroup.astro +38 -14
- package/src/runtime/components/mdx/Preview.astro +16 -6
- package/src/runtime/components/mdx/Steps.astro +5 -1
- package/src/runtime/components/mdx/Tabs.astro +1 -1
- package/src/runtime/lib/docs.js +107 -36
- package/src/runtime/lib/highlight.js +27 -0
- package/src/runtime/lib/macros.js +5 -1
- package/src/runtime/lib/markdown-export.js +200 -0
- package/src/runtime/lib/markdown-exporters/index.js +144 -0
- package/src/runtime/lib/mdx-source.js +19 -0
- package/src/runtime/lib/preview-source-plugin.js +33 -0
- package/src/runtime/lib/site.js +0 -1
- package/src/runtime/pages/doc.astro +4 -1
- package/src/runtime/pages/llms-full.txt.ts +2 -2
- package/src/runtime/pages/llms.txt.ts +2 -2
- package/src/runtime/pages/markdown.ts +2 -2
- package/src/runtime/pages/search-index.ts +2 -2
- package/src/runtime/scripts/copy-code.js +51 -6
- package/src/site/ContentHeader.astro +33 -0
- package/src/runtime/components/mdx/Badge.astro +0 -15
- package/src/runtime/components/mdx/LinkButton.astro +0 -14
package/src/runtime/lib/docs.js
CHANGED
|
@@ -2,8 +2,9 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { marked } from "marked";
|
|
4
4
|
import GithubSlugger from "github-slugger";
|
|
5
|
-
import { codeToHtml } from "shiki";
|
|
6
5
|
import * as lucideIcons from "lucide-static";
|
|
6
|
+
import { highlightCode as renderHighlightedCode } from "./highlight.js";
|
|
7
|
+
import { mdxToMarkdown } from "./markdown-export.js";
|
|
7
8
|
import { markdownPath, routePath } from "./paths.js";
|
|
8
9
|
|
|
9
10
|
export function getManifest(config) {
|
|
@@ -82,8 +83,14 @@ export function getDocMarkdown(config, page) {
|
|
|
82
83
|
return fs.readFileSync(filePath, "utf8");
|
|
83
84
|
}
|
|
84
85
|
|
|
85
|
-
export function getMarkdownExport(config, page) {
|
|
86
|
-
|
|
86
|
+
export async function getMarkdownExport(config, page) {
|
|
87
|
+
const source = getDocMarkdown(config, page);
|
|
88
|
+
if (page.extension === ".mdx") return await mdxToMarkdown(source, config);
|
|
89
|
+
return `${source.trim()}\n`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export async function getMarkdownBodyExport(config, page) {
|
|
93
|
+
return parseDocMarkdown(await getMarkdownExport(config, page), page.title).body.trim();
|
|
87
94
|
}
|
|
88
95
|
|
|
89
96
|
export async function renderDoc(config, page) {
|
|
@@ -117,7 +124,7 @@ export async function renderDoc(config, page) {
|
|
|
117
124
|
};
|
|
118
125
|
|
|
119
126
|
const tokens = marked.lexer(content);
|
|
120
|
-
await
|
|
127
|
+
await highlightMarkdownCode(tokens);
|
|
121
128
|
const html = marked.parser(tokens, { renderer });
|
|
122
129
|
return { page, html, headings };
|
|
123
130
|
}
|
|
@@ -152,6 +159,46 @@ export function getNavigation(config, page) {
|
|
|
152
159
|
};
|
|
153
160
|
}
|
|
154
161
|
|
|
162
|
+
export function getBreadcrumbs(config, page) {
|
|
163
|
+
const pages = getPages(config);
|
|
164
|
+
const bySlug = new Map(pages.map((entry) => [entry.slug, entry]));
|
|
165
|
+
const trail = [];
|
|
166
|
+
|
|
167
|
+
const walk = (items = [], ancestors = []) => {
|
|
168
|
+
for (const item of items) {
|
|
169
|
+
if (item?.type === "group") {
|
|
170
|
+
const result = walk(item.items || [], ancestors);
|
|
171
|
+
if (result) return result;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (item?.type === "submenu") {
|
|
176
|
+
const result = walk(item.items || [], item.label ? [...ancestors, { label: item.label }] : ancestors);
|
|
177
|
+
if (result) return result;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const slug = typeof item === "string" ? item : getPageItemSlug(item);
|
|
182
|
+
if (slug !== page.slug) continue;
|
|
183
|
+
const matchedPage = bySlug.get(slug);
|
|
184
|
+
if (!matchedPage) return ancestors;
|
|
185
|
+
return [
|
|
186
|
+
...ancestors,
|
|
187
|
+
{
|
|
188
|
+
label: item.label || matchedPage.title,
|
|
189
|
+
url: matchedPage.path,
|
|
190
|
+
},
|
|
191
|
+
];
|
|
192
|
+
}
|
|
193
|
+
return null;
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const manifest = getManifest(config);
|
|
197
|
+
const found = walk(manifest.menu || []);
|
|
198
|
+
if (found) trail.push(...found);
|
|
199
|
+
return trail;
|
|
200
|
+
}
|
|
201
|
+
|
|
155
202
|
export function resolveIcon(icon) {
|
|
156
203
|
if (!icon || typeof icon !== "string") return "";
|
|
157
204
|
const trimmed = icon.trim();
|
|
@@ -164,7 +211,9 @@ export function resolveIcon(icon) {
|
|
|
164
211
|
.join("")
|
|
165
212
|
: trimmed.charAt(0).toUpperCase() + trimmed.slice(1);
|
|
166
213
|
const iconClass = trimmed.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
167
|
-
|
|
214
|
+
const svg = lucideIcons[pascalName] || "";
|
|
215
|
+
if (!svg || svg.includes(`lucide-${iconClass}`)) return svg;
|
|
216
|
+
return svg.replace('class="lucide', `class="lucide lucide-${iconClass}`);
|
|
168
217
|
}
|
|
169
218
|
|
|
170
219
|
export function buildMenus(config, currentSlug) {
|
|
@@ -185,10 +234,30 @@ export function buildMenus(config, currentSlug) {
|
|
|
185
234
|
};
|
|
186
235
|
};
|
|
187
236
|
|
|
237
|
+
const processExternalLink = (item) => {
|
|
238
|
+
if (!item?.url || !item?.label) return null;
|
|
239
|
+
const external = isExternalUrl(item.url);
|
|
240
|
+
const attrs = {
|
|
241
|
+
...item.attrs,
|
|
242
|
+
target: external ? item.attrs?.target || "_blank" : item.attrs?.target,
|
|
243
|
+
rel: external ? item.attrs?.rel || "noopener" : item.attrs?.rel,
|
|
244
|
+
};
|
|
245
|
+
return {
|
|
246
|
+
type: "item",
|
|
247
|
+
icon: resolveIcon(item.icon),
|
|
248
|
+
url: item.url,
|
|
249
|
+
label: item.label,
|
|
250
|
+
external,
|
|
251
|
+
attrs,
|
|
252
|
+
keywords: item.label,
|
|
253
|
+
};
|
|
254
|
+
};
|
|
255
|
+
|
|
188
256
|
const processItem = (item) => {
|
|
189
257
|
if (typeof item === "string") return processSlug(item);
|
|
190
258
|
const slug = getPageItemSlug(item);
|
|
191
259
|
if (slug) return processSlug(slug, item);
|
|
260
|
+
if (item?.url) return processExternalLink(item);
|
|
192
261
|
if (item?.type === "submenu") {
|
|
193
262
|
const items = (item.items || []).map(processItem).filter(Boolean);
|
|
194
263
|
return {
|
|
@@ -218,6 +287,8 @@ export function buildMenus(config, currentSlug) {
|
|
|
218
287
|
icon: item.icon,
|
|
219
288
|
url: item.url,
|
|
220
289
|
label: item.label,
|
|
290
|
+
external: item.external,
|
|
291
|
+
attrs: item.attrs,
|
|
221
292
|
keywords: item.keywords,
|
|
222
293
|
})),
|
|
223
294
|
}));
|
|
@@ -225,23 +296,28 @@ export function buildMenus(config, currentSlug) {
|
|
|
225
296
|
return { sidebar, command };
|
|
226
297
|
}
|
|
227
298
|
|
|
228
|
-
export function getLlmDocs(config) {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
299
|
+
export async function getLlmDocs(config) {
|
|
300
|
+
const docs = [];
|
|
301
|
+
for (const page of getPages(config)) {
|
|
302
|
+
docs.push({
|
|
303
|
+
...page,
|
|
304
|
+
content: await getMarkdownBodyExport(config, page),
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
return docs;
|
|
233
308
|
}
|
|
234
309
|
|
|
235
|
-
export function getSearchDocs(config) {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
310
|
+
export async function getSearchDocs(config) {
|
|
311
|
+
const docs = [];
|
|
312
|
+
for (const page of getPages(config)) {
|
|
313
|
+
docs.push({
|
|
239
314
|
slug: page.slug,
|
|
240
315
|
title: page.title,
|
|
241
316
|
path: page.path,
|
|
242
|
-
body: markdownPlainText(
|
|
243
|
-
};
|
|
244
|
-
}
|
|
317
|
+
body: markdownPlainText(await getMarkdownBodyExport(config, page)),
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
return docs;
|
|
245
321
|
}
|
|
246
322
|
|
|
247
323
|
function flattenSidebarItems(items = []) {
|
|
@@ -253,6 +329,10 @@ export function getPageItemSlug(item) {
|
|
|
253
329
|
return item.slug || item.page || "";
|
|
254
330
|
}
|
|
255
331
|
|
|
332
|
+
function isExternalUrl(url) {
|
|
333
|
+
return /^[a-z][a-z0-9+.-]*:/i.test(String(url || ""));
|
|
334
|
+
}
|
|
335
|
+
|
|
256
336
|
function parseDocMarkdown(source, fallbackTitle) {
|
|
257
337
|
const content = normalizeDocSource(source);
|
|
258
338
|
const tokens = marked.lexer(content);
|
|
@@ -282,11 +362,11 @@ function normalizeDocSource(source) {
|
|
|
282
362
|
const firstHeading = withoutFrontmatter.search(/^#\s+/m);
|
|
283
363
|
if (firstHeading > 0) return withoutFrontmatter.slice(firstHeading).trimStart();
|
|
284
364
|
return withoutFrontmatter
|
|
285
|
-
.replace(/^(\
|
|
365
|
+
.replace(/^(?:[ \t]*(?:import|export)\s+[^\n]*\n)+/, "")
|
|
286
366
|
.trimStart();
|
|
287
367
|
}
|
|
288
368
|
|
|
289
|
-
async function
|
|
369
|
+
async function highlightMarkdownCode(tokens) {
|
|
290
370
|
for (const token of tokens) {
|
|
291
371
|
if (token.type === "code") {
|
|
292
372
|
const html = await highlightToken(token);
|
|
@@ -300,33 +380,24 @@ async function highlightCode(tokens) {
|
|
|
300
380
|
}
|
|
301
381
|
|
|
302
382
|
for (const childTokens of nestedTokenLists(token)) {
|
|
303
|
-
await
|
|
383
|
+
await highlightMarkdownCode(childTokens);
|
|
304
384
|
}
|
|
305
385
|
}
|
|
306
386
|
}
|
|
307
387
|
|
|
308
388
|
async function highlightToken(token) {
|
|
389
|
+
const lang = normalizeLanguage(token.lang);
|
|
309
390
|
try {
|
|
310
|
-
return await
|
|
311
|
-
lang: normalizeLanguage(token.lang),
|
|
312
|
-
themes: {
|
|
313
|
-
light: "github-light",
|
|
314
|
-
dark: "github-dark",
|
|
315
|
-
},
|
|
316
|
-
defaultColor: false,
|
|
317
|
-
});
|
|
391
|
+
return wrapCodeBlock(await renderHighlightedCode(token.text, lang));
|
|
318
392
|
} catch {
|
|
319
|
-
return
|
|
320
|
-
lang: "text",
|
|
321
|
-
themes: {
|
|
322
|
-
light: "github-light",
|
|
323
|
-
dark: "github-dark",
|
|
324
|
-
},
|
|
325
|
-
defaultColor: false,
|
|
326
|
-
});
|
|
393
|
+
return wrapCodeBlock(await renderHighlightedCode(token.text, "text"));
|
|
327
394
|
}
|
|
328
395
|
}
|
|
329
396
|
|
|
397
|
+
function wrapCodeBlock(html) {
|
|
398
|
+
return `<div class="relative my-6 w-full max-w-full overflow-hidden rounded-lg" data-code-block>${html}</div>`;
|
|
399
|
+
}
|
|
400
|
+
|
|
330
401
|
function normalizeLanguage(lang) {
|
|
331
402
|
const value = lang?.trim().split(/\s+/)[0].toLowerCase() || "text";
|
|
332
403
|
if (value === "njk") return "html";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { codeToHtml } from "shiki";
|
|
2
|
+
|
|
3
|
+
export async function highlightCode(code, lang = "text") {
|
|
4
|
+
const value = String(code || "");
|
|
5
|
+
const language = String(lang || "text").toLowerCase() === "mdx" ? "tsx" : String(lang || "text");
|
|
6
|
+
const html = await codeToHtml(value, {
|
|
7
|
+
lang: language,
|
|
8
|
+
themes: {
|
|
9
|
+
light: "github-light",
|
|
10
|
+
dark: "github-dark",
|
|
11
|
+
},
|
|
12
|
+
defaultColor: false,
|
|
13
|
+
});
|
|
14
|
+
return addCodeLayoutClasses(html);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function addCodeLayoutClasses(html) {
|
|
18
|
+
return String(html)
|
|
19
|
+
.replace(
|
|
20
|
+
/<pre class="/,
|
|
21
|
+
'<pre class="!my-0 max-w-full overflow-x-auto !rounded-none !border-0 ',
|
|
22
|
+
)
|
|
23
|
+
.replace(
|
|
24
|
+
/<code>/,
|
|
25
|
+
'<code class="block min-w-full w-max px-4 py-3 text-[13px]">',
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import ArrowUpRight from "lucide-static/icons/arrow-up-right.svg?raw";
|
|
1
2
|
import Search from "lucide-static/icons/search.svg?raw";
|
|
2
3
|
import { attrs, escapeHtml } from "./html.js";
|
|
3
4
|
|
|
5
|
+
const externalLinkIcon = ArrowUpRight.replace('class="lucide', 'class="lucide ml-auto size-3.5 shrink-0');
|
|
6
|
+
|
|
4
7
|
export function renderSidebar({
|
|
5
8
|
id = "sidebar",
|
|
6
9
|
label = "Sidebar navigation",
|
|
@@ -58,6 +61,7 @@ export function renderSidebarContent(items = [], parentIdPrefix = "content") {
|
|
|
58
61
|
<a${attrs({ href: item.url, "aria-current": item.current ? "page" : undefined, ...without(item.attrs, ["href", "aria-current"]) })}>
|
|
59
62
|
${item.icon || ""}
|
|
60
63
|
<span>${escapeHtml(item.label)}</span>
|
|
64
|
+
${item.external ? externalLinkIcon : ""}
|
|
61
65
|
</a>
|
|
62
66
|
</li>`;
|
|
63
67
|
})
|
|
@@ -67,7 +71,7 @@ export function renderSidebarContent(items = [], parentIdPrefix = "content") {
|
|
|
67
71
|
export function renderCommandDialog({
|
|
68
72
|
id = "command-search",
|
|
69
73
|
items = [],
|
|
70
|
-
placeholder = "Search
|
|
74
|
+
placeholder = "Search...",
|
|
71
75
|
emptyText = "No results found.",
|
|
72
76
|
open = false,
|
|
73
77
|
commandAttrs = {},
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { gfmToMarkdown } from "mdast-util-gfm";
|
|
2
|
+
import { toMarkdown } from "mdast-util-to-markdown";
|
|
3
|
+
import remarkGfm from "remark-gfm";
|
|
4
|
+
import remarkMdx from "remark-mdx";
|
|
5
|
+
import remarkParse from "remark-parse";
|
|
6
|
+
import { unified } from "unified";
|
|
7
|
+
import { serializeComponent } from "./markdown-exporters/index.js";
|
|
8
|
+
import { getMdxChildrenSource, trimCode } from "./mdx-source.js";
|
|
9
|
+
|
|
10
|
+
const processor = unified().use(remarkParse).use(remarkGfm).use(remarkMdx);
|
|
11
|
+
|
|
12
|
+
const customSerializersCache = new Map();
|
|
13
|
+
|
|
14
|
+
export async function mdxToMarkdown(source, options = {}) {
|
|
15
|
+
const tree = processor.parse(source);
|
|
16
|
+
const state = {
|
|
17
|
+
constants: collectExportedStrings(tree),
|
|
18
|
+
customSerializers: await loadCustomSerializers(options.markdownSerializers),
|
|
19
|
+
source: String(source || ""),
|
|
20
|
+
};
|
|
21
|
+
const children = await transformBlocks(tree.children || [], state);
|
|
22
|
+
return `${stringify({ type: "root", children }).trim()}\n`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function loadCustomSerializers(entries = {}) {
|
|
26
|
+
const cacheKey = JSON.stringify(entries || {});
|
|
27
|
+
if (customSerializersCache.has(cacheKey)) return customSerializersCache.get(cacheKey);
|
|
28
|
+
|
|
29
|
+
const serializers = {};
|
|
30
|
+
for (const [name, moduleId] of Object.entries(entries || {})) {
|
|
31
|
+
if (!moduleId) continue;
|
|
32
|
+
const module = await import(/* @vite-ignore */ moduleId);
|
|
33
|
+
const serializer = module.default || module.serialize;
|
|
34
|
+
if (typeof serializer !== "function") {
|
|
35
|
+
throw new Error(`Markdown serializer for "${name}" must export a function as default or serialize.`);
|
|
36
|
+
}
|
|
37
|
+
serializers[name] = serializer;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
customSerializersCache.set(cacheKey, serializers);
|
|
41
|
+
return serializers;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function transformBlocks(nodes, state) {
|
|
45
|
+
const blocks = [];
|
|
46
|
+
for (const node of nodes) {
|
|
47
|
+
blocks.push(...(await transformBlock(node, state)));
|
|
48
|
+
}
|
|
49
|
+
return blocks.filter(Boolean);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function transformBlock(node, state) {
|
|
53
|
+
if (!node) return [];
|
|
54
|
+
|
|
55
|
+
if (node.type === "mdxjsEsm") return [];
|
|
56
|
+
|
|
57
|
+
if (isMdxElement(node)) {
|
|
58
|
+
return await transformComponent(node, state);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (node.type === "paragraph") {
|
|
62
|
+
const children = significantChildren(node.children || []);
|
|
63
|
+
if (children.length === 1 && isMdxElement(children[0])) {
|
|
64
|
+
return await transformComponent(children[0], state);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const clone = { ...node };
|
|
69
|
+
if (Array.isArray(clone.children)) {
|
|
70
|
+
if (isBlockContainer(clone.type)) {
|
|
71
|
+
clone.children = await transformBlocks(clone.children, state);
|
|
72
|
+
} else {
|
|
73
|
+
clone.children = await transformInline(clone.children, state);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return [clone];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function transformComponent(node, state) {
|
|
80
|
+
return (
|
|
81
|
+
(await serializeComponent(
|
|
82
|
+
node,
|
|
83
|
+
{
|
|
84
|
+
transformBlocks: (nodes) => transformBlocks(nodes, state),
|
|
85
|
+
getAttr: (entry, name) => getAttr(entry, name, state.constants),
|
|
86
|
+
getCodeValue: (entry) => getCodeValue(entry, state.constants),
|
|
87
|
+
getChildrenSource: (entry) => getMdxChildrenSource(entry, state.source),
|
|
88
|
+
isNamedElement,
|
|
89
|
+
significantChildren,
|
|
90
|
+
},
|
|
91
|
+
state.customSerializers,
|
|
92
|
+
)) || (await transformBlocks(node.children || [], state))
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function transformInline(nodes, state) {
|
|
97
|
+
const inline = [];
|
|
98
|
+
for (const node of nodes) {
|
|
99
|
+
if (isMdxElement(node)) {
|
|
100
|
+
const text = stringify({ type: "root", children: await transformComponent(node, state) }).trim();
|
|
101
|
+
if (text) inline.push({ type: "text", value: text });
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (node.type === "mdxTextExpression" || node.type === "mdxFlowExpression") {
|
|
105
|
+
const value = expressionValue(node, state.constants);
|
|
106
|
+
if (value) inline.push({ type: "text", value });
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const clone = { ...node };
|
|
110
|
+
if (Array.isArray(clone.children)) clone.children = await transformInline(clone.children, state);
|
|
111
|
+
inline.push(clone);
|
|
112
|
+
}
|
|
113
|
+
return inline;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function getCodeValue(node, constants) {
|
|
117
|
+
const parts = (node.children || []).map((child) => {
|
|
118
|
+
if (child.type === "text") return child.value || "";
|
|
119
|
+
if (child.type === "mdxFlowExpression" || child.type === "mdxTextExpression") {
|
|
120
|
+
return expressionValue(child, constants);
|
|
121
|
+
}
|
|
122
|
+
return "";
|
|
123
|
+
});
|
|
124
|
+
return trimCode(parts.join("") || String(getAttr(node, "code", constants) || ""));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function getAttr(node, name, constants) {
|
|
128
|
+
const attr = (node.attributes || []).find((entry) => entry.type === "mdxJsxAttribute" && entry.name === name);
|
|
129
|
+
if (!attr) return "";
|
|
130
|
+
if (attr.value == null) return "true";
|
|
131
|
+
if (typeof attr.value === "string") return attr.value;
|
|
132
|
+
return expressionValue(attr.value, constants);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function collectExportedStrings(tree) {
|
|
136
|
+
const constants = new Map();
|
|
137
|
+
for (const node of tree.children || []) {
|
|
138
|
+
if (node.type !== "mdxjsEsm") continue;
|
|
139
|
+
for (const statement of node.data?.estree?.body || []) {
|
|
140
|
+
if (statement.type !== "ExportNamedDeclaration") continue;
|
|
141
|
+
if (statement.declaration?.type !== "VariableDeclaration") continue;
|
|
142
|
+
for (const declaration of statement.declaration.declarations || []) {
|
|
143
|
+
if (declaration.id?.type !== "Identifier") continue;
|
|
144
|
+
const value = staticValue(declaration.init, constants);
|
|
145
|
+
if (value) constants.set(declaration.id.name, value);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return constants;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function expressionValue(node, constants) {
|
|
153
|
+
const expression = node.data?.estree?.body?.[0]?.expression || node.data?.estree?.body?.[0];
|
|
154
|
+
return staticValue(expression, constants) || "";
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function staticValue(node, constants) {
|
|
158
|
+
if (!node) return "";
|
|
159
|
+
if (node.type === "Literal") return node.value ?? "";
|
|
160
|
+
if (node.type === "TemplateLiteral" && node.expressions.length === 0) {
|
|
161
|
+
return node.quasis.map((quasi) => quasi.value.cooked ?? quasi.value.raw ?? "").join("");
|
|
162
|
+
}
|
|
163
|
+
if (node.type === "Identifier") return constants.get(node.name) || "";
|
|
164
|
+
if (node.type === "ObjectExpression") {
|
|
165
|
+
return Object.fromEntries(
|
|
166
|
+
node.properties
|
|
167
|
+
.filter((property) => property.type === "Property")
|
|
168
|
+
.map((property) => [property.key.name || property.key.value, staticValue(property.value, constants)]),
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
if (node.type === "ArrayExpression") {
|
|
172
|
+
return node.elements.map((element) => staticValue(element, constants));
|
|
173
|
+
}
|
|
174
|
+
return "";
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function stringify(tree) {
|
|
178
|
+
return toMarkdown(tree, {
|
|
179
|
+
bullet: "-",
|
|
180
|
+
fences: true,
|
|
181
|
+
rule: "-",
|
|
182
|
+
extensions: [gfmToMarkdown()],
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function isMdxElement(node) {
|
|
187
|
+
return node?.type === "mdxJsxFlowElement" || node?.type === "mdxJsxTextElement";
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function isNamedElement(node, name) {
|
|
191
|
+
return isMdxElement(node) && node.name === name;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function significantChildren(children) {
|
|
195
|
+
return children.filter((child) => child.type !== "text" || child.value.trim());
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function isBlockContainer(type) {
|
|
199
|
+
return type === "root" || type === "blockquote" || type === "list" || type === "listItem";
|
|
200
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
const serializers = {
|
|
2
|
+
Callout: renderCallout,
|
|
3
|
+
Code: renderCode,
|
|
4
|
+
CodeGroup: renderCodeGroup,
|
|
5
|
+
Preview: renderPreview,
|
|
6
|
+
Steps: renderSteps,
|
|
7
|
+
Tabs: renderChildren,
|
|
8
|
+
Card: renderCard,
|
|
9
|
+
CardGrid: renderCardGrid,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export async function serializeComponent(node, context, customSerializers = {}) {
|
|
13
|
+
if (customSerializers[node.name]) return await customSerializers[node.name](node, context);
|
|
14
|
+
return serializers[node.name]?.(node, context) || null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function renderChildren(node, context) {
|
|
18
|
+
return await context.transformBlocks(node.children || []);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function renderCallout(node, context) {
|
|
22
|
+
const title = context.getAttr(node, "title");
|
|
23
|
+
const action = context.getAttr(node, "action");
|
|
24
|
+
const children = await context.transformBlocks(node.children || []);
|
|
25
|
+
const quoteChildren = [];
|
|
26
|
+
if (title) quoteChildren.push(strongParagraph(title));
|
|
27
|
+
quoteChildren.push(...children);
|
|
28
|
+
if (action?.label) {
|
|
29
|
+
quoteChildren.push({
|
|
30
|
+
type: "paragraph",
|
|
31
|
+
children: action.href
|
|
32
|
+
? [{ type: "link", url: action.href, children: [{ type: "text", value: action.label }] }]
|
|
33
|
+
: [{ type: "strong", children: [{ type: "text", value: action.label }] }],
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return [{ type: "blockquote", children: quoteChildren }];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function renderCode(node, context) {
|
|
40
|
+
const title = context.getAttr(node, "title") || context.getAttr(node, "slot");
|
|
41
|
+
const lang = context.getAttr(node, "lang") || "text";
|
|
42
|
+
const value = context.getCodeValue(node);
|
|
43
|
+
const blocks = [];
|
|
44
|
+
if (title) blocks.push(strongParagraph(title));
|
|
45
|
+
blocks.push({ type: "code", lang, value });
|
|
46
|
+
return blocks;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function renderCodeGroup(node, context) {
|
|
50
|
+
const codeNodes = collectCodeChildren(node, context);
|
|
51
|
+
if (!codeNodes.length) return renderChildren(node, context);
|
|
52
|
+
return codeNodes.flatMap((child, index) => {
|
|
53
|
+
const blocks = renderCode(child, context);
|
|
54
|
+
return index === 0 ? blocks : [{ type: "thematicBreak" }, ...blocks];
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function renderPreview(node, context) {
|
|
59
|
+
const value = context.getAttr(node, "code") || context.getChildrenSource(node);
|
|
60
|
+
if (value) {
|
|
61
|
+
return [{ type: "code", lang: context.getAttr(node, "lang") || "mdx", value }];
|
|
62
|
+
}
|
|
63
|
+
return await renderChildren(node, context);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function renderSteps(node, context) {
|
|
67
|
+
const steps = (node.children || []).filter((child) => context.isNamedElement(child, "Step"));
|
|
68
|
+
if (!steps.length) return await renderChildren(node, context);
|
|
69
|
+
const children = [];
|
|
70
|
+
for (const step of steps) {
|
|
71
|
+
children.push({
|
|
72
|
+
type: "listItem",
|
|
73
|
+
spread: true,
|
|
74
|
+
children: await stepToBlocks(step, context),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
return [
|
|
78
|
+
{
|
|
79
|
+
type: "list",
|
|
80
|
+
ordered: true,
|
|
81
|
+
spread: true,
|
|
82
|
+
children,
|
|
83
|
+
},
|
|
84
|
+
];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function renderCard(node, context) {
|
|
88
|
+
return [cardList([node], context)];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function renderCardGrid(node, context) {
|
|
92
|
+
return [cardList((node.children || []).filter((child) => context.isNamedElement(child, "Card")), context)];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function stepToBlocks(node, context) {
|
|
96
|
+
const title = context.getAttr(node, "title");
|
|
97
|
+
const children = await context.transformBlocks(node.children || []);
|
|
98
|
+
if (!title) return children;
|
|
99
|
+
return [strongParagraph(title), ...children];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function cardList(cards, context) {
|
|
103
|
+
return {
|
|
104
|
+
type: "list",
|
|
105
|
+
ordered: false,
|
|
106
|
+
spread: true,
|
|
107
|
+
children: cards.map((card) => ({
|
|
108
|
+
type: "listItem",
|
|
109
|
+
spread: false,
|
|
110
|
+
children: [cardParagraph(card, context)],
|
|
111
|
+
})),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function cardParagraph(node, context) {
|
|
116
|
+
const title = context.getAttr(node, "title") || "Card";
|
|
117
|
+
const description = context.getAttr(node, "description");
|
|
118
|
+
const href = context.getAttr(node, "href");
|
|
119
|
+
const titleNode =
|
|
120
|
+
href && href !== "#"
|
|
121
|
+
? { type: "link", url: href, children: [{ type: "text", value: title }] }
|
|
122
|
+
: { type: "strong", children: [{ type: "text", value: title }] };
|
|
123
|
+
return {
|
|
124
|
+
type: "paragraph",
|
|
125
|
+
children: description ? [titleNode, { type: "text", value: `: ${description}` }] : [titleNode],
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function collectCodeChildren(node, context) {
|
|
130
|
+
return (node.children || []).flatMap((child) => {
|
|
131
|
+
if (context.isNamedElement(child, "Code")) return [child];
|
|
132
|
+
if (child.type === "paragraph") {
|
|
133
|
+
return context.significantChildren(child.children || []).filter((entry) => context.isNamedElement(entry, "Code"));
|
|
134
|
+
}
|
|
135
|
+
return [];
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function strongParagraph(value) {
|
|
140
|
+
return {
|
|
141
|
+
type: "paragraph",
|
|
142
|
+
children: [{ type: "strong", children: [{ type: "text", value }] }],
|
|
143
|
+
};
|
|
144
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function getMdxChildrenSource(node, source) {
|
|
2
|
+
const children = node?.children || [];
|
|
3
|
+
const first = children[0];
|
|
4
|
+
const last = children.at(-1);
|
|
5
|
+
const start = first?.position?.start?.offset;
|
|
6
|
+
const end = last?.position?.end?.offset;
|
|
7
|
+
if (!Number.isFinite(start) || !Number.isFinite(end) || end <= start) return "";
|
|
8
|
+
return trimCode(String(source || "").slice(start, end));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function trimCode(value) {
|
|
12
|
+
const lines = String(value || "").replace(/\r\n/g, "\n").split("\n");
|
|
13
|
+
while (lines.length && !lines[0].trim()) lines.shift();
|
|
14
|
+
while (lines.length && !lines.at(-1).trim()) lines.pop();
|
|
15
|
+
const indent = lines
|
|
16
|
+
.filter((line) => line.trim())
|
|
17
|
+
.reduce((min, line) => Math.min(min, line.match(/^\s*/)[0].length), Infinity);
|
|
18
|
+
return lines.map((line) => line.slice(Number.isFinite(indent) ? indent : 0)).join("\n");
|
|
19
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { getMdxChildrenSource } from "./mdx-source.js";
|
|
2
|
+
|
|
3
|
+
export function previewSourceRemarkPlugin() {
|
|
4
|
+
return (tree, file) => {
|
|
5
|
+
const source = String(file.value || "");
|
|
6
|
+
walk(tree, (node) => {
|
|
7
|
+
if (!isPreview(node) || hasAttribute(node, "code")) return;
|
|
8
|
+
const code = getMdxChildrenSource(node, source);
|
|
9
|
+
node.attributes = [
|
|
10
|
+
...(node.attributes || []),
|
|
11
|
+
{
|
|
12
|
+
type: "mdxJsxAttribute",
|
|
13
|
+
name: "code",
|
|
14
|
+
value: code,
|
|
15
|
+
},
|
|
16
|
+
];
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function walk(node, callback) {
|
|
22
|
+
if (!node || typeof node !== "object") return;
|
|
23
|
+
callback(node);
|
|
24
|
+
for (const child of node.children || []) walk(child, callback);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isPreview(node) {
|
|
28
|
+
return node?.type === "mdxJsxFlowElement" && node.name === "Preview";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function hasAttribute(node, name) {
|
|
32
|
+
return (node.attributes || []).some((attr) => attr.type === "mdxJsxAttribute" && attr.name === name);
|
|
33
|
+
}
|