reallysimpledocs 1.0.0-beta.5 → 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 +52 -32
- package/package.json +7 -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 +0 -16
- 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
|
@@ -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);
|