reallysimpledocs 1.0.0-beta.5 → 1.0.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 +52 -32
- package/package.json +8 -4
- package/src/astro/index.js +18 -0
- package/src/astro/preview-scripts.js +140 -0
- package/src/astro/preview-source.js +68 -8
- package/src/astro/rehype-basecoat-tables.js +62 -0
- package/src/css/custom.css +49 -129
- package/src/css/styles/luma.css +4 -6
- package/src/css/styles/lyra.css +4 -6
- package/src/css/styles/maia.css +4 -6
- package/src/css/styles/mira.css +4 -6
- package/src/css/styles/nova.css +4 -6
- package/src/css/styles/rhea.css +4 -6
- package/src/css/styles/sera.css +4 -6
- package/src/css/styles/vega.css +4 -6
- package/src/runtime/components/BaseLayout.astro +20 -19
- package/src/runtime/components/CommandDialog.astro +24 -5
- package/src/runtime/components/DefaultHead.astro +20 -30
- package/src/runtime/components/DocsLayout.astro +83 -39
- package/src/runtime/components/ThemeToggle.astro +1 -1
- package/src/runtime/components/mdx/Code.astro +22 -14
- package/src/runtime/components/mdx/CodeGroup.astro +109 -15
- package/src/runtime/components/mdx/Preview.astro +1 -1
- package/src/runtime/lib/code-meta.js +8 -0
- package/src/runtime/lib/docs.js +11 -17
- package/src/runtime/lib/html.js +9 -0
- package/src/runtime/lib/markdown-export.js +111 -3
|
@@ -4,7 +4,6 @@ import ArrowLeft from "lucide-static/icons/arrow-left.svg?raw";
|
|
|
4
4
|
import ArrowRight from "lucide-static/icons/arrow-right.svg?raw";
|
|
5
5
|
import Check from "lucide-static/icons/check.svg?raw";
|
|
6
6
|
import Copy from "lucide-static/icons/copy.svg?raw";
|
|
7
|
-
import Menu from "lucide-static/icons/menu.svg?raw";
|
|
8
7
|
import PanelLeft from "lucide-static/icons/panel-left.svg?raw";
|
|
9
8
|
import Search from "lucide-static/icons/search.svg?raw";
|
|
10
9
|
import ClaudeLogo from "../../icons/logos/claude.svg?raw";
|
|
@@ -62,7 +61,11 @@ const actionItems = [
|
|
|
62
61
|
];
|
|
63
62
|
---
|
|
64
63
|
|
|
65
|
-
<BaseLayout
|
|
64
|
+
<BaseLayout
|
|
65
|
+
title={page.title}
|
|
66
|
+
description={site.description}
|
|
67
|
+
pagePath={page.path}
|
|
68
|
+
>
|
|
66
69
|
<CommandDialog />
|
|
67
70
|
<Sidebar menu={sidebar} />
|
|
68
71
|
|
|
@@ -86,35 +89,30 @@ const actionItems = [
|
|
|
86
89
|
</button>
|
|
87
90
|
|
|
88
91
|
<div
|
|
89
|
-
class="input-group h-8 w-full min-w-0 max-w-72 cursor-text
|
|
92
|
+
class="input-group h-8 w-full min-w-0 max-w-72 cursor-text ml-auto"
|
|
90
93
|
role="button"
|
|
91
94
|
tabindex="0"
|
|
92
95
|
aria-label="Search docs"
|
|
93
96
|
onclick="window.rsdOpenCommandSearch ? window.rsdOpenCommandSearch() : document.getElementById('command-search')?.showModal()"
|
|
94
97
|
onkeydown="if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); window.rsdOpenCommandSearch ? window.rsdOpenCommandSearch() : document.getElementById('command-search')?.showModal(); }"
|
|
95
98
|
>
|
|
96
|
-
<input
|
|
97
|
-
|
|
98
|
-
|
|
99
|
+
<input
|
|
100
|
+
type="text"
|
|
101
|
+
placeholder="Search..."
|
|
102
|
+
readonly
|
|
103
|
+
tabindex="-1"
|
|
104
|
+
aria-hidden="true"
|
|
105
|
+
/>
|
|
106
|
+
<span data-align="start" aria-hidden="true"
|
|
107
|
+
><Fragment set:html={searchIcon} /></span
|
|
108
|
+
>
|
|
109
|
+
<span data-align="end" aria-hidden="true"
|
|
110
|
+
><kbd class="kbd">⌘K</kbd></span
|
|
111
|
+
>
|
|
99
112
|
</div>
|
|
100
113
|
|
|
101
114
|
<ContentHeader site={site} page={page} />
|
|
102
115
|
<ThemeToggle />
|
|
103
|
-
|
|
104
|
-
<div class="sm:hidden">
|
|
105
|
-
<button
|
|
106
|
-
type="button"
|
|
107
|
-
class="btn peer group"
|
|
108
|
-
data-variant="ghost"
|
|
109
|
-
data-size="icon-sm"
|
|
110
|
-
aria-label="Toggle sidebar"
|
|
111
|
-
aria-expanded="false"
|
|
112
|
-
aria-controls="sidebar"
|
|
113
|
-
onclick="document.getElementById('sidebar')?.toggle()"
|
|
114
|
-
>
|
|
115
|
-
<Fragment set:html={Menu} />
|
|
116
|
-
</button>
|
|
117
|
-
</div>
|
|
118
116
|
</div>
|
|
119
117
|
</header>
|
|
120
118
|
|
|
@@ -122,9 +120,17 @@ const actionItems = [
|
|
|
122
120
|
<article class="min-w-0 flex-1 wrap-break-word">
|
|
123
121
|
<div class="mx-auto w-full max-w-2xl flex-1 space-y-10">
|
|
124
122
|
<header class="space-y-2">
|
|
125
|
-
<div
|
|
126
|
-
|
|
127
|
-
|
|
123
|
+
<div
|
|
124
|
+
class="flex flex-col-reverse items-start gap-3 sm:flex-row sm:gap-x-2"
|
|
125
|
+
>
|
|
126
|
+
<h1
|
|
127
|
+
class="mr-auto text-3xl font-semibold tracking-tight sm:text-4xl"
|
|
128
|
+
>
|
|
129
|
+
{page.title}
|
|
130
|
+
</h1>
|
|
131
|
+
<div
|
|
132
|
+
class="flex w-full items-center justify-end gap-x-2 sm:mt-1 sm:w-auto"
|
|
133
|
+
>
|
|
128
134
|
<div role="group" class="button-group">
|
|
129
135
|
<button
|
|
130
136
|
type="button"
|
|
@@ -143,16 +149,30 @@ const actionItems = [
|
|
|
143
149
|
<div role="group" class="button-group">
|
|
144
150
|
{
|
|
145
151
|
navigation.prev && (
|
|
146
|
-
<a
|
|
152
|
+
<a
|
|
153
|
+
href={navigation.prev.path}
|
|
154
|
+
class="btn"
|
|
155
|
+
data-variant="secondary"
|
|
156
|
+
data-size="icon-sm"
|
|
157
|
+
>
|
|
147
158
|
<span class="sr-only">{navigation.prev.title}</span>
|
|
148
159
|
<Fragment set:html={ArrowLeft} />
|
|
149
160
|
</a>
|
|
150
161
|
)
|
|
151
162
|
}
|
|
152
|
-
{
|
|
163
|
+
{
|
|
164
|
+
navigation.prev && navigation.next && (
|
|
165
|
+
<hr role="separator" />
|
|
166
|
+
)
|
|
167
|
+
}
|
|
153
168
|
{
|
|
154
169
|
navigation.next && (
|
|
155
|
-
<a
|
|
170
|
+
<a
|
|
171
|
+
href={navigation.next.path}
|
|
172
|
+
class="btn"
|
|
173
|
+
data-variant="secondary"
|
|
174
|
+
data-size="icon-sm"
|
|
175
|
+
>
|
|
156
176
|
<span class="sr-only">{navigation.next.title}</span>
|
|
157
177
|
<Fragment set:html={ArrowRight} />
|
|
158
178
|
</a>
|
|
@@ -168,7 +188,12 @@ const actionItems = [
|
|
|
168
188
|
<footer class="mt-12 flex items-center">
|
|
169
189
|
{
|
|
170
190
|
navigation.prev && (
|
|
171
|
-
<a
|
|
191
|
+
<a
|
|
192
|
+
href={navigation.prev.path}
|
|
193
|
+
class="btn mr-auto"
|
|
194
|
+
data-variant="secondary"
|
|
195
|
+
data-size="sm"
|
|
196
|
+
>
|
|
172
197
|
<Fragment set:html={ArrowLeft} />
|
|
173
198
|
{navigation.prev.title}
|
|
174
199
|
</a>
|
|
@@ -176,7 +201,12 @@ const actionItems = [
|
|
|
176
201
|
}
|
|
177
202
|
{
|
|
178
203
|
navigation.next && (
|
|
179
|
-
<a
|
|
204
|
+
<a
|
|
205
|
+
href={navigation.next.path}
|
|
206
|
+
class="btn ml-auto"
|
|
207
|
+
data-variant="secondary"
|
|
208
|
+
data-size="sm"
|
|
209
|
+
>
|
|
180
210
|
{navigation.next.title}
|
|
181
211
|
<Fragment set:html={ArrowRight} />
|
|
182
212
|
</a>
|
|
@@ -186,15 +216,18 @@ const actionItems = [
|
|
|
186
216
|
</div>
|
|
187
217
|
</article>
|
|
188
218
|
<aside class="hidden w-56 xl:block">
|
|
189
|
-
<div
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
219
|
+
<div
|
|
220
|
+
class="docs-toc sticky top-20 space-y-2 text-sm [&_a]:text-muted-foreground [&_a]:hover:text-foreground [&_a[aria-current='location']]:text-foreground [&_ol]:space-y-2 [&_ol_ol]:mt-2 [&_ol_ol]:pl-3"
|
|
221
|
+
data-docs-toc
|
|
222
|
+
>
|
|
223
|
+
<h2 class="flex items-center gap-1 font-medium">On this page</h2>
|
|
193
224
|
<ol>
|
|
194
225
|
{
|
|
195
226
|
headings.map((heading) => (
|
|
196
227
|
<li class={heading.depth === 3 ? "pl-3" : ""}>
|
|
197
|
-
<a href={`#${heading.id}`} data-toc-link>
|
|
228
|
+
<a href={`#${heading.id}`} data-toc-link>
|
|
229
|
+
{heading.text}
|
|
230
|
+
</a>
|
|
198
231
|
</li>
|
|
199
232
|
))
|
|
200
233
|
}
|
|
@@ -212,7 +245,10 @@ const actionItems = [
|
|
|
212
245
|
const originalIcon = icon?.outerHTML || "";
|
|
213
246
|
|
|
214
247
|
try {
|
|
215
|
-
const response = await fetch(markdownUrl, {
|
|
248
|
+
const response = await fetch(markdownUrl, {
|
|
249
|
+
credentials: "same-origin",
|
|
250
|
+
cache: "no-store",
|
|
251
|
+
});
|
|
216
252
|
if (!response.ok) throw new Error("Failed to fetch markdown");
|
|
217
253
|
const markdown = await response.text();
|
|
218
254
|
await writeToClipboard(markdown);
|
|
@@ -220,11 +256,16 @@ const actionItems = [
|
|
|
220
256
|
icon.outerHTML = checkIcon;
|
|
221
257
|
window.setTimeout(() => {
|
|
222
258
|
const currentIcon = button.querySelector("svg");
|
|
223
|
-
if (currentIcon && originalIcon)
|
|
259
|
+
if (currentIcon && originalIcon)
|
|
260
|
+
currentIcon.outerHTML = originalIcon;
|
|
224
261
|
}, 1200);
|
|
225
262
|
}
|
|
226
263
|
} catch (_) {
|
|
227
|
-
window.open(
|
|
264
|
+
window.open(
|
|
265
|
+
new URL(markdownUrl, window.location.href).toString(),
|
|
266
|
+
"_blank",
|
|
267
|
+
"noopener",
|
|
268
|
+
);
|
|
228
269
|
}
|
|
229
270
|
};
|
|
230
271
|
|
|
@@ -275,7 +316,8 @@ const actionItems = [
|
|
|
275
316
|
let current = entries[0];
|
|
276
317
|
|
|
277
318
|
for (const entry of entries) {
|
|
278
|
-
if (entry.heading.getBoundingClientRect().top <= marker)
|
|
319
|
+
if (entry.heading.getBoundingClientRect().top <= marker)
|
|
320
|
+
current = entry;
|
|
279
321
|
else break;
|
|
280
322
|
}
|
|
281
323
|
|
|
@@ -302,7 +344,9 @@ const actionItems = [
|
|
|
302
344
|
activeFromScroll();
|
|
303
345
|
window.addEventListener("scroll", queueActiveUpdate, { passive: true });
|
|
304
346
|
window.addEventListener("resize", queueActiveUpdate);
|
|
305
|
-
window.addEventListener("hashchange", () =>
|
|
347
|
+
window.addEventListener("hashchange", () =>
|
|
348
|
+
window.setTimeout(activeFromScroll, 0),
|
|
349
|
+
);
|
|
306
350
|
}
|
|
307
351
|
|
|
308
352
|
initDocsToc();
|
|
@@ -1,25 +1,33 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { highlightCode } from "../../lib/highlight.js";
|
|
3
|
+
import { decodeHtml } from "../../lib/html.js";
|
|
3
4
|
|
|
4
|
-
const { lang = "text", title, class: className = "" } = Astro.props;
|
|
5
|
+
const { lang = "text", title, code, class: className = "" } = Astro.props;
|
|
5
6
|
const slotHtml = Astro.slots.has("default") ? await Astro.slots.render("default") : "";
|
|
6
|
-
const rawCode = decodeSlotText(slotHtml);
|
|
7
|
+
const rawCode = typeof code === "string" ? code.trim() : decodeSlotText(slotHtml);
|
|
7
8
|
const html = await highlightCode(rawCode, lang);
|
|
8
9
|
|
|
9
10
|
function decodeSlotText(value) {
|
|
10
|
-
|
|
11
|
+
const text = String(value || "")
|
|
11
12
|
.replace(/^<p>/, "")
|
|
12
|
-
.replace(/<\/p>$/, "")
|
|
13
|
-
|
|
14
|
-
.replace(/>/g, ">")
|
|
15
|
-
.replace(/&/g, "&")
|
|
16
|
-
.replace(/"/g, '"')
|
|
17
|
-
.replace(/'/g, "'")
|
|
18
|
-
.trim();
|
|
13
|
+
.replace(/<\/p>$/, "");
|
|
14
|
+
return decodeHtml(text).trim();
|
|
19
15
|
}
|
|
20
16
|
---
|
|
21
17
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
18
|
+
{
|
|
19
|
+
title ? (
|
|
20
|
+
<div class:list={["code-frame", className]} data-code-title={title}>
|
|
21
|
+
<header>
|
|
22
|
+
<span>{title}</span>
|
|
23
|
+
</header>
|
|
24
|
+
<div class="code-block">
|
|
25
|
+
<Fragment set:html={html} />
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
) : (
|
|
29
|
+
<div class:list={["code-block", className]}>
|
|
30
|
+
<Fragment set:html={html} />
|
|
31
|
+
</div>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
+
import { decodeHtml } from "../../lib/html.js";
|
|
3
|
+
|
|
2
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") : "";
|
|
@@ -7,32 +9,123 @@ const defaultValue = normalizedTabs[0]?.value;
|
|
|
7
9
|
|
|
8
10
|
function parseCodeTabs(html) {
|
|
9
11
|
const blocks = [];
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const label = decodeHtml(match[1]).trim();
|
|
15
|
-
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;
|
|
16
16
|
blocks.push({
|
|
17
17
|
label,
|
|
18
18
|
value: label,
|
|
19
|
-
html:
|
|
19
|
+
html: code.html,
|
|
20
20
|
});
|
|
21
21
|
}
|
|
22
22
|
return blocks;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
function
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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;
|
|
32
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
|
+
|
|
33
126
|
---
|
|
34
127
|
|
|
35
|
-
<div class:list={["
|
|
128
|
+
<div class:list={["tabs-frame", className]}>
|
|
36
129
|
<header>
|
|
37
130
|
<div class="tabs">
|
|
38
131
|
<nav role="tablist" aria-orientation="horizontal" data-variant="line">
|
|
@@ -62,6 +155,7 @@ function decodeHtml(value) {
|
|
|
62
155
|
return (
|
|
63
156
|
<div
|
|
64
157
|
role="tabpanel"
|
|
158
|
+
data-code-panel
|
|
65
159
|
id={`${id}-panel-${index + 1}`}
|
|
66
160
|
aria-labelledby={`${id}-tab-${index + 1}`}
|
|
67
161
|
tabindex="-1"
|
|
@@ -7,7 +7,7 @@ const html = await highlightCode(code, lang);
|
|
|
7
7
|
const previewClassName = className || "w-fit";
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
-
<section class="
|
|
10
|
+
<section class="tabs-frame">
|
|
11
11
|
<header>
|
|
12
12
|
<div class="tabs">
|
|
13
13
|
<nav role="tablist" aria-orientation="horizontal" data-variant="line">
|
package/src/runtime/lib/docs.js
CHANGED
|
@@ -4,6 +4,9 @@ import { marked } from "marked";
|
|
|
4
4
|
import GithubSlugger from "github-slugger";
|
|
5
5
|
import { codeToHtml } from "shiki";
|
|
6
6
|
import * as lucideIcons from "lucide-static";
|
|
7
|
+
import { transformBasecoatTablesHtml } from "../../astro/rehype-basecoat-tables.js";
|
|
8
|
+
import { parseCodeMeta } from "./code-meta.js";
|
|
9
|
+
import { escapeHtml } from "./html.js";
|
|
7
10
|
import { normalizeDocMarkdown } from "./markdown-export.js";
|
|
8
11
|
import { markdownPath, routePath } from "./paths.js";
|
|
9
12
|
|
|
@@ -102,25 +105,10 @@ export async function renderDoc(config, page) {
|
|
|
102
105
|
return `<h${depth} id="${id}" tabindex="-1"><a class="header-anchor" href="#${id}">${text}</a></h${depth}>`;
|
|
103
106
|
};
|
|
104
107
|
|
|
105
|
-
renderer.table = (token) => {
|
|
106
|
-
const header = renderer.tablerow({
|
|
107
|
-
text: token.header.map((cell) => renderer.tablecell(cell)).join(""),
|
|
108
|
-
});
|
|
109
|
-
const body = token.rows
|
|
110
|
-
.map((row) =>
|
|
111
|
-
renderer.tablerow({
|
|
112
|
-
text: row.map((cell) => renderer.tablecell(cell)).join(""),
|
|
113
|
-
}),
|
|
114
|
-
)
|
|
115
|
-
.join("");
|
|
116
|
-
const tbody = body ? `<tbody>${body}</tbody>` : "";
|
|
117
|
-
return `<div class="relative my-6 w-full overflow-auto"><table><thead>${header}</thead>${tbody}</table></div>`;
|
|
118
|
-
};
|
|
119
|
-
|
|
120
108
|
const tokens = marked.lexer(content);
|
|
121
109
|
await highlightCode(tokens);
|
|
122
110
|
const html = marked.parser(tokens, { renderer });
|
|
123
|
-
return { page, html, headings };
|
|
111
|
+
return { page, html: await transformBasecoatTablesHtml(html), headings };
|
|
124
112
|
}
|
|
125
113
|
|
|
126
114
|
export function getDocHeadings(config, page) {
|
|
@@ -374,7 +362,7 @@ function normalizeDocSource(source) {
|
|
|
374
362
|
async function highlightCode(tokens) {
|
|
375
363
|
for (const token of tokens) {
|
|
376
364
|
if (token.type === "code") {
|
|
377
|
-
const html = await highlightToken(token);
|
|
365
|
+
const html = codeBlockHtml(token, await highlightToken(token));
|
|
378
366
|
Object.assign(token, {
|
|
379
367
|
type: "html",
|
|
380
368
|
raw: html,
|
|
@@ -390,6 +378,12 @@ async function highlightCode(tokens) {
|
|
|
390
378
|
}
|
|
391
379
|
}
|
|
392
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
|
+
|
|
393
387
|
async function highlightToken(token) {
|
|
394
388
|
try {
|
|
395
389
|
return await codeToHtml(token.text, {
|
package/src/runtime/lib/html.js
CHANGED
|
@@ -6,6 +6,15 @@ export function escapeHtml(value) {
|
|
|
6
6
|
.replace(/"/g, """);
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
export function decodeHtml(value) {
|
|
10
|
+
return String(value ?? "")
|
|
11
|
+
.replace(/</g, "<")
|
|
12
|
+
.replace(/>/g, ">")
|
|
13
|
+
.replace(/"/g, '"')
|
|
14
|
+
.replace(/'/g, "'")
|
|
15
|
+
.replace(/&/g, "&");
|
|
16
|
+
}
|
|
17
|
+
|
|
9
18
|
export function attrs(values = {}) {
|
|
10
19
|
return Object.entries(values)
|
|
11
20
|
.filter(([, value]) => value !== undefined && value !== null && value !== false)
|
|
@@ -56,9 +56,29 @@ export function normalizeMdxComponents(content, context = new Map()) {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
function replacePairedComponent(content, name, serialize) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
);
|
|
59
|
+
let output = "";
|
|
60
|
+
let cursor = 0;
|
|
61
|
+
const source = String(content || "");
|
|
62
|
+
|
|
63
|
+
while (cursor < source.length) {
|
|
64
|
+
const open = findNextTag(source, name, cursor);
|
|
65
|
+
if (!open) {
|
|
66
|
+
output += source.slice(cursor);
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const close = findMatchingCloseTag(source, open);
|
|
71
|
+
if (!close) {
|
|
72
|
+
output += source.slice(cursor);
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
output += source.slice(cursor, open.start);
|
|
77
|
+
output += serialize(open.attrs, source.slice(open.end, close.start));
|
|
78
|
+
cursor = close.end;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return output;
|
|
62
82
|
}
|
|
63
83
|
|
|
64
84
|
function serializeChildren(_attrs, children, context = new Map()) {
|
|
@@ -155,6 +175,94 @@ function longestBacktickRun(value) {
|
|
|
155
175
|
return Math.max(0, ...String(value || "").matchAll(/`+/g).map((match) => match[0].length));
|
|
156
176
|
}
|
|
157
177
|
|
|
178
|
+
function findMatchingCloseTag(source, open) {
|
|
179
|
+
let depth = 1;
|
|
180
|
+
let cursor = open.end;
|
|
181
|
+
|
|
182
|
+
while (cursor < source.length) {
|
|
183
|
+
const tag = findNextTag(source, open.name, cursor);
|
|
184
|
+
if (!tag) return null;
|
|
185
|
+
if (tag.closing) depth -= 1;
|
|
186
|
+
else if (!tag.selfClosing) depth += 1;
|
|
187
|
+
if (depth === 0) return tag;
|
|
188
|
+
cursor = tag.end;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function findNextTag(source, name, fromIndex) {
|
|
195
|
+
let cursor = fromIndex;
|
|
196
|
+
|
|
197
|
+
while (cursor < source.length) {
|
|
198
|
+
const start = source.indexOf("<", cursor);
|
|
199
|
+
if (start === -1) return null;
|
|
200
|
+
const tag = readTag(source, start);
|
|
201
|
+
if (!tag) {
|
|
202
|
+
cursor = start + 1;
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (tag.name === name) return tag;
|
|
206
|
+
if (!tag.closing && !tag.selfClosing && tag.name.toLowerCase() === "script") {
|
|
207
|
+
const scriptEnd = source.toLowerCase().indexOf("</script>", tag.end);
|
|
208
|
+
cursor = scriptEnd === -1 ? source.length : scriptEnd + "</script>".length;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
cursor = tag.end;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function readTag(source, start) {
|
|
218
|
+
let cursor = start + 1;
|
|
219
|
+
let closing = false;
|
|
220
|
+
|
|
221
|
+
if (source[cursor] === "/") {
|
|
222
|
+
closing = true;
|
|
223
|
+
cursor += 1;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const nameStart = cursor;
|
|
227
|
+
while (/[$A-Z_a-z0-9-]/.test(source[cursor] || "")) cursor += 1;
|
|
228
|
+
const name = source.slice(nameStart, cursor);
|
|
229
|
+
if (!name) return null;
|
|
230
|
+
|
|
231
|
+
let quote = "";
|
|
232
|
+
while (cursor < source.length) {
|
|
233
|
+
const char = source[cursor];
|
|
234
|
+
if (quote) {
|
|
235
|
+
if (char === "\\" && source[cursor + 1]) {
|
|
236
|
+
cursor += 2;
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
if (char === quote) quote = "";
|
|
240
|
+
cursor += 1;
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
244
|
+
quote = char;
|
|
245
|
+
cursor += 1;
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
if (char === ">") {
|
|
249
|
+
const sourceTag = source.slice(start, cursor + 1);
|
|
250
|
+
const beforeEnd = sourceTag.trimEnd();
|
|
251
|
+
return {
|
|
252
|
+
start,
|
|
253
|
+
end: cursor + 1,
|
|
254
|
+
name,
|
|
255
|
+
closing,
|
|
256
|
+
selfClosing: beforeEnd.endsWith("/>"),
|
|
257
|
+
attrs: closing ? "" : sourceTag.slice(name.length + 1, -1).replace(/\/$/, ""),
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
cursor += 1;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
|
|
158
266
|
function stripCommonIndent(value) {
|
|
159
267
|
const lines = String(value || "").replace(/^\n+|\n+$/g, "").split("\n");
|
|
160
268
|
const indents = lines.filter((line) => line.trim()).map((line) => line.match(/^[ \t]*/)[0].length);
|