@vx-oss/docs-satteri 1.0.3
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/LICENSE +21 -0
- package/dist/compile-SBpZsgmI.d.ts +42 -0
- package/dist/compile.d.ts +2 -0
- package/dist/compile.js +63 -0
- package/dist/index-AvS9vC2S.d.ts +29 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/local-md/client.d.ts +2 -0
- package/dist/local-md/client.js +2 -0
- package/dist/local-md/dev/react-client.d.ts +2 -0
- package/dist/local-md/dev/react-client.js +2 -0
- package/dist/local-md/dev/vite.d.ts +2 -0
- package/dist/local-md/dev/vite.js +2 -0
- package/dist/local-md/dev/ws.d.ts +2 -0
- package/dist/local-md/dev/ws.js +2 -0
- package/dist/local-md/index.d.ts +70 -0
- package/dist/local-md/index.js +104 -0
- package/dist/preset-DdTDKnqd.js +104 -0
- package/dist/preset-DzMkd76i.d.ts +35 -0
- package/dist/preset.d.ts +2 -0
- package/dist/preset.js +2 -0
- package/dist/rehype-code-CLGqDUSe.d.ts +13 -0
- package/dist/rehype-code.d.ts +2 -0
- package/dist/rehype-code.js +79 -0
- package/dist/rehype-katex-h_E-WUwT.d.ts +15 -0
- package/dist/rehype-katex.d.ts +2 -0
- package/dist/rehype-katex.js +52 -0
- package/dist/rehype-toc-wT8IcPFi.d.ts +33 -0
- package/dist/rehype-toc.d.ts +2 -0
- package/dist/rehype-toc.js +84 -0
- package/dist/remark-admonition.d.ts +13 -0
- package/dist/remark-admonition.js +79 -0
- package/dist/remark-auto-type-table.d.ts +9 -0
- package/dist/remark-auto-type-table.js +189 -0
- package/dist/remark-block-id.d.ts +18 -0
- package/dist/remark-block-id.js +46 -0
- package/dist/remark-code-tab-BYruQJUs.d.ts +15 -0
- package/dist/remark-code-tab.d.ts +2 -0
- package/dist/remark-code-tab.js +170 -0
- package/dist/remark-directive-admonition.d.ts +13 -0
- package/dist/remark-directive-admonition.js +54 -0
- package/dist/remark-feedback-block.d.ts +18 -0
- package/dist/remark-feedback-block.js +51 -0
- package/dist/remark-heading-CYhI2Dkg.d.ts +15 -0
- package/dist/remark-heading.d.ts +2 -0
- package/dist/remark-heading.js +43 -0
- package/dist/remark-image-C28nbosW.d.ts +29 -0
- package/dist/remark-image.d.ts +2 -0
- package/dist/remark-image.js +141 -0
- package/dist/remark-include.d.ts +22 -0
- package/dist/remark-include.js +336 -0
- package/dist/remark-llms.d.ts +36 -0
- package/dist/remark-llms.js +191 -0
- package/dist/remark-mdx-mermaid.d.ts +10 -0
- package/dist/remark-mdx-mermaid.js +22 -0
- package/dist/remark-npm-DdmTKhyi.d.ts +18 -0
- package/dist/remark-npm.d.ts +2 -0
- package/dist/remark-npm.js +72 -0
- package/dist/remark-steps.d.ts +14 -0
- package/dist/remark-steps.js +127 -0
- package/dist/remark-structure.d.ts +29 -0
- package/dist/remark-structure.js +94 -0
- package/dist/remark-ts2js.d.ts +25 -0
- package/dist/remark-ts2js.js +56 -0
- package/dist/renderer-BtFWPZB8.js +54 -0
- package/dist/renderer-E5KzV19n.d.ts +30 -0
- package/dist/stringifier-COIWcXys.js +130 -0
- package/dist/utils-I3DkmAkd.js +27 -0
- package/package.json +124 -0
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import { i as replaceSource } from "./stringifier-COIWcXys.js";
|
|
2
|
+
import { t as flattenNode } from "./utils-I3DkmAkd.js";
|
|
3
|
+
import Slugger from "github-slugger";
|
|
4
|
+
import { defineMdastPlugin, markdownToMdast, mdxToMdast } from "satteri";
|
|
5
|
+
import * as path$1 from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { frontmatter } from "@vx-oss/docs-core/content/md/frontmatter";
|
|
8
|
+
import { readFile } from "node:fs/promises";
|
|
9
|
+
//#region src/remark-include.ts
|
|
10
|
+
const INCLUDE_TYPES = /* @__PURE__ */ new Set([
|
|
11
|
+
"mdxJsxFlowElement",
|
|
12
|
+
"mdxJsxTextElement",
|
|
13
|
+
"containerDirective",
|
|
14
|
+
"leafDirective",
|
|
15
|
+
"textDirective"
|
|
16
|
+
]);
|
|
17
|
+
const PARSE_FEATURES = {
|
|
18
|
+
gfm: true,
|
|
19
|
+
directive: true,
|
|
20
|
+
headingAttributes: true
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Sätteri port of @vx-oss/docs-mdx's `remark-include`: embed other documents with
|
|
24
|
+
* `<include>./file.mdx</include>` (or the `:::include` / `::include`
|
|
25
|
+
* directives), sections with `<include>./file.mdx#section</include>`, and
|
|
26
|
+
* non-markdown files as code blocks — with VS Code-style `#region` extraction.
|
|
27
|
+
*
|
|
28
|
+
* Nested includes are resolved recursively, relative to the file they appear in.
|
|
29
|
+
*/
|
|
30
|
+
function remarkInclude({ cwd } = {}) {
|
|
31
|
+
async function visit(node, ctx) {
|
|
32
|
+
if (node.name !== "include") return;
|
|
33
|
+
const result = await replaceInclude(node, ctx, cwd);
|
|
34
|
+
if (!result) return;
|
|
35
|
+
replaceSource(ctx, node, result.markdown);
|
|
36
|
+
return result.replacement;
|
|
37
|
+
}
|
|
38
|
+
async function visitInline(node, ctx) {
|
|
39
|
+
if (node.name !== "include") return;
|
|
40
|
+
const result = await replaceInclude(node, ctx, cwd);
|
|
41
|
+
if (!result) return;
|
|
42
|
+
const parent = ctx.parent(node);
|
|
43
|
+
if (parent?.type === "paragraph") {
|
|
44
|
+
replaceSource(ctx, parent, result.markdown);
|
|
45
|
+
ctx.replaceNode(parent, result.replacement);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
replaceSource(ctx, node, result.markdown);
|
|
49
|
+
return result.replacement;
|
|
50
|
+
}
|
|
51
|
+
return defineMdastPlugin({
|
|
52
|
+
name: "remark-include",
|
|
53
|
+
mdxJsxFlowElement: visit,
|
|
54
|
+
containerDirective: visit,
|
|
55
|
+
leafDirective: visit,
|
|
56
|
+
mdxJsxTextElement: visitInline,
|
|
57
|
+
textDirective: visitInline
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
async function replaceInclude(node, ctx, cwdOption) {
|
|
61
|
+
const specifier = ctx.textContent(node).trim();
|
|
62
|
+
if (!specifier) return;
|
|
63
|
+
const attributes = parseElementAttributes(node);
|
|
64
|
+
const { file: relativePath, section } = parseSpecifier(specifier);
|
|
65
|
+
const filePath = ctx.fileURL ? fileURLToPath(ctx.fileURL) : void 0;
|
|
66
|
+
const cwd = cwdOption ?? process.cwd();
|
|
67
|
+
const baseDir = "cwd" in attributes || !filePath ? cwd : path$1.dirname(filePath);
|
|
68
|
+
const targetPath = path$1.resolve(baseDir, relativePath);
|
|
69
|
+
const state = {
|
|
70
|
+
cwd,
|
|
71
|
+
addDependency: (file) => ctx.data._compiler?.addDependency(file),
|
|
72
|
+
stack: filePath ? [filePath] : []
|
|
73
|
+
};
|
|
74
|
+
if (isCodeInclude(targetPath, attributes)) {
|
|
75
|
+
const code = await readCodeInclude(targetPath, section, attributes, state);
|
|
76
|
+
return {
|
|
77
|
+
replacement: code,
|
|
78
|
+
markdown: () => toCodeFence(code)
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const raw = await readMarkdownInclude(targetPath, section, state);
|
|
82
|
+
return {
|
|
83
|
+
replacement: { raw },
|
|
84
|
+
markdown: () => stripEsm(raw, path$1.extname(targetPath))
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Drop top-level ESM nodes from included content: they stay in the compiled
|
|
89
|
+
* document, but are noise in Markdown output.
|
|
90
|
+
*/
|
|
91
|
+
function stripEsm(text, ext) {
|
|
92
|
+
if (ext !== ".mdx") return text;
|
|
93
|
+
let out = "";
|
|
94
|
+
let cursor = 0;
|
|
95
|
+
for (const node of parseTree(text, ext).children) {
|
|
96
|
+
if (node.type !== "mdxjsEsm") continue;
|
|
97
|
+
const start = node.position?.start.offset;
|
|
98
|
+
const end = node.position?.end.offset;
|
|
99
|
+
if (start === void 0 || end === void 0) continue;
|
|
100
|
+
out += text.slice(cursor, start);
|
|
101
|
+
cursor = end;
|
|
102
|
+
while (text[cursor] === "\n" && (out === "" || out.endsWith("\n\n"))) cursor++;
|
|
103
|
+
}
|
|
104
|
+
return out + text.slice(cursor);
|
|
105
|
+
}
|
|
106
|
+
function isCodeInclude(targetPath, attributes) {
|
|
107
|
+
if (attributes.lang) return true;
|
|
108
|
+
const ext = path$1.extname(targetPath);
|
|
109
|
+
return ext !== ".md" && ext !== ".mdx";
|
|
110
|
+
}
|
|
111
|
+
async function readCodeInclude(targetPath, section, attributes, state) {
|
|
112
|
+
const content = await readInclude(targetPath, state);
|
|
113
|
+
let value = content;
|
|
114
|
+
if (section) value = extractCodeRegion(content, section.trim(), targetPath);
|
|
115
|
+
return {
|
|
116
|
+
type: "code",
|
|
117
|
+
lang: attributes.lang ?? path$1.extname(targetPath).slice(1),
|
|
118
|
+
meta: attributes.meta ?? void 0,
|
|
119
|
+
value
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
async function readMarkdownInclude(targetPath, section, state) {
|
|
123
|
+
if (state.stack.includes(targetPath)) throw new Error(`Circular include detected: ${[...state.stack, targetPath].join(" -> ")}`);
|
|
124
|
+
const content = await readInclude(targetPath, state);
|
|
125
|
+
const body = frontmatter(content).content;
|
|
126
|
+
const ext = path$1.extname(targetPath);
|
|
127
|
+
let raw;
|
|
128
|
+
if (section) {
|
|
129
|
+
const extracted = extractSectionRaw(parseTree(body, ext).children, section, body);
|
|
130
|
+
if (extracted === void 0) throw new Error(`Cannot find section ${section} in ${targetPath}, make sure you have encapsulated the section in a <section id="${section}"> tag, or a :::section directive with remark-directive configured.`);
|
|
131
|
+
raw = extracted;
|
|
132
|
+
} else raw = body.trimEnd();
|
|
133
|
+
state.stack.push(targetPath);
|
|
134
|
+
try {
|
|
135
|
+
return await expandNestedIncludes(raw, targetPath, state);
|
|
136
|
+
} finally {
|
|
137
|
+
state.stack.pop();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async function readInclude(file, state) {
|
|
141
|
+
state.addDependency(file);
|
|
142
|
+
try {
|
|
143
|
+
return await readFile(file, "utf-8");
|
|
144
|
+
} catch (e) {
|
|
145
|
+
const from = state.stack.at(-1);
|
|
146
|
+
throw new Error(`failed to read file ${file}${from ? ` (included from ${from})` : ""}\n${e instanceof Error ? e.message : String(e)}`, { cause: e });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* The visitor pass only sees the entry document, so includes inside included
|
|
151
|
+
* content must be expanded here: parse the content, then splice each nested
|
|
152
|
+
* include's replacement text over its source span (back to front, so earlier
|
|
153
|
+
* offsets stay valid).
|
|
154
|
+
*/
|
|
155
|
+
async function expandNestedIncludes(text, file, state) {
|
|
156
|
+
const tree = parseTree(text, path$1.extname(file));
|
|
157
|
+
const targets = [];
|
|
158
|
+
collectIncludes(tree.children, targets);
|
|
159
|
+
if (targets.length === 0) return text;
|
|
160
|
+
let out = text;
|
|
161
|
+
for (let i = targets.length - 1; i >= 0; i--) {
|
|
162
|
+
const target = targets[i];
|
|
163
|
+
const replacement = await resolveNestedInclude(target.node, file, state);
|
|
164
|
+
if (replacement === void 0) continue;
|
|
165
|
+
out = out.slice(0, target.start) + replacement + out.slice(target.end);
|
|
166
|
+
}
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
169
|
+
function collectIncludes(nodes, targets) {
|
|
170
|
+
for (const node of nodes) {
|
|
171
|
+
if (INCLUDE_TYPES.has(node.type) && node.name === "include") {
|
|
172
|
+
const start = node.position?.start.offset;
|
|
173
|
+
const end = node.position?.end.offset;
|
|
174
|
+
if (start !== void 0 && end !== void 0) targets.push({
|
|
175
|
+
node,
|
|
176
|
+
start,
|
|
177
|
+
end
|
|
178
|
+
});
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (Array.isArray(node.children)) collectIncludes(node.children, targets);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
async function resolveNestedInclude(node, fromFile, state) {
|
|
185
|
+
const specifier = flattenNode(node).trim();
|
|
186
|
+
if (!specifier) return;
|
|
187
|
+
const attributes = parseElementAttributes(node);
|
|
188
|
+
const { file: relativePath, section } = parseSpecifier(specifier);
|
|
189
|
+
const baseDir = "cwd" in attributes ? state.cwd : path$1.dirname(fromFile);
|
|
190
|
+
const targetPath = path$1.resolve(baseDir, relativePath);
|
|
191
|
+
if (isCodeInclude(targetPath, attributes)) return toCodeFence(await readCodeInclude(targetPath, section, attributes, state));
|
|
192
|
+
return readMarkdownInclude(targetPath, section, state);
|
|
193
|
+
}
|
|
194
|
+
function toCodeFence(code) {
|
|
195
|
+
const longestRun = (code.value.match(/`+/g) ?? []).reduce((max, run) => {
|
|
196
|
+
return Math.max(max, run.length);
|
|
197
|
+
}, 2);
|
|
198
|
+
const fence = "`".repeat(longestRun + 1);
|
|
199
|
+
return `${fence}${[code.lang ?? "", code.meta ?? ""].filter(Boolean).join(" ")}\n${code.value}\n${fence}`;
|
|
200
|
+
}
|
|
201
|
+
function parseTree(source, ext) {
|
|
202
|
+
return (ext === ".mdx" ? mdxToMdast : markdownToMdast)(source, { features: PARSE_FEATURES });
|
|
203
|
+
}
|
|
204
|
+
function parseElementAttributes(element) {
|
|
205
|
+
if (!element.attributes) return {};
|
|
206
|
+
if (Array.isArray(element.attributes)) {
|
|
207
|
+
const attributes = {};
|
|
208
|
+
for (const attr of element.attributes) if (attr.type === "mdxJsxAttribute" && (typeof attr.value === "string" || attr.value === null)) attributes[attr.name] = attr.value;
|
|
209
|
+
return attributes;
|
|
210
|
+
}
|
|
211
|
+
return element.attributes;
|
|
212
|
+
}
|
|
213
|
+
function parseSpecifier(specifier) {
|
|
214
|
+
const idx = specifier.lastIndexOf("#");
|
|
215
|
+
if (idx === -1) return { file: specifier };
|
|
216
|
+
return {
|
|
217
|
+
file: specifier.slice(0, idx),
|
|
218
|
+
section: specifier.slice(idx + 1)
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
function sliceByPosition(source, start, end) {
|
|
222
|
+
if (start === void 0 || end === void 0) return void 0;
|
|
223
|
+
return source.slice(start, end);
|
|
224
|
+
}
|
|
225
|
+
const customIdRegex = /\s*\[#(?<slug>[^]+?)]\s*$/;
|
|
226
|
+
function headingId(node, slugger) {
|
|
227
|
+
const dataId = node.data?.hProperties?.id;
|
|
228
|
+
if (dataId) return dataId;
|
|
229
|
+
const text = flattenNode(node);
|
|
230
|
+
const match = customIdRegex.exec(text);
|
|
231
|
+
if (match?.[1]) return match[1];
|
|
232
|
+
return slugger.slug(text);
|
|
233
|
+
}
|
|
234
|
+
function findSectionElement(nodes, section) {
|
|
235
|
+
for (const node of nodes) {
|
|
236
|
+
if ((node.type === "mdxJsxFlowElement" || node.type === "containerDirective") && node.name === "section" && parseElementAttributes(node).id === section) return node;
|
|
237
|
+
if (Array.isArray(node.children)) {
|
|
238
|
+
const found = findSectionElement(node.children, section);
|
|
239
|
+
if (found) return found;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
function extractSectionRaw(children, section, source) {
|
|
244
|
+
const element = findSectionElement(children, section);
|
|
245
|
+
if (element) {
|
|
246
|
+
const kids = element.children ?? [];
|
|
247
|
+
if (kids.length === 0) return "";
|
|
248
|
+
return sliceByPosition(source, kids[0]?.position?.start.offset, kids[kids.length - 1]?.position?.end.offset);
|
|
249
|
+
}
|
|
250
|
+
const slugger = new Slugger();
|
|
251
|
+
let capturing = false;
|
|
252
|
+
let start;
|
|
253
|
+
let end;
|
|
254
|
+
for (const node of children) {
|
|
255
|
+
if (node.type === "heading") {
|
|
256
|
+
if (capturing) break;
|
|
257
|
+
if (headingId(node, slugger) === section) {
|
|
258
|
+
capturing = true;
|
|
259
|
+
start = node.position?.start.offset;
|
|
260
|
+
end = node.position?.end.offset;
|
|
261
|
+
}
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
if (capturing) end = node.position?.end.offset ?? end;
|
|
265
|
+
}
|
|
266
|
+
return sliceByPosition(source, start, end);
|
|
267
|
+
}
|
|
268
|
+
const REGION_MARKERS = [
|
|
269
|
+
{
|
|
270
|
+
start: /^\s*\/\/\s*#?region\b\s*(.*?)\s*$/,
|
|
271
|
+
end: /^\s*\/\/\s*#?endregion\b\s*(.*?)\s*$/
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
start: /^\s*<!--\s*#?region\b\s*(.*?)\s*-->/,
|
|
275
|
+
end: /^\s*<!--\s*#?endregion\b\s*(.*?)\s*-->/
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
start: /^\s*\/\*\s*#region\b\s*(.*?)\s*\*\//,
|
|
279
|
+
end: /^\s*\/\*\s*#endregion\b\s*(.*?)\s*\*\//
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
start: /^\s*#[rR]egion\b\s*(.*?)\s*$/,
|
|
283
|
+
end: /^\s*#[eE]nd ?[rR]egion\b\s*(.*?)\s*$/
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
start: /^\s*#\s*#?region\b\s*(.*?)\s*$/,
|
|
287
|
+
end: /^\s*#\s*#?endregion\b\s*(.*?)\s*$/
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
start: /^\s*(?:--|::|@?REM)\s*#region\b\s*(.*?)\s*$/,
|
|
291
|
+
end: /^\s*(?:--|::|@?REM)\s*#endregion\b\s*(.*?)\s*$/
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
start: /^\s*#pragma\s+region\b\s*(.*?)\s*$/,
|
|
295
|
+
end: /^\s*#pragma\s+endregion\b\s*(.*?)\s*$/
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
start: /^\s*\(\*\s*#region\b\s*(.*?)\s*\*\)/,
|
|
299
|
+
end: /^\s*\(\*\s*#endregion\b\s*(.*?)\s*\*\)/
|
|
300
|
+
}
|
|
301
|
+
];
|
|
302
|
+
function dedent(lines) {
|
|
303
|
+
const minIndent = lines.reduce((min, line) => {
|
|
304
|
+
const match = line.match(/^(\s*)\S/);
|
|
305
|
+
return match ? Math.min(min, match[1].length) : min;
|
|
306
|
+
}, Infinity);
|
|
307
|
+
return minIndent === Infinity ? lines.join("\n") : lines.map((l) => l.slice(minIndent)).join("\n");
|
|
308
|
+
}
|
|
309
|
+
function extractCodeRegion(content, regionName, file) {
|
|
310
|
+
const lines = content.split("\n");
|
|
311
|
+
for (let i = 0; i < lines.length; i++) for (const re of REGION_MARKERS) {
|
|
312
|
+
let match = re.start.exec(lines[i]);
|
|
313
|
+
if (match?.[1] !== regionName) continue;
|
|
314
|
+
let depth = 1;
|
|
315
|
+
const extractedLines = [];
|
|
316
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
317
|
+
match = re.start.exec(lines[j]);
|
|
318
|
+
if (match) {
|
|
319
|
+
depth++;
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
match = re.end.exec(lines[j]);
|
|
323
|
+
if (match) {
|
|
324
|
+
if (match[1] === regionName) depth = 0;
|
|
325
|
+
else if (match[1] === "") depth--;
|
|
326
|
+
else continue;
|
|
327
|
+
if (depth > 0) continue;
|
|
328
|
+
return dedent(extractedLines);
|
|
329
|
+
}
|
|
330
|
+
extractedLines.push(lines[j]);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
throw new Error(`Region "${regionName}" not found in ${file}`);
|
|
334
|
+
}
|
|
335
|
+
//#endregion
|
|
336
|
+
export { remarkInclude };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { o as ExtraPluginHooks } from "./compile-SBpZsgmI.js";
|
|
2
|
+
import { MdastPluginEntry } from "satteri";
|
|
3
|
+
//#region src/remark-llms.d.ts
|
|
4
|
+
interface LLMsOptions {
|
|
5
|
+
/**
|
|
6
|
+
* export name for output Markdown.
|
|
7
|
+
*
|
|
8
|
+
* @default _markdown
|
|
9
|
+
*/
|
|
10
|
+
as?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Explicit heading IDs in output.
|
|
13
|
+
*
|
|
14
|
+
* @default true
|
|
15
|
+
*/
|
|
16
|
+
headingIds?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Form of the export:
|
|
19
|
+
*
|
|
20
|
+
* - `string`: the output Markdown.
|
|
21
|
+
* - `function`: a component: Markdown content is kept as authored source, while JSX elements
|
|
22
|
+
* stay as JSX, receiving their original props and resolving from `props.components`.
|
|
23
|
+
* Render it with `renderToMarkdown` from `@vx-oss/docs-core/server`, where a component
|
|
24
|
+
* can call `asMarkdown()` to define its own Markdown form.
|
|
25
|
+
*
|
|
26
|
+
* @default string
|
|
27
|
+
*/
|
|
28
|
+
output?: 'function' | 'string';
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Export the document as Markdown, sliced from the authored source: heading IDs are added,
|
|
32
|
+
* frontmatter & ESM nodes are dropped, and includes are replaced with their content.
|
|
33
|
+
*/
|
|
34
|
+
declare function remarkLlms({ as, headingIds, output }?: LLMsOptions): MdastPluginEntry & ExtraPluginHooks;
|
|
35
|
+
//#endregion
|
|
36
|
+
export { LLMsOptions, remarkLlms };
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { r as offsets, t as createStringifier } from "./stringifier-COIWcXys.js";
|
|
2
|
+
import { defineMdastPlugin } from "satteri";
|
|
3
|
+
//#region src/remark-llms.ts
|
|
4
|
+
const customIdRegex = /\s*\[#[^]+?]\s*$/;
|
|
5
|
+
const componentNameRegex = /^[A-Z][\w$]*$/;
|
|
6
|
+
/**
|
|
7
|
+
* Export the document as Markdown, sliced from the authored source: heading IDs are added,
|
|
8
|
+
* frontmatter & ESM nodes are dropped, and includes are replaced with their content.
|
|
9
|
+
*/
|
|
10
|
+
function remarkLlms({ as = "_markdown", headingIds = true, output = "string" } = {}) {
|
|
11
|
+
const jsx = output === "function";
|
|
12
|
+
const factory = () => {
|
|
13
|
+
const spans = [];
|
|
14
|
+
let s;
|
|
15
|
+
const drop = (node) => s.remove(node);
|
|
16
|
+
const collectJsx = (node) => {
|
|
17
|
+
if (!jsx || !node.name || !componentNameRegex.test(node.name)) return;
|
|
18
|
+
const pos = offsets(node);
|
|
19
|
+
if (!pos) return;
|
|
20
|
+
spans.push({
|
|
21
|
+
...pos,
|
|
22
|
+
name: node.name,
|
|
23
|
+
inline: node.type === "mdxJsxTextElement",
|
|
24
|
+
children: []
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
return defineMdastPlugin({
|
|
28
|
+
name: "remark-llms",
|
|
29
|
+
options: { position: true },
|
|
30
|
+
before(_root, ctx) {
|
|
31
|
+
s = createStringifier(ctx);
|
|
32
|
+
},
|
|
33
|
+
mdxjsEsm: drop,
|
|
34
|
+
yaml: drop,
|
|
35
|
+
toml: drop,
|
|
36
|
+
heading(node) {
|
|
37
|
+
const pos = offsets(node);
|
|
38
|
+
if (!pos) return;
|
|
39
|
+
const slice = s.source.slice(pos.start, pos.end);
|
|
40
|
+
const marker = customIdRegex.exec(slice);
|
|
41
|
+
if (!headingIds) {
|
|
42
|
+
if (marker) s.edit(pos.start + marker.index, pos.end, "");
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const id = node.data?.hProperties?.id;
|
|
46
|
+
if (typeof id === "string" && !marker) s.edit(pos.end, pos.end, ` [#${id}]`);
|
|
47
|
+
},
|
|
48
|
+
mdxJsxFlowElement: collectJsx,
|
|
49
|
+
mdxJsxTextElement: collectJsx,
|
|
50
|
+
after(root, ctx) {
|
|
51
|
+
const end = root.position?.end.offset ?? s.source.length;
|
|
52
|
+
if (!jsx) {
|
|
53
|
+
ctx.data.markdown ??= `${s.slice(0, end).trim()}\n`;
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (!as) return;
|
|
57
|
+
const roots = buildTree(spans, s.edits);
|
|
58
|
+
ctx.prependChild(root, {
|
|
59
|
+
type: "mdxjsEsm",
|
|
60
|
+
value: toComponentCode(as, childrenToCode(s, 0, end, roots), roots.length > 0)
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
return Object.assign(factory, { collectExports({ data, addExport }) {
|
|
66
|
+
if (as && !jsx) addExport(as, JSON.stringify(data.markdown ?? ""));
|
|
67
|
+
} });
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Nest the pre-order span list by containment, dropping spans that overlap an edit
|
|
71
|
+
* (e.g. a JSX element inside an include-replaced paragraph).
|
|
72
|
+
*/
|
|
73
|
+
function buildTree(spans, edits) {
|
|
74
|
+
const roots = [];
|
|
75
|
+
const stack = [];
|
|
76
|
+
let e = 0;
|
|
77
|
+
for (const span of spans) {
|
|
78
|
+
while (e < edits.length && edits[e].end <= span.start) e++;
|
|
79
|
+
if (e < edits.length && edits[e].start < span.end && edits[e].end > span.start) continue;
|
|
80
|
+
while (stack.length > 0 && stack[stack.length - 1].end <= span.start) stack.pop();
|
|
81
|
+
const parent = stack[stack.length - 1];
|
|
82
|
+
(parent ? parent.children : roots).push(span);
|
|
83
|
+
stack.push(span);
|
|
84
|
+
}
|
|
85
|
+
return roots;
|
|
86
|
+
}
|
|
87
|
+
function toComponentCode(as, body, hasJsx) {
|
|
88
|
+
return [
|
|
89
|
+
`import { asMarkdown as _asMarkdown${hasJsx ? ", jsxComponents as _jsxComponents" : ""} } from "@vx-oss/docs-core/server";`,
|
|
90
|
+
`export function ${as}(props) {`,
|
|
91
|
+
" if (!_asMarkdown()) return null;",
|
|
92
|
+
...hasJsx ? [" const _c = _jsxComponents(props.components);"] : [],
|
|
93
|
+
` return ${body};`,
|
|
94
|
+
"}"
|
|
95
|
+
].join("\n");
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Generate the JSX for the document: Markdown becomes string literals, kept
|
|
99
|
+
* elements stay as JSX with `_c.`-prefixed names and their attributes verbatim.
|
|
100
|
+
*/
|
|
101
|
+
function childrenToCode(s, start, end, spans) {
|
|
102
|
+
if (spans.length === 0) {
|
|
103
|
+
const text = s.slice(start, end).trim();
|
|
104
|
+
return text ? JSON.stringify(`${text}\n`) : "\"\"";
|
|
105
|
+
}
|
|
106
|
+
return `<>${innerToCode(s, start, end, spans)}</>`;
|
|
107
|
+
}
|
|
108
|
+
function elementToCode(s, span) {
|
|
109
|
+
const tagEnd = scanTagEnd(s.source, span.start, span.end);
|
|
110
|
+
const open = `<_c.${s.source.slice(span.start + 1, tagEnd + 1)}`;
|
|
111
|
+
let out;
|
|
112
|
+
if (s.source[tagEnd - 1] === "/") out = open;
|
|
113
|
+
else {
|
|
114
|
+
const closeStart = s.source.lastIndexOf("</", span.end);
|
|
115
|
+
out = `${open}${innerToCode(s, tagEnd + 1, closeStart, span.children)}</_c.${span.name}>`;
|
|
116
|
+
}
|
|
117
|
+
return span.inline ? `<span>${out}</span>` : out;
|
|
118
|
+
}
|
|
119
|
+
function innerToCode(s, start, end, children) {
|
|
120
|
+
const indent = commonIndent(s.source.slice(start, end));
|
|
121
|
+
const parts = [];
|
|
122
|
+
let cursor = start;
|
|
123
|
+
const pushText = (text) => {
|
|
124
|
+
if (text.trim()) parts.push(`{${JSON.stringify(dedent(text, indent))}}`);
|
|
125
|
+
};
|
|
126
|
+
for (const child of children) {
|
|
127
|
+
pushText(s.slice(cursor, child.start));
|
|
128
|
+
parts.push(elementToCode(s, child));
|
|
129
|
+
cursor = child.end;
|
|
130
|
+
}
|
|
131
|
+
pushText(s.slice(cursor, end));
|
|
132
|
+
return parts.join("");
|
|
133
|
+
}
|
|
134
|
+
/** the authored indentation of an element's children, stripped from their chunks */
|
|
135
|
+
function commonIndent(text) {
|
|
136
|
+
let min = Infinity;
|
|
137
|
+
for (const line of text.split("\n")) {
|
|
138
|
+
if (line.trim().length === 0) continue;
|
|
139
|
+
let i = 0;
|
|
140
|
+
while (line[i] === " ") i++;
|
|
141
|
+
min = Math.min(min, i);
|
|
142
|
+
}
|
|
143
|
+
return min === Infinity ? 0 : min;
|
|
144
|
+
}
|
|
145
|
+
function dedent(text, indent) {
|
|
146
|
+
if (indent === 0) return text;
|
|
147
|
+
const lines = text.split("\n");
|
|
148
|
+
let out = "";
|
|
149
|
+
for (let j = 0; j < lines.length; j++) {
|
|
150
|
+
if (j > 0) out += "\n";
|
|
151
|
+
let i = 0;
|
|
152
|
+
while (i < indent && lines[j][i] === " ") i++;
|
|
153
|
+
out += lines[j].slice(i);
|
|
154
|
+
}
|
|
155
|
+
return out;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Index of the `>` ending the opening tag. Quotes at brace depth 0 are JSX attribute
|
|
159
|
+
* strings (no escapes); quotes inside `{}` follow JavaScript escaping.
|
|
160
|
+
*/
|
|
161
|
+
function scanTagEnd(source, start, end) {
|
|
162
|
+
let quote = "";
|
|
163
|
+
let depth = 0;
|
|
164
|
+
for (let i = start + 1; i < end; i++) {
|
|
165
|
+
const c = source[i];
|
|
166
|
+
if (quote) {
|
|
167
|
+
if (c === "\\" && depth > 0) i++;
|
|
168
|
+
else if (c === quote) quote = "";
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
switch (c) {
|
|
172
|
+
case "\"":
|
|
173
|
+
case "'":
|
|
174
|
+
quote = c;
|
|
175
|
+
break;
|
|
176
|
+
case "`":
|
|
177
|
+
if (depth > 0) quote = c;
|
|
178
|
+
break;
|
|
179
|
+
case "{":
|
|
180
|
+
depth++;
|
|
181
|
+
break;
|
|
182
|
+
case "}":
|
|
183
|
+
depth--;
|
|
184
|
+
break;
|
|
185
|
+
case ">": if (depth === 0) return i;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return end - 1;
|
|
189
|
+
}
|
|
190
|
+
//#endregion
|
|
191
|
+
export { remarkLlms };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/remark-mdx-mermaid.d.ts
|
|
2
|
+
interface RemarkMdxMermaidOptions {
|
|
3
|
+
lang?: string;
|
|
4
|
+
}
|
|
5
|
+
declare function remarkMdxMermaid({ lang }?: RemarkMdxMermaidOptions): {
|
|
6
|
+
name: string;
|
|
7
|
+
code(node: Readonly<import("mdast").Code>, ctx: import("satteri").MdastVisitorContext): undefined;
|
|
8
|
+
};
|
|
9
|
+
//#endregion
|
|
10
|
+
export { RemarkMdxMermaidOptions, remarkMdxMermaid };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineMdastPlugin } from "satteri";
|
|
2
|
+
//#region src/remark-mdx-mermaid.ts
|
|
3
|
+
function remarkMdxMermaid({ lang = "mermaid" } = {}) {
|
|
4
|
+
return defineMdastPlugin({
|
|
5
|
+
name: "remark-mdx-mermaid",
|
|
6
|
+
code(node, ctx) {
|
|
7
|
+
if (node.lang !== lang || !node.value) return;
|
|
8
|
+
ctx.replaceNode(node, {
|
|
9
|
+
type: "mdxJsxFlowElement",
|
|
10
|
+
name: "Mermaid",
|
|
11
|
+
attributes: [{
|
|
12
|
+
type: "mdxJsxAttribute",
|
|
13
|
+
name: "chart",
|
|
14
|
+
value: node.value.trim()
|
|
15
|
+
}],
|
|
16
|
+
children: []
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
export { remarkMdxMermaid };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/remark-npm.d.ts
|
|
2
|
+
interface PackageManager {
|
|
3
|
+
name: string;
|
|
4
|
+
value?: string;
|
|
5
|
+
command: (command: string) => string | undefined;
|
|
6
|
+
}
|
|
7
|
+
interface RemarkNpmOptions {
|
|
8
|
+
persist?: {
|
|
9
|
+
id: string;
|
|
10
|
+
} | false;
|
|
11
|
+
packageManagers?: PackageManager[];
|
|
12
|
+
}
|
|
13
|
+
declare function remarkNpm({ persist, packageManagers }?: RemarkNpmOptions): {
|
|
14
|
+
name: string;
|
|
15
|
+
code(node: Readonly<import("mdast").Code>, ctx: import("satteri").MdastVisitorContext): undefined;
|
|
16
|
+
};
|
|
17
|
+
//#endregion
|
|
18
|
+
export { remarkNpm as n, RemarkNpmOptions as t };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { defineMdastPlugin } from "satteri";
|
|
2
|
+
import { generateCodeBlockTabs } from "@vx-oss/docs-core/mdx-plugins/codeblock-utils";
|
|
3
|
+
import convert from "npm-to-yarn";
|
|
4
|
+
//#region src/remark-npm.ts
|
|
5
|
+
function convertLines(cmd, to) {
|
|
6
|
+
return cmd.split("\n").map((l) => convert(l, to)).join("\n");
|
|
7
|
+
}
|
|
8
|
+
function remarkNpm({ persist = false, packageManagers = [
|
|
9
|
+
{
|
|
10
|
+
command: (cmd) => cmd,
|
|
11
|
+
name: "npm"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
command: (cmd) => convertLines(cmd, "pnpm"),
|
|
15
|
+
name: "pnpm"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
command: (cmd) => convertLines(cmd, "yarn"),
|
|
19
|
+
name: "yarn"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
command: (cmd) => convertLines(cmd, "bun"),
|
|
23
|
+
name: "bun"
|
|
24
|
+
}
|
|
25
|
+
] } = {}) {
|
|
26
|
+
return defineMdastPlugin({
|
|
27
|
+
name: "remark-npm",
|
|
28
|
+
code(node, ctx) {
|
|
29
|
+
let code;
|
|
30
|
+
switch (node.lang) {
|
|
31
|
+
case "package-install":
|
|
32
|
+
code = node.value;
|
|
33
|
+
if (!code.startsWith("npm") && !code.startsWith("npx")) code = `npm install ${code}`;
|
|
34
|
+
break;
|
|
35
|
+
case "npm":
|
|
36
|
+
code = node.value;
|
|
37
|
+
break;
|
|
38
|
+
default: return;
|
|
39
|
+
}
|
|
40
|
+
const options = {
|
|
41
|
+
persist,
|
|
42
|
+
tabs: [],
|
|
43
|
+
triggers: []
|
|
44
|
+
};
|
|
45
|
+
for (const manager of packageManagers) {
|
|
46
|
+
const value = manager.value ?? manager.name;
|
|
47
|
+
const command = manager.command(code);
|
|
48
|
+
if (!command || command.length === 0) continue;
|
|
49
|
+
options.defaultValue ??= value;
|
|
50
|
+
options.triggers.push({
|
|
51
|
+
value,
|
|
52
|
+
children: [{
|
|
53
|
+
type: "text",
|
|
54
|
+
value: manager.name
|
|
55
|
+
}]
|
|
56
|
+
});
|
|
57
|
+
options.tabs.push({
|
|
58
|
+
value,
|
|
59
|
+
children: [{
|
|
60
|
+
type: "code",
|
|
61
|
+
lang: "bash",
|
|
62
|
+
meta: node.meta,
|
|
63
|
+
value: command
|
|
64
|
+
}]
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
ctx.replaceNode(node, generateCodeBlockTabs(options));
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
//#endregion
|
|
72
|
+
export { remarkNpm };
|