reallysimpledocs 1.0.0-beta.4 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -39
- package/package.json +11 -11
- package/src/astro/index.js +64 -98
- package/src/astro/preview-scripts.js +140 -0
- package/src/astro/preview-source.js +150 -0
- package/src/astro/rehype-basecoat-tables.js +62 -0
- package/src/components/index.js +1 -0
- package/src/css/custom.css +197 -58
- package/src/css/styles/luma.css +9 -0
- package/src/css/styles/lyra.css +9 -0
- package/src/css/styles/maia.css +9 -0
- package/src/css/styles/mira.css +9 -0
- package/src/css/styles/nova.css +9 -0
- package/src/css/styles/rhea.css +9 -0
- package/src/css/styles/sera.css +9 -0
- package/src/css/styles/vega.css +9 -0
- package/src/runtime/components/BaseLayout.astro +22 -28
- package/src/runtime/components/CommandDialog.astro +52 -35
- package/src/runtime/components/DefaultContentHeader.astro +0 -17
- package/src/runtime/components/DefaultHead.astro +110 -0
- package/src/runtime/components/DefaultHeadContent.astro +2 -0
- package/src/runtime/components/DefaultSidebarHeader.astro +1 -1
- package/src/runtime/components/DocsLayout.astro +99 -166
- package/src/runtime/components/DropdownMenu.astro +1 -1
- package/src/runtime/components/ThemeToggle.astro +21 -0
- package/src/runtime/components/mdx/Callout.astro +2 -32
- package/src/runtime/components/mdx/Card.astro +3 -8
- package/src/runtime/components/mdx/CardGrid.astro +1 -1
- package/src/runtime/components/mdx/Code.astro +21 -27
- package/src/runtime/components/mdx/CodeGroup.astro +114 -24
- package/src/runtime/components/mdx/Preview.astro +7 -13
- package/src/runtime/components/mdx/Steps.astro +1 -5
- package/src/runtime/components/mdx/Tab.astro +6 -0
- package/src/runtime/components/mdx/Tabs.astro +7 -7
- package/src/runtime/lib/code-meta.js +8 -0
- package/src/runtime/lib/docs.js +141 -119
- package/src/runtime/lib/highlight.js +1 -14
- package/src/runtime/lib/html.js +9 -0
- package/src/runtime/lib/macros.js +10 -5
- package/src/runtime/lib/markdown-export.js +240 -169
- package/src/runtime/pages/doc.astro +1 -3
- 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 +12 -3
- package/CHANGELOG.md +0 -35
- package/src/css/overrides.css +0 -1
- package/src/css/sources.css +0 -1
- package/src/runtime/lib/markdown-exporters/index.js +0 -144
- package/src/runtime/lib/mdx-source.js +0 -19
- package/src/runtime/lib/preview-source-plugin.js +0 -33
- package/src/runtime/scripts/copy-code.js +0 -130
- package/src/site/ContentHeader.astro +0 -33
|
@@ -1,45 +1,134 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
2
|
+
import { decodeHtml } from "../../lib/html.js";
|
|
3
|
+
|
|
4
|
+
const { class: className = "" } = Astro.props;
|
|
3
5
|
const id = `code-group-${Math.random().toString(36).slice(2)}`;
|
|
4
6
|
const slotHtml = Astro.slots.has("default") ? await Astro.slots.render("default") : "";
|
|
5
7
|
const normalizedTabs = parseCodeTabs(slotHtml);
|
|
6
|
-
const defaultValue =
|
|
7
|
-
const classes = [
|
|
8
|
-
"relative my-6 w-full max-w-full overflow-hidden rounded-lg [&>[role=tabpanel]]:border-t-0 [&_[data-code-block]]:my-0 [&_[data-code-block]]:min-w-0 [&_[data-code-block]]:rounded-none [&_[data-code-block]>header]:hidden",
|
|
9
|
-
className,
|
|
10
|
-
];
|
|
8
|
+
const defaultValue = normalizedTabs[0]?.value;
|
|
11
9
|
|
|
12
10
|
function parseCodeTabs(html) {
|
|
13
11
|
const blocks = [];
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const label = decodeHtml(match[1]).trim();
|
|
19
|
-
if (!label) continue;
|
|
12
|
+
for (const block of findCodeFrames(html)) {
|
|
13
|
+
const label = decodeHtml(block.title).trim();
|
|
14
|
+
const code = findCodeBlock(block.html);
|
|
15
|
+
if (!label || !code) continue;
|
|
20
16
|
blocks.push({
|
|
21
17
|
label,
|
|
22
18
|
value: label,
|
|
23
|
-
html:
|
|
19
|
+
html: code.html,
|
|
24
20
|
});
|
|
25
21
|
}
|
|
26
22
|
return blocks;
|
|
27
23
|
}
|
|
28
24
|
|
|
29
|
-
function
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
25
|
+
function findCodeFrames(html) {
|
|
26
|
+
const blocks = [];
|
|
27
|
+
let cursor = 0;
|
|
28
|
+
|
|
29
|
+
while (cursor < html.length) {
|
|
30
|
+
const open = findNextDiv(html, cursor);
|
|
31
|
+
if (!open) break;
|
|
32
|
+
const title = attrValue(open.source, "data-code-title");
|
|
33
|
+
if (hasClass(open.source, "code-frame") && title) {
|
|
34
|
+
const close = findMatchingDivClose(html, open.end);
|
|
35
|
+
if (!close) break;
|
|
36
|
+
blocks.push({ title, html: html.slice(open.start, close.end) });
|
|
37
|
+
cursor = close.end;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
cursor = open.end;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return blocks;
|
|
36
44
|
}
|
|
45
|
+
|
|
46
|
+
function findCodeBlock(html) {
|
|
47
|
+
let cursor = 0;
|
|
48
|
+
|
|
49
|
+
while (cursor < html.length) {
|
|
50
|
+
const open = findNextDiv(html, cursor);
|
|
51
|
+
if (!open) break;
|
|
52
|
+
if (hasClass(open.source, "code-block")) {
|
|
53
|
+
const close = findMatchingDivClose(html, open.end);
|
|
54
|
+
if (!close) return null;
|
|
55
|
+
return { html: html.slice(open.start, close.end) };
|
|
56
|
+
}
|
|
57
|
+
cursor = open.end;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function findMatchingDivClose(html, fromIndex) {
|
|
64
|
+
let depth = 1;
|
|
65
|
+
let cursor = fromIndex;
|
|
66
|
+
|
|
67
|
+
while (cursor < html.length) {
|
|
68
|
+
const tag = findNextDiv(html, cursor);
|
|
69
|
+
if (!tag) return null;
|
|
70
|
+
depth += tag.closing ? -1 : 1;
|
|
71
|
+
if (depth === 0) return tag;
|
|
72
|
+
cursor = tag.end;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function findNextDiv(html, fromIndex) {
|
|
79
|
+
let cursor = fromIndex;
|
|
80
|
+
while (cursor < html.length) {
|
|
81
|
+
const start = html.indexOf("<div", cursor);
|
|
82
|
+
const closeStart = html.indexOf("</div", cursor);
|
|
83
|
+
const isClose = closeStart !== -1 && (start === -1 || closeStart < start);
|
|
84
|
+
const tagStart = isClose ? closeStart : start;
|
|
85
|
+
if (tagStart === -1) return null;
|
|
86
|
+
|
|
87
|
+
const end = findTagEnd(html, tagStart);
|
|
88
|
+
if (end === -1) return null;
|
|
89
|
+
return {
|
|
90
|
+
start: tagStart,
|
|
91
|
+
end: end + 1,
|
|
92
|
+
closing: isClose,
|
|
93
|
+
source: html.slice(tagStart, end + 1),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function findTagEnd(html, start) {
|
|
101
|
+
let quote = "";
|
|
102
|
+
for (let index = start; index < html.length; index += 1) {
|
|
103
|
+
const char = html[index];
|
|
104
|
+
if (quote) {
|
|
105
|
+
if (char === quote) quote = "";
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (char === '"' || char === "'") {
|
|
109
|
+
quote = char;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (char === ">") return index;
|
|
113
|
+
}
|
|
114
|
+
return -1;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function attrValue(tag, name) {
|
|
118
|
+
const pattern = new RegExp(`\\s${name}=(["'])(.*?)\\1`, "s");
|
|
119
|
+
return tag.match(pattern)?.[2] || "";
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function hasClass(tag, name) {
|
|
123
|
+
return attrValue(tag, "class").split(/\s+/).includes(name);
|
|
124
|
+
}
|
|
125
|
+
|
|
37
126
|
---
|
|
38
127
|
|
|
39
|
-
<div class:list={
|
|
40
|
-
<header
|
|
41
|
-
<div class="tabs
|
|
42
|
-
<nav
|
|
128
|
+
<div class:list={["tabs-frame", className]}>
|
|
129
|
+
<header>
|
|
130
|
+
<div class="tabs">
|
|
131
|
+
<nav role="tablist" aria-orientation="horizontal" data-variant="line">
|
|
43
132
|
{
|
|
44
133
|
normalizedTabs.map((tab, index) => {
|
|
45
134
|
const selected = tab.value === defaultValue;
|
|
@@ -66,6 +155,7 @@ function decodeHtml(value) {
|
|
|
66
155
|
return (
|
|
67
156
|
<div
|
|
68
157
|
role="tabpanel"
|
|
158
|
+
data-code-panel
|
|
69
159
|
id={`${id}-panel-${index + 1}`}
|
|
70
160
|
aria-labelledby={`${id}-tab-${index + 1}`}
|
|
71
161
|
tabindex="-1"
|
|
@@ -4,15 +4,13 @@ import { highlightCode } from "../../lib/highlight.js";
|
|
|
4
4
|
const { code = "", lang = "html", title = "Preview", class: className = "" } = Astro.props;
|
|
5
5
|
const id = `preview-${Math.random().toString(36).slice(2)}`;
|
|
6
6
|
const html = await highlightCode(code, lang);
|
|
7
|
+
const previewClassName = className || "w-fit";
|
|
7
8
|
---
|
|
8
9
|
|
|
9
|
-
<section
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
>
|
|
13
|
-
<header class="flex h-10 items-center gap-2 border-b border-border/50 bg-muted/50 px-3 text-sm font-medium">
|
|
14
|
-
<div class="tabs my-0 min-w-0 max-w-full gap-0">
|
|
15
|
-
<nav class="h-full justify-start rounded-none bg-transparent p-0" role="tablist" aria-orientation="horizontal" data-variant="line">
|
|
10
|
+
<section class="tabs-frame">
|
|
11
|
+
<header>
|
|
12
|
+
<div class="tabs">
|
|
13
|
+
<nav role="tablist" aria-orientation="horizontal" data-variant="line">
|
|
16
14
|
<button
|
|
17
15
|
type="button"
|
|
18
16
|
role="tab"
|
|
@@ -43,9 +41,8 @@ const html = await highlightCode(code, lang);
|
|
|
43
41
|
aria-labelledby={`${id}-preview-tab`}
|
|
44
42
|
tabindex="-1"
|
|
45
43
|
aria-selected="true"
|
|
46
|
-
class="flex min-h-64 w-full items-center justify-center p-6"
|
|
47
44
|
>
|
|
48
|
-
<div class={
|
|
45
|
+
<div class={previewClassName}>
|
|
49
46
|
<slot />
|
|
50
47
|
</div>
|
|
51
48
|
</section>
|
|
@@ -58,10 +55,7 @@ const html = await highlightCode(code, lang);
|
|
|
58
55
|
aria-selected="false"
|
|
59
56
|
hidden
|
|
60
57
|
>
|
|
61
|
-
<div
|
|
62
|
-
class="relative my-6 w-full max-w-full overflow-hidden rounded-lg"
|
|
63
|
-
data-code-block
|
|
64
|
-
>
|
|
58
|
+
<div class="code-block">
|
|
65
59
|
<Fragment set:html={html} />
|
|
66
60
|
</div>
|
|
67
61
|
</section>
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
const { class: className = "" } = Astro.props;
|
|
3
|
-
const classes = [
|
|
4
|
-
"mb-12 mt-6 [counter-reset:step] md:ml-4 md:border-l md:pl-8 [&>section]:mb-10 [&>section]:[counter-increment:step] [&>section:last-child]:mb-0 [&>section>*+*]:mt-6 [&>section>*]:leading-relaxed [&>section>*:first-child]:mt-0 [&>section>*:last-child]:mb-0 [&>section>h3+*]:mt-4 [&>section>h3]:relative [&>section>h3]:scroll-m-28 [&>section>h3]:text-lg [&>section>h3]:font-medium [&>section>h3]:tracking-tight [&>section>h3]:before:mr-4 [&>section>h3]:before:inline-flex [&>section>h3]:before:size-7 [&>section>h3]:before:items-center [&>section>h3]:before:justify-center [&>section>h3]:before:rounded-full [&>section>h3]:before:bg-muted [&>section>h3]:before:text-sm [&>section>h3]:before:font-medium [&>section>h3]:before:text-muted-foreground [&>section>h3]:before:content-[counter(step)] md:[&>section>h3]:before:absolute md:[&>section>h3]:before:left-0 md:[&>section>h3]:before:top-0 md:[&>section>h3]:before:mr-0 md:[&>section>h3]:before:-translate-x-[2.875rem]",
|
|
5
|
-
className,
|
|
6
|
-
];
|
|
7
3
|
---
|
|
8
4
|
|
|
9
|
-
<div role="list" class:list={
|
|
5
|
+
<div role="list" class:list={["steps", className]}>
|
|
10
6
|
<slot />
|
|
11
7
|
</div>
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
---
|
|
2
|
-
const { tabs = [],
|
|
2
|
+
const { tabs = [], variant, class: className = "" } = Astro.props;
|
|
3
3
|
const id = `tabs-${Math.random().toString(36).slice(2)}`;
|
|
4
|
-
const
|
|
4
|
+
const configuredTabs = typeof tabs === "string" ? JSON.parse(tabs) : tabs;
|
|
5
|
+
const normalizedTabs = configuredTabs.map((tab, index) => ({
|
|
5
6
|
...tab,
|
|
6
7
|
slot: tab.slot || String(index),
|
|
7
8
|
value: tab.value || tab.slot || String(index),
|
|
8
9
|
}));
|
|
9
|
-
const defaultValue =
|
|
10
|
+
const defaultValue = normalizedTabs[0]?.value;
|
|
10
11
|
---
|
|
11
12
|
|
|
12
|
-
<div class:list={["tabs
|
|
13
|
-
<nav role="tablist" aria-orientation=
|
|
13
|
+
<div class:list={["tabs", className]}>
|
|
14
|
+
<nav role="tablist" aria-orientation="horizontal" data-variant={variant}>
|
|
14
15
|
{
|
|
15
16
|
normalizedTabs.map((tab, index) => {
|
|
16
17
|
const selected = tab.value === defaultValue;
|
|
@@ -21,8 +22,7 @@ const defaultValue = defaultTab || normalizedTabs.find((tab) => !tab.disabled)?.
|
|
|
21
22
|
id={`${id}-tab-${index + 1}`}
|
|
22
23
|
aria-controls={`${id}-panel-${index + 1}`}
|
|
23
24
|
aria-selected={selected ? "true" : "false"}
|
|
24
|
-
tabindex={selected
|
|
25
|
-
disabled={tab.disabled}
|
|
25
|
+
tabindex={selected ? "0" : "-1"}
|
|
26
26
|
>
|
|
27
27
|
{tab.label}
|
|
28
28
|
</button>
|
package/src/runtime/lib/docs.js
CHANGED
|
@@ -2,9 +2,12 @@ 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";
|
|
5
6
|
import * as lucideIcons from "lucide-static";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
7
|
+
import { transformBasecoatTablesHtml } from "../../astro/rehype-basecoat-tables.js";
|
|
8
|
+
import { parseCodeMeta } from "./code-meta.js";
|
|
9
|
+
import { escapeHtml } from "./html.js";
|
|
10
|
+
import { normalizeDocMarkdown } from "./markdown-export.js";
|
|
8
11
|
import { markdownPath, routePath } from "./paths.js";
|
|
9
12
|
|
|
10
13
|
export function getManifest(config) {
|
|
@@ -83,14 +86,8 @@ export function getDocMarkdown(config, page) {
|
|
|
83
86
|
return fs.readFileSync(filePath, "utf8");
|
|
84
87
|
}
|
|
85
88
|
|
|
86
|
-
export
|
|
87
|
-
|
|
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();
|
|
89
|
+
export function getMarkdownExport(config, page) {
|
|
90
|
+
return `${getNormalizedDocMarkdown(config, page).trim()}\n`;
|
|
94
91
|
}
|
|
95
92
|
|
|
96
93
|
export async function renderDoc(config, page) {
|
|
@@ -108,25 +105,10 @@ export async function renderDoc(config, page) {
|
|
|
108
105
|
return `<h${depth} id="${id}" tabindex="-1"><a class="header-anchor" href="#${id}">${text}</a></h${depth}>`;
|
|
109
106
|
};
|
|
110
107
|
|
|
111
|
-
renderer.table = (token) => {
|
|
112
|
-
const header = renderer.tablerow({
|
|
113
|
-
text: token.header.map((cell) => renderer.tablecell(cell)).join(""),
|
|
114
|
-
});
|
|
115
|
-
const body = token.rows
|
|
116
|
-
.map((row) =>
|
|
117
|
-
renderer.tablerow({
|
|
118
|
-
text: row.map((cell) => renderer.tablecell(cell)).join(""),
|
|
119
|
-
}),
|
|
120
|
-
)
|
|
121
|
-
.join("");
|
|
122
|
-
const tbody = body ? `<tbody>${body}</tbody>` : "";
|
|
123
|
-
return `<div class="relative my-6 w-full overflow-auto"><table><thead>${header}</thead>${tbody}</table></div>`;
|
|
124
|
-
};
|
|
125
|
-
|
|
126
108
|
const tokens = marked.lexer(content);
|
|
127
|
-
await
|
|
109
|
+
await highlightCode(tokens);
|
|
128
110
|
const html = marked.parser(tokens, { renderer });
|
|
129
|
-
return { page, html, headings };
|
|
111
|
+
return { page, html: await transformBasecoatTablesHtml(html), headings };
|
|
130
112
|
}
|
|
131
113
|
|
|
132
114
|
export function getDocHeadings(config, page) {
|
|
@@ -159,46 +141,6 @@ export function getNavigation(config, page) {
|
|
|
159
141
|
};
|
|
160
142
|
}
|
|
161
143
|
|
|
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
|
-
|
|
202
144
|
export function resolveIcon(icon) {
|
|
203
145
|
if (!icon || typeof icon !== "string") return "";
|
|
204
146
|
const trimmed = icon.trim();
|
|
@@ -229,35 +171,28 @@ export function buildMenus(config, currentSlug) {
|
|
|
229
171
|
icon,
|
|
230
172
|
url: page.path,
|
|
231
173
|
label: item.label || page.title,
|
|
174
|
+
badge: item.badge,
|
|
232
175
|
current: page.slug === currentSlug,
|
|
233
176
|
keywords: page.title,
|
|
234
|
-
|
|
235
|
-
};
|
|
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,
|
|
177
|
+
attrs: item.attrs,
|
|
253
178
|
};
|
|
254
179
|
};
|
|
255
180
|
|
|
256
181
|
const processItem = (item) => {
|
|
257
182
|
if (typeof item === "string") return processSlug(item);
|
|
183
|
+
if (item?.url && item?.label) {
|
|
184
|
+
return {
|
|
185
|
+
type: "item",
|
|
186
|
+
icon: resolveIcon(item.icon),
|
|
187
|
+
url: item.url,
|
|
188
|
+
label: item.label,
|
|
189
|
+
badge: item.badge,
|
|
190
|
+
external: /^https?:\/\//.test(item.url),
|
|
191
|
+
attrs: item.attrs,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
258
194
|
const slug = getPageItemSlug(item);
|
|
259
195
|
if (slug) return processSlug(slug, item);
|
|
260
|
-
if (item?.url) return processExternalLink(item);
|
|
261
196
|
if (item?.type === "submenu") {
|
|
262
197
|
const items = (item.items || []).map(processItem).filter(Boolean);
|
|
263
198
|
return {
|
|
@@ -287,37 +222,90 @@ export function buildMenus(config, currentSlug) {
|
|
|
287
222
|
icon: item.icon,
|
|
288
223
|
url: item.url,
|
|
289
224
|
label: item.label,
|
|
290
|
-
external: item.external,
|
|
291
|
-
attrs: item.attrs,
|
|
292
225
|
keywords: item.keywords,
|
|
226
|
+
attrs: item.attrs,
|
|
293
227
|
})),
|
|
294
228
|
}));
|
|
295
229
|
|
|
296
230
|
return { sidebar, command };
|
|
297
231
|
}
|
|
298
232
|
|
|
299
|
-
export
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
content: await getMarkdownBodyExport(config, page),
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
return docs;
|
|
233
|
+
export function getLlmDocs(config) {
|
|
234
|
+
return getPages(config).map((page) => ({
|
|
235
|
+
...page,
|
|
236
|
+
content: getNormalizedDocMarkdown(config, page, { includeTitle: false }).trim(),
|
|
237
|
+
}));
|
|
308
238
|
}
|
|
309
239
|
|
|
310
|
-
export
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
|
|
240
|
+
export function getSearchDocs(config) {
|
|
241
|
+
const pages = getPages(config);
|
|
242
|
+
const trails = getPageTrails(config, pages);
|
|
243
|
+
|
|
244
|
+
return pages.map((page) => {
|
|
245
|
+
const markdown = getNormalizedDocMarkdown(config, page, { includeTitle: false });
|
|
246
|
+
const { prose, code } = splitFencedMarkdown(markdown);
|
|
247
|
+
const trail = trails.get(page.slug) || [page.title];
|
|
248
|
+
|
|
249
|
+
return {
|
|
314
250
|
slug: page.slug,
|
|
315
251
|
title: page.title,
|
|
252
|
+
breadcrumb: trail.length > 1 ? trail.join(" › ") : "",
|
|
316
253
|
path: page.path,
|
|
317
|
-
body: markdownPlainText(
|
|
254
|
+
body: markdownPlainText(prose),
|
|
255
|
+
code: markdownPlainText(code),
|
|
256
|
+
};
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function getPageTrails(config, pages) {
|
|
261
|
+
const manifest = getManifest(config);
|
|
262
|
+
const pagesBySlug = new Map(pages.map((page) => [page.slug, page]));
|
|
263
|
+
const trails = new Map();
|
|
264
|
+
|
|
265
|
+
const pageLabel = (item, slug) => {
|
|
266
|
+
if (typeof item === "object" && item?.label) return item.label;
|
|
267
|
+
return pagesBySlug.get(slug)?.title || fallbackLabelFromSlug(slug);
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const walk = (items = [], parents = []) => {
|
|
271
|
+
items.forEach((item) => {
|
|
272
|
+
if (typeof item === "string") {
|
|
273
|
+
trails.set(item, [...parents, pageLabel(item, item)]);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (item?.type === "submenu") {
|
|
278
|
+
walk(item.items || [], [...parents, item.label].filter(Boolean));
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const slug = getPageItemSlug(item);
|
|
283
|
+
if (slug) trails.set(slug, [...parents, pageLabel(item, slug)]);
|
|
318
284
|
});
|
|
319
|
-
}
|
|
320
|
-
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
(manifest.menu || []).forEach((group) => {
|
|
288
|
+
if (group?.type === "group") walk(group.items || []);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
return trails;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function splitFencedMarkdown(markdown) {
|
|
295
|
+
const code = [];
|
|
296
|
+
const prose = String(markdown || "").replace(/```[^\n]*\n([\s\S]*?)```/g, (_, block) => {
|
|
297
|
+
code.push(block);
|
|
298
|
+
return "\n";
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
return { prose, code: code.join("\n\n") };
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function getNormalizedDocMarkdown(config, page, { includeTitle = true } = {}) {
|
|
305
|
+
const source = getDocMarkdown(config, page);
|
|
306
|
+
const parsed = parseDocMarkdown(source, page.title);
|
|
307
|
+
const body = normalizeDocMarkdown(renderNunjucksCompat(parsed.body), collectMdxExports(source));
|
|
308
|
+
return includeTitle ? `# ${parsed.title}\n\n${body}` : body;
|
|
321
309
|
}
|
|
322
310
|
|
|
323
311
|
function flattenSidebarItems(items = []) {
|
|
@@ -329,10 +317,6 @@ export function getPageItemSlug(item) {
|
|
|
329
317
|
return item.slug || item.page || "";
|
|
330
318
|
}
|
|
331
319
|
|
|
332
|
-
function isExternalUrl(url) {
|
|
333
|
-
return /^[a-z][a-z0-9+.-]*:/i.test(String(url || ""));
|
|
334
|
-
}
|
|
335
|
-
|
|
336
320
|
function parseDocMarkdown(source, fallbackTitle) {
|
|
337
321
|
const content = normalizeDocSource(source);
|
|
338
322
|
const tokens = marked.lexer(content);
|
|
@@ -355,6 +339,15 @@ function markdownPlainText(markdown) {
|
|
|
355
339
|
return tokensText(marked.lexer(renderNunjucksCompat(normalizeDocSource(markdown))));
|
|
356
340
|
}
|
|
357
341
|
|
|
342
|
+
function collectMdxExports(source) {
|
|
343
|
+
const values = new Map();
|
|
344
|
+
const pattern = /export\s+const\s+([A-Za-z_$][\w$]*)\s*=\s*`([\s\S]*?)`;/g;
|
|
345
|
+
for (const match of String(source || "").matchAll(pattern)) {
|
|
346
|
+
values.set(match[1], match[2].replace(/\\n/g, "\n").replace(/\\`/g, "`"));
|
|
347
|
+
}
|
|
348
|
+
return values;
|
|
349
|
+
}
|
|
350
|
+
|
|
358
351
|
function normalizeDocSource(source) {
|
|
359
352
|
const withoutFrontmatter = String(source || "")
|
|
360
353
|
.trimStart()
|
|
@@ -366,10 +359,10 @@ function normalizeDocSource(source) {
|
|
|
366
359
|
.trimStart();
|
|
367
360
|
}
|
|
368
361
|
|
|
369
|
-
async function
|
|
362
|
+
async function highlightCode(tokens) {
|
|
370
363
|
for (const token of tokens) {
|
|
371
364
|
if (token.type === "code") {
|
|
372
|
-
const html = await highlightToken(token);
|
|
365
|
+
const html = codeBlockHtml(token, await highlightToken(token));
|
|
373
366
|
Object.assign(token, {
|
|
374
367
|
type: "html",
|
|
375
368
|
raw: html,
|
|
@@ -380,24 +373,39 @@ async function highlightMarkdownCode(tokens) {
|
|
|
380
373
|
}
|
|
381
374
|
|
|
382
375
|
for (const childTokens of nestedTokenLists(token)) {
|
|
383
|
-
await
|
|
376
|
+
await highlightCode(childTokens);
|
|
384
377
|
}
|
|
385
378
|
}
|
|
386
379
|
}
|
|
387
380
|
|
|
381
|
+
function codeBlockHtml(token, html) {
|
|
382
|
+
const title = parseCodeMeta(token.lang).title;
|
|
383
|
+
if (!title) return html;
|
|
384
|
+
return `<div class="code-frame" data-code-title="${escapeHtml(title)}"><header><span>${escapeHtml(title)}</span></header><div class="code-block">${html}</div></div>`;
|
|
385
|
+
}
|
|
386
|
+
|
|
388
387
|
async function highlightToken(token) {
|
|
389
|
-
const lang = normalizeLanguage(token.lang);
|
|
390
388
|
try {
|
|
391
|
-
return
|
|
389
|
+
return await codeToHtml(token.text, {
|
|
390
|
+
lang: normalizeLanguage(token.lang),
|
|
391
|
+
themes: {
|
|
392
|
+
light: "github-light",
|
|
393
|
+
dark: "github-dark",
|
|
394
|
+
},
|
|
395
|
+
defaultColor: false,
|
|
396
|
+
});
|
|
392
397
|
} catch {
|
|
393
|
-
return
|
|
398
|
+
return codeToHtml(token.text, {
|
|
399
|
+
lang: "text",
|
|
400
|
+
themes: {
|
|
401
|
+
light: "github-light",
|
|
402
|
+
dark: "github-dark",
|
|
403
|
+
},
|
|
404
|
+
defaultColor: false,
|
|
405
|
+
});
|
|
394
406
|
}
|
|
395
407
|
}
|
|
396
408
|
|
|
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
|
-
|
|
401
409
|
function normalizeLanguage(lang) {
|
|
402
410
|
const value = lang?.trim().split(/\s+/)[0].toLowerCase() || "text";
|
|
403
411
|
if (value === "njk") return "html";
|
|
@@ -427,6 +435,7 @@ function plainText(tokens) {
|
|
|
427
435
|
function tokensText(tokens) {
|
|
428
436
|
return tokens
|
|
429
437
|
.map((token) => {
|
|
438
|
+
if (token.type === "html") return htmlText(token.raw || token.text || "");
|
|
430
439
|
const childTokens = [];
|
|
431
440
|
if (token.tokens) childTokens.push(token.tokens);
|
|
432
441
|
if (token.items) childTokens.push(...token.items.flatMap((item) => (item.tokens ? [item.tokens] : [])));
|
|
@@ -438,3 +447,16 @@ function tokensText(tokens) {
|
|
|
438
447
|
.replace(/\s+/g, " ")
|
|
439
448
|
.trim();
|
|
440
449
|
}
|
|
450
|
+
|
|
451
|
+
function htmlText(html) {
|
|
452
|
+
return String(html || "")
|
|
453
|
+
.replace(/<script\b[\s\S]*?<\/script>/gi, " ")
|
|
454
|
+
.replace(/<style\b[\s\S]*?<\/style>/gi, " ")
|
|
455
|
+
.replace(/<[^>]+>/g, " ")
|
|
456
|
+
.replace(/ /g, " ")
|
|
457
|
+
.replace(/&/g, "&")
|
|
458
|
+
.replace(/</g, "<")
|
|
459
|
+
.replace(/>/g, ">")
|
|
460
|
+
.replace(/"/g, '"')
|
|
461
|
+
.replace(/'/g, "'");
|
|
462
|
+
}
|
|
@@ -3,7 +3,7 @@ import { codeToHtml } from "shiki";
|
|
|
3
3
|
export async function highlightCode(code, lang = "text") {
|
|
4
4
|
const value = String(code || "");
|
|
5
5
|
const language = String(lang || "text").toLowerCase() === "mdx" ? "tsx" : String(lang || "text");
|
|
6
|
-
|
|
6
|
+
return codeToHtml(value, {
|
|
7
7
|
lang: language,
|
|
8
8
|
themes: {
|
|
9
9
|
light: "github-light",
|
|
@@ -11,17 +11,4 @@ export async function highlightCode(code, lang = "text") {
|
|
|
11
11
|
},
|
|
12
12
|
defaultColor: false,
|
|
13
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
14
|
}
|