@docfuse/plugins 0.1.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/LICENSE +21 -0
- package/README.md +52 -0
- package/README.zh-CN.md +52 -0
- package/dist/client/kroki.js +221 -0
- package/dist/client/mermaid.js +366 -0
- package/dist/client/plantuml.js +221 -0
- package/dist/diagram.css +195 -0
- package/dist/external-links.d.ts +10 -0
- package/dist/external-links.js +49 -0
- package/dist/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- package/dist/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/dist/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/dist/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/dist/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/dist/fonts/KaTeX_Main-Bold.woff2 +0 -0
- package/dist/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/dist/fonts/KaTeX_Main-Italic.woff2 +0 -0
- package/dist/fonts/KaTeX_Main-Regular.woff2 +0 -0
- package/dist/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/dist/fonts/KaTeX_Math-Italic.woff2 +0 -0
- package/dist/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/dist/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/dist/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/dist/fonts/KaTeX_Script-Regular.woff2 +0 -0
- package/dist/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- package/dist/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- package/dist/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- package/dist/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- package/dist/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +757 -0
- package/dist/kroki.d.ts +12 -0
- package/dist/kroki.js +293 -0
- package/dist/link-card.d.ts +9 -0
- package/dist/link-card.js +87 -0
- package/dist/math.css +3 -0
- package/dist/math.d.ts +19 -0
- package/dist/math.js +140 -0
- package/dist/mermaid.d.ts +10 -0
- package/dist/mermaid.js +290 -0
- package/dist/pagefind.d.ts +66 -0
- package/dist/pagefind.js +84 -0
- package/dist/plantuml.d.ts +10 -0
- package/dist/plantuml.js +297 -0
- package/dist/reading-time.d.ts +20 -0
- package/dist/reading-time.js +90 -0
- package/package.json +122 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,757 @@
|
|
|
1
|
+
// src/kroki/index.ts
|
|
2
|
+
import { defineMarkdownPlugin } from "@docfuse/markdown";
|
|
3
|
+
import { strToU8, zlibSync } from "fflate";
|
|
4
|
+
|
|
5
|
+
// src/shared/diagram.ts
|
|
6
|
+
import { visit } from "unist-util-visit";
|
|
7
|
+
function displayKind(kind) {
|
|
8
|
+
if (kind === "plantuml") return "PlantUML";
|
|
9
|
+
return kind.charAt(0).toUpperCase() + kind.slice(1);
|
|
10
|
+
}
|
|
11
|
+
var DIAGRAM_LABELS = {
|
|
12
|
+
en: {
|
|
13
|
+
copy: "Copy source",
|
|
14
|
+
source: "Show source",
|
|
15
|
+
preview: "Show preview",
|
|
16
|
+
expand: "Expand diagram",
|
|
17
|
+
close: "Close expanded diagram",
|
|
18
|
+
controls: "Diagram zoom controls",
|
|
19
|
+
zoomOut: "Zoom out",
|
|
20
|
+
zoomReset: "Reset zoom",
|
|
21
|
+
zoomIn: "Zoom in",
|
|
22
|
+
imageAlt: (kind) => `${displayKind(kind)} diagram`,
|
|
23
|
+
loadingPreview: "Diagram preview loads in the browser."
|
|
24
|
+
},
|
|
25
|
+
zh: {
|
|
26
|
+
copy: "\u590D\u5236\u56FE\u8868\u6E90\u7801",
|
|
27
|
+
source: "\u663E\u793A\u56FE\u8868\u6E90\u7801",
|
|
28
|
+
preview: "\u663E\u793A\u56FE\u8868\u9884\u89C8",
|
|
29
|
+
expand: "\u653E\u5927\u56FE\u8868",
|
|
30
|
+
close: "\u5173\u95ED\u56FE\u8868\u9884\u89C8",
|
|
31
|
+
controls: "\u56FE\u8868\u7F29\u653E\u63A7\u4EF6",
|
|
32
|
+
zoomOut: "\u7F29\u5C0F\u56FE\u8868",
|
|
33
|
+
zoomReset: "\u91CD\u7F6E\u56FE\u8868\u7F29\u653E",
|
|
34
|
+
zoomIn: "\u653E\u5927\u56FE\u8868",
|
|
35
|
+
imageAlt: (kind) => `${displayKind(kind)} \u56FE\u8868`,
|
|
36
|
+
loadingPreview: "\u56FE\u8868\u9884\u89C8\u5C06\u5728\u6D4F\u89C8\u5668\u4E2D\u52A0\u8F7D\u3002"
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
function labelsFromFile(file) {
|
|
40
|
+
if (!file || typeof file !== "object" || !("data" in file)) return DIAGRAM_LABELS.en;
|
|
41
|
+
const data = file.data;
|
|
42
|
+
if (!data || typeof data !== "object" || !("docfuseLocale" in data)) return DIAGRAM_LABELS.en;
|
|
43
|
+
const locale = typeof data.docfuseLocale === "string" ? data.docfuseLocale.toLowerCase() : "";
|
|
44
|
+
return locale === "zh" || locale.startsWith("zh-") ? DIAGRAM_LABELS.zh : DIAGRAM_LABELS.en;
|
|
45
|
+
}
|
|
46
|
+
function languageOf(node) {
|
|
47
|
+
const code = node.children.find(
|
|
48
|
+
(child) => child.type === "element" && child.tagName === "code"
|
|
49
|
+
);
|
|
50
|
+
if (!code) return void 0;
|
|
51
|
+
const classes = Array.isArray(code.properties.className) ? code.properties.className : [];
|
|
52
|
+
const language = classes.map(String).find((className) => className.startsWith("language-"))?.slice("language-".length).toLowerCase();
|
|
53
|
+
const source = code.children.filter((child) => child.type === "text").map((child) => child.value).join("");
|
|
54
|
+
return language ? { language, source } : void 0;
|
|
55
|
+
}
|
|
56
|
+
function action(name, label, properties = {}) {
|
|
57
|
+
return {
|
|
58
|
+
type: "element",
|
|
59
|
+
tagName: "button",
|
|
60
|
+
properties: { type: "button", dataDfDiagramAction: name, ariaLabel: label, ...properties },
|
|
61
|
+
children: [{ type: "text", value: label }]
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function zoomControl(name, label) {
|
|
65
|
+
return {
|
|
66
|
+
type: "element",
|
|
67
|
+
tagName: "button",
|
|
68
|
+
properties: { type: "button", dataDfDiagramAction: name, ariaLabel: label, title: label },
|
|
69
|
+
children: [{ type: "text", value: label }]
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function diagramFence(options) {
|
|
73
|
+
return () => (tree, file) => {
|
|
74
|
+
const labels = labelsFromFile(file);
|
|
75
|
+
visit(tree, "element", (node, index, parent) => {
|
|
76
|
+
if (!parent || index === void 0 || node.tagName !== "pre") return;
|
|
77
|
+
const fence = languageOf(node);
|
|
78
|
+
if (!fence || !options.languages.has(fence.language)) return;
|
|
79
|
+
const imageUrl = options.imageUrl?.(fence.source, fence.language);
|
|
80
|
+
const hasVisualPreview = Boolean(imageUrl || options.kind === "mermaid");
|
|
81
|
+
const preview = imageUrl ? {
|
|
82
|
+
type: "element",
|
|
83
|
+
tagName: "img",
|
|
84
|
+
properties: {
|
|
85
|
+
className: ["df-diagram-img"],
|
|
86
|
+
src: imageUrl,
|
|
87
|
+
alt: labels.imageAlt(options.kind),
|
|
88
|
+
loading: "lazy"
|
|
89
|
+
},
|
|
90
|
+
children: []
|
|
91
|
+
} : {
|
|
92
|
+
type: "element",
|
|
93
|
+
tagName: "div",
|
|
94
|
+
properties: { className: ["df-diagram-placeholder"] },
|
|
95
|
+
children: [{ type: "text", value: labels.loadingPreview }]
|
|
96
|
+
};
|
|
97
|
+
parent.children[index] = {
|
|
98
|
+
type: "element",
|
|
99
|
+
tagName: "figure",
|
|
100
|
+
properties: {
|
|
101
|
+
className: ["df-diagram-window"],
|
|
102
|
+
dataDfPluginDiagram: options.kind,
|
|
103
|
+
dataDfSource: fence.source,
|
|
104
|
+
...options.moduleUrl ? { dataDfModuleUrl: options.moduleUrl } : {}
|
|
105
|
+
},
|
|
106
|
+
children: [
|
|
107
|
+
{
|
|
108
|
+
type: "element",
|
|
109
|
+
tagName: "figcaption",
|
|
110
|
+
properties: { className: ["df-diagram-toolbar"] },
|
|
111
|
+
children: [
|
|
112
|
+
{
|
|
113
|
+
type: "element",
|
|
114
|
+
tagName: "span",
|
|
115
|
+
properties: { className: ["df-diagram-title"] },
|
|
116
|
+
children: [{ type: "text", value: options.filename(fence.language) }]
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
type: "element",
|
|
120
|
+
tagName: "span",
|
|
121
|
+
properties: { className: ["df-diagram-actions"] },
|
|
122
|
+
children: [
|
|
123
|
+
action("copy", labels.copy),
|
|
124
|
+
action("source", labels.source, { dataDfDiagramPreviewLabel: labels.preview }),
|
|
125
|
+
action("expand", labels.expand, { dataDfDiagramCloseLabel: labels.close })
|
|
126
|
+
]
|
|
127
|
+
}
|
|
128
|
+
]
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
type: "element",
|
|
132
|
+
tagName: "div",
|
|
133
|
+
properties: { className: ["df-diagram-stage"] },
|
|
134
|
+
children: [
|
|
135
|
+
{
|
|
136
|
+
type: "element",
|
|
137
|
+
tagName: "div",
|
|
138
|
+
properties: { className: ["df-diagram-preview"] },
|
|
139
|
+
children: [preview]
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
type: "element",
|
|
143
|
+
tagName: "pre",
|
|
144
|
+
properties: { className: ["df-diagram-source"], hidden: true },
|
|
145
|
+
children: [
|
|
146
|
+
{
|
|
147
|
+
type: "element",
|
|
148
|
+
tagName: "code",
|
|
149
|
+
properties: {},
|
|
150
|
+
children: [{ type: "text", value: fence.source }]
|
|
151
|
+
}
|
|
152
|
+
]
|
|
153
|
+
},
|
|
154
|
+
...hasVisualPreview ? [
|
|
155
|
+
{
|
|
156
|
+
type: "element",
|
|
157
|
+
tagName: "div",
|
|
158
|
+
properties: {
|
|
159
|
+
className: ["df-diagram-zoom-controls"],
|
|
160
|
+
role: "group",
|
|
161
|
+
ariaLabel: labels.controls
|
|
162
|
+
},
|
|
163
|
+
children: [
|
|
164
|
+
zoomControl("zoom-out", labels.zoomOut),
|
|
165
|
+
zoomControl("zoom-reset", labels.zoomReset),
|
|
166
|
+
zoomControl("zoom-in", labels.zoomIn)
|
|
167
|
+
]
|
|
168
|
+
}
|
|
169
|
+
] : []
|
|
170
|
+
]
|
|
171
|
+
}
|
|
172
|
+
]
|
|
173
|
+
};
|
|
174
|
+
});
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// src/shared/markdownSource.ts
|
|
179
|
+
function stripColumns(value, columns) {
|
|
180
|
+
let index = 0;
|
|
181
|
+
let consumed = 0;
|
|
182
|
+
while (index < value.length && consumed < columns) {
|
|
183
|
+
if (value[index] === " ") consumed += 1;
|
|
184
|
+
else if (value[index] === " ") consumed += 4 - consumed % 4;
|
|
185
|
+
else break;
|
|
186
|
+
index += 1;
|
|
187
|
+
}
|
|
188
|
+
return consumed >= columns ? value.slice(index) : void 0;
|
|
189
|
+
}
|
|
190
|
+
function containerContent(line, state) {
|
|
191
|
+
let value = line;
|
|
192
|
+
while (true) {
|
|
193
|
+
const quote = value.match(/^ {0,3}>[ \t]?/);
|
|
194
|
+
if (!quote) break;
|
|
195
|
+
value = value.slice(quote[0].length);
|
|
196
|
+
}
|
|
197
|
+
const continued = state.listIndent > 0 ? stripColumns(value, state.listIndent) : void 0;
|
|
198
|
+
const content = continued ?? value;
|
|
199
|
+
const item = content.match(/^ {0,3}(?:[-+*]|\d{1,9}[.)])([ \t]+)(.*)$/);
|
|
200
|
+
if (item) {
|
|
201
|
+
const body = item[2] ?? "";
|
|
202
|
+
state.listIndent = (continued === void 0 ? 0 : state.listIndent) + content.length - body.length;
|
|
203
|
+
return body;
|
|
204
|
+
}
|
|
205
|
+
if (continued !== void 0 || value.trim() === "") return content;
|
|
206
|
+
state.listIndent = 0;
|
|
207
|
+
return value;
|
|
208
|
+
}
|
|
209
|
+
function openingFence(line) {
|
|
210
|
+
const match = line.match(/^ {0,3}(`{3,}|~{3,})(.*)$/);
|
|
211
|
+
if (!match?.[1]) return void 0;
|
|
212
|
+
const marker = match[1][0];
|
|
213
|
+
const info = match[2]?.trim() ?? "";
|
|
214
|
+
if (marker === "`" && info.includes("`")) return void 0;
|
|
215
|
+
return {
|
|
216
|
+
marker,
|
|
217
|
+
size: match[1].length,
|
|
218
|
+
language: info.split(/\s+/, 1)[0]?.toLowerCase() ?? ""
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
function closesFence(line, fence) {
|
|
222
|
+
const match = line.match(/^ {0,3}(`+|~+)[ \t]*$/);
|
|
223
|
+
return Boolean(match?.[1]?.[0] === fence.marker && match[1].length >= fence.size);
|
|
224
|
+
}
|
|
225
|
+
function hasMarkdownFenceLanguage(source, languages) {
|
|
226
|
+
let open;
|
|
227
|
+
const container = { listIndent: 0 };
|
|
228
|
+
for (const line of source.split(/\r?\n/)) {
|
|
229
|
+
const content = containerContent(line, container);
|
|
230
|
+
if (open) {
|
|
231
|
+
if (closesFence(content, open)) open = void 0;
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
const fence = openingFence(content);
|
|
235
|
+
if (!fence) continue;
|
|
236
|
+
if (languages.has(fence.language)) return true;
|
|
237
|
+
open = fence;
|
|
238
|
+
}
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
function removeInlineCode(line) {
|
|
242
|
+
let result = "";
|
|
243
|
+
let index = 0;
|
|
244
|
+
while (index < line.length) {
|
|
245
|
+
if (line[index] !== "`") {
|
|
246
|
+
result += line[index];
|
|
247
|
+
index += 1;
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
let endOfOpening = index + 1;
|
|
251
|
+
while (line[endOfOpening] === "`") endOfOpening += 1;
|
|
252
|
+
const delimiter = line.slice(index, endOfOpening);
|
|
253
|
+
const closing = line.indexOf(delimiter, endOfOpening);
|
|
254
|
+
if (closing === -1) {
|
|
255
|
+
result += delimiter;
|
|
256
|
+
index = endOfOpening;
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
result += " ".repeat(closing + delimiter.length - index);
|
|
260
|
+
index = closing + delimiter.length;
|
|
261
|
+
}
|
|
262
|
+
return result;
|
|
263
|
+
}
|
|
264
|
+
function markdownProse(source) {
|
|
265
|
+
let open;
|
|
266
|
+
const container = { listIndent: 0 };
|
|
267
|
+
return source.split(/\r?\n/).map((line) => {
|
|
268
|
+
const content = containerContent(line, container);
|
|
269
|
+
if (open) {
|
|
270
|
+
if (closesFence(content, open)) open = void 0;
|
|
271
|
+
return "";
|
|
272
|
+
}
|
|
273
|
+
const fence = openingFence(content);
|
|
274
|
+
if (fence) {
|
|
275
|
+
open = fence;
|
|
276
|
+
return "";
|
|
277
|
+
}
|
|
278
|
+
if (/^(?: {4}|\t)/.test(content)) return "";
|
|
279
|
+
return removeInlineCode(content);
|
|
280
|
+
}).join("\n");
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// src/kroki/index.ts
|
|
284
|
+
var PLUGIN_VERSION = "3";
|
|
285
|
+
var DEFAULT_SERVER = "https://kroki.io";
|
|
286
|
+
var DEFAULT_LANGUAGES = {
|
|
287
|
+
graphviz: "graphviz",
|
|
288
|
+
dot: "graphviz",
|
|
289
|
+
gv: "graphviz",
|
|
290
|
+
d2: "d2"
|
|
291
|
+
};
|
|
292
|
+
function encodeKroki(source) {
|
|
293
|
+
let binary = "";
|
|
294
|
+
for (const byte of zlibSync(strToU8(source), { level: 9 })) binary += String.fromCharCode(byte);
|
|
295
|
+
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
|
|
296
|
+
}
|
|
297
|
+
function krokiUrl(server, type, format, source) {
|
|
298
|
+
return `${server}/${encodeURIComponent(type)}/${format}/${encodeKroki(source)}`;
|
|
299
|
+
}
|
|
300
|
+
function kroki(options = {}) {
|
|
301
|
+
const server = options.server?.replace(/\/+$/, "") || DEFAULT_SERVER;
|
|
302
|
+
const languages = { ...DEFAULT_LANGUAGES, ...options.languages };
|
|
303
|
+
const format = options.format === "png" ? "png" : "svg";
|
|
304
|
+
const languageMap = Object.fromEntries(
|
|
305
|
+
Object.entries(languages).map(([language, type]) => [language.trim().toLowerCase(), type.trim().toLowerCase()]).filter(([language, type]) => language && type)
|
|
306
|
+
);
|
|
307
|
+
return defineMarkdownPlugin({
|
|
308
|
+
name: "kroki",
|
|
309
|
+
version: PLUGIN_VERSION,
|
|
310
|
+
cacheKey: { server, languages: languageMap, format },
|
|
311
|
+
browserCompiler: {
|
|
312
|
+
module: "@docfuse/plugins/kroki",
|
|
313
|
+
exportName: "kroki",
|
|
314
|
+
options: { server, languages: languageMap, format }
|
|
315
|
+
},
|
|
316
|
+
fenceLanguages: Object.keys(languageMap),
|
|
317
|
+
appliesTo: ({ source }) => hasMarkdownFenceLanguage(source, new Set(Object.keys(languageMap))),
|
|
318
|
+
assets: {
|
|
319
|
+
clients: [{ id: "kroki", module: "@docfuse/plugins/client/kroki" }],
|
|
320
|
+
styles: [{ id: "diagrams", module: "@docfuse/plugins/diagram.css" }]
|
|
321
|
+
},
|
|
322
|
+
rehypePlugins: [
|
|
323
|
+
diagramFence({
|
|
324
|
+
languages: new Set(Object.keys(languageMap)),
|
|
325
|
+
kind: "kroki",
|
|
326
|
+
filename: (language) => `diagram.${languageMap[language] ?? language}`,
|
|
327
|
+
imageUrl: (source, language) => krokiUrl(server, languageMap[language] ?? language, format, source)
|
|
328
|
+
})
|
|
329
|
+
]
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// src/external-links/index.ts
|
|
334
|
+
import { defineMarkdownPlugin as defineMarkdownPlugin2 } from "@docfuse/markdown";
|
|
335
|
+
import { visit as visit2 } from "unist-util-visit";
|
|
336
|
+
|
|
337
|
+
// src/shared/urls.ts
|
|
338
|
+
function hostnameOf(href) {
|
|
339
|
+
try {
|
|
340
|
+
return new URL(href).hostname.toLowerCase();
|
|
341
|
+
} catch {
|
|
342
|
+
return void 0;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
function isInternalHost(host, internalHosts) {
|
|
346
|
+
return internalHosts.some((internal) => host === internal || host.endsWith(`.${internal}`));
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// src/external-links/index.ts
|
|
350
|
+
var PLUGIN_VERSION2 = "1";
|
|
351
|
+
function externalLinks(options = {}) {
|
|
352
|
+
const newTab = options.newTab !== false;
|
|
353
|
+
const rel = [...options.rel ?? ["noopener", "noreferrer"]];
|
|
354
|
+
const internalHosts = (options.internalHosts ?? []).map((host) => host.toLowerCase());
|
|
355
|
+
return defineMarkdownPlugin2({
|
|
356
|
+
name: "external-links",
|
|
357
|
+
version: PLUGIN_VERSION2,
|
|
358
|
+
cacheKey: { newTab, rel, internalHosts },
|
|
359
|
+
browserCompiler: {
|
|
360
|
+
module: "@docfuse/plugins/external-links",
|
|
361
|
+
exportName: "externalLinks",
|
|
362
|
+
options: { newTab, rel, internalHosts }
|
|
363
|
+
},
|
|
364
|
+
rehypePlugins: [
|
|
365
|
+
() => (tree) => {
|
|
366
|
+
visit2(tree, "element", (node) => {
|
|
367
|
+
if (node.tagName !== "a") return;
|
|
368
|
+
const href = node.properties.href;
|
|
369
|
+
if (typeof href !== "string" || !/^https?:\/\//i.test(href)) return;
|
|
370
|
+
const host = hostnameOf(href);
|
|
371
|
+
if (!host || isInternalHost(host, internalHosts)) return;
|
|
372
|
+
if (newTab) node.properties.target = "_blank";
|
|
373
|
+
if (rel.length > 0) node.properties.rel = [...rel];
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
]
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// src/link-card/index.ts
|
|
381
|
+
import { defineMarkdownPlugin as defineMarkdownPlugin3 } from "@docfuse/markdown";
|
|
382
|
+
import { SKIP, visit as visit3 } from "unist-util-visit";
|
|
383
|
+
|
|
384
|
+
// src/shared/hast.ts
|
|
385
|
+
function element(tagName, properties = {}, children = []) {
|
|
386
|
+
return { type: "element", tagName, properties, children };
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// src/link-card/index.ts
|
|
390
|
+
var PLUGIN_VERSION3 = "1";
|
|
391
|
+
function isExternalHttpLink(href, internalHosts, includeRelative) {
|
|
392
|
+
if (!href) return false;
|
|
393
|
+
if (href.startsWith("/") || href.startsWith("#") || href.startsWith("mailto:"))
|
|
394
|
+
return includeRelative && href.startsWith("/");
|
|
395
|
+
if (!/^https?:\/\//i.test(href)) return false;
|
|
396
|
+
const host = hostnameOf(href);
|
|
397
|
+
return Boolean(host && !isInternalHost(host, internalHosts));
|
|
398
|
+
}
|
|
399
|
+
function textOf(node) {
|
|
400
|
+
if (node.type === "text") return node.value;
|
|
401
|
+
if ("children" in node) return node.children.map((child) => textOf(child)).join("");
|
|
402
|
+
return "";
|
|
403
|
+
}
|
|
404
|
+
function displayHost(href) {
|
|
405
|
+
try {
|
|
406
|
+
const url = new URL(href);
|
|
407
|
+
return url.hostname + (url.pathname === "/" ? "" : url.pathname);
|
|
408
|
+
} catch {
|
|
409
|
+
return href;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
function linkCard(options = {}) {
|
|
413
|
+
const internalHosts = (options.internalHosts ?? []).map((host) => host.toLowerCase());
|
|
414
|
+
const includeRelative = options.includeRelative === true;
|
|
415
|
+
return defineMarkdownPlugin3({
|
|
416
|
+
name: "link-card",
|
|
417
|
+
version: PLUGIN_VERSION3,
|
|
418
|
+
cacheKey: { internalHosts, includeRelative },
|
|
419
|
+
browserCompiler: {
|
|
420
|
+
module: "@docfuse/plugins/link-card",
|
|
421
|
+
exportName: "linkCard",
|
|
422
|
+
options: { internalHosts, includeRelative }
|
|
423
|
+
},
|
|
424
|
+
rehypePlugins: [
|
|
425
|
+
() => (tree) => {
|
|
426
|
+
visit3(tree, "element", (node, index, parent) => {
|
|
427
|
+
if (node.tagName !== "p" || !parent || index === void 0) return;
|
|
428
|
+
const meaningful = node.children.filter(
|
|
429
|
+
(child) => child.type !== "text" || child.value.trim() !== ""
|
|
430
|
+
);
|
|
431
|
+
const onlyChild = meaningful[0];
|
|
432
|
+
if (meaningful.length !== 1 || onlyChild?.type !== "element" || onlyChild.tagName !== "a") return;
|
|
433
|
+
const href = String(onlyChild.properties.href ?? "");
|
|
434
|
+
if (!isExternalHttpLink(href, internalHosts, includeRelative)) return;
|
|
435
|
+
if (onlyChild.properties.dataDfElement === "file-link") return;
|
|
436
|
+
const title = textOf(onlyChild).trim() || displayHost(href);
|
|
437
|
+
const meta = displayHost(href);
|
|
438
|
+
parent.children[index] = element(
|
|
439
|
+
"a",
|
|
440
|
+
{ ...onlyChild.properties, className: ["df-link-card"], href, dataDfComponent: "link-card" },
|
|
441
|
+
[
|
|
442
|
+
element("strong", {}, [{ type: "text", value: title }]),
|
|
443
|
+
...meta && meta !== title ? [element("span", {}, [{ type: "text", value: meta }])] : []
|
|
444
|
+
]
|
|
445
|
+
);
|
|
446
|
+
return [SKIP, index];
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
]
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// src/math/index.ts
|
|
454
|
+
import { defineMarkdownPlugin as defineMarkdownPlugin4 } from "@docfuse/markdown";
|
|
455
|
+
import rehypeKatex from "rehype-katex";
|
|
456
|
+
import remarkMath from "remark-math";
|
|
457
|
+
var PLUGIN_VERSION4 = "3";
|
|
458
|
+
function hasMathSyntax(source) {
|
|
459
|
+
const prose = markdownProse(source);
|
|
460
|
+
return hasMarkdownFenceLanguage(source, /* @__PURE__ */ new Set(["math"])) || /(^|[^\\])\$\$[\s\S]+?\$\$/.test(prose) || /(^|[^\\$])\$(?!\s)(?:\\.|[^$\n])+?(?<!\s)\$/.test(prose);
|
|
461
|
+
}
|
|
462
|
+
function math(options = {}) {
|
|
463
|
+
const resolved = {
|
|
464
|
+
throwOnError: options.throwOnError ?? false,
|
|
465
|
+
errorColor: options.errorColor ?? "#b42318",
|
|
466
|
+
trust: options.trust ?? false,
|
|
467
|
+
strict: options.strict ?? "warn",
|
|
468
|
+
macros: { ...options.macros ?? {} }
|
|
469
|
+
};
|
|
470
|
+
return defineMarkdownPlugin4({
|
|
471
|
+
name: "math",
|
|
472
|
+
version: PLUGIN_VERSION4,
|
|
473
|
+
cacheKey: resolved,
|
|
474
|
+
browserCompiler: { module: "@docfuse/plugins/math", exportName: "math", options: resolved },
|
|
475
|
+
appliesTo: ({ source }) => hasMathSyntax(source),
|
|
476
|
+
fenceLanguages: ["math"],
|
|
477
|
+
assets: { math: true },
|
|
478
|
+
remarkPlugins: [remarkMath],
|
|
479
|
+
rehypePlugins: [[rehypeKatex, resolved]]
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// src/mermaid/index.ts
|
|
484
|
+
import { defineMarkdownPlugin as defineMarkdownPlugin5 } from "@docfuse/markdown";
|
|
485
|
+
var PLUGIN_VERSION5 = "3";
|
|
486
|
+
function mermaid(options = {}) {
|
|
487
|
+
const moduleUrl = options.moduleUrl?.trim();
|
|
488
|
+
return defineMarkdownPlugin5({
|
|
489
|
+
name: "mermaid",
|
|
490
|
+
version: PLUGIN_VERSION5,
|
|
491
|
+
cacheKey: { moduleUrl: moduleUrl ?? null },
|
|
492
|
+
browserCompiler: {
|
|
493
|
+
module: "@docfuse/plugins/mermaid",
|
|
494
|
+
exportName: "mermaid",
|
|
495
|
+
options: moduleUrl ? { moduleUrl } : {}
|
|
496
|
+
},
|
|
497
|
+
fenceLanguages: ["mermaid"],
|
|
498
|
+
appliesTo: ({ source }) => hasMarkdownFenceLanguage(source, /* @__PURE__ */ new Set(["mermaid"])),
|
|
499
|
+
assets: {
|
|
500
|
+
clients: [
|
|
501
|
+
{
|
|
502
|
+
id: "mermaid",
|
|
503
|
+
module: "@docfuse/plugins/client/mermaid",
|
|
504
|
+
resources: [
|
|
505
|
+
{
|
|
506
|
+
module: "mermaid/dist/mermaid.esm.min.mjs",
|
|
507
|
+
output: "mermaid.esm.min.mjs",
|
|
508
|
+
directories: [
|
|
509
|
+
{
|
|
510
|
+
source: "chunks/mermaid.esm.min",
|
|
511
|
+
output: "chunks/mermaid.esm.min",
|
|
512
|
+
extensions: [".mjs"]
|
|
513
|
+
}
|
|
514
|
+
]
|
|
515
|
+
}
|
|
516
|
+
]
|
|
517
|
+
}
|
|
518
|
+
],
|
|
519
|
+
styles: [{ id: "diagrams", module: "@docfuse/plugins/diagram.css" }]
|
|
520
|
+
},
|
|
521
|
+
rehypePlugins: [
|
|
522
|
+
diagramFence({
|
|
523
|
+
languages: /* @__PURE__ */ new Set(["mermaid"]),
|
|
524
|
+
kind: "mermaid",
|
|
525
|
+
filename: () => "diagram.mmd",
|
|
526
|
+
...moduleUrl ? { moduleUrl } : {}
|
|
527
|
+
})
|
|
528
|
+
]
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// src/pagefind/index.ts
|
|
533
|
+
var PLUGIN_VERSION6 = "1";
|
|
534
|
+
var unusedPagefindUiFiles = [
|
|
535
|
+
"pagefind-ui.js",
|
|
536
|
+
"pagefind-ui.css",
|
|
537
|
+
"pagefind-modular-ui.js",
|
|
538
|
+
"pagefind-component-ui.js",
|
|
539
|
+
"pagefind-component-ui.css",
|
|
540
|
+
"pagefind-highlight.js"
|
|
541
|
+
];
|
|
542
|
+
function assertPagefind(errors, action2) {
|
|
543
|
+
if (errors.length) throw new Error(`Pagefind ${action2} failed:
|
|
544
|
+
${errors.join("\n")}`);
|
|
545
|
+
}
|
|
546
|
+
async function loadPagefindDependencies() {
|
|
547
|
+
const filesystemModule = "node:fs/promises";
|
|
548
|
+
const pathModule = "node:path";
|
|
549
|
+
const pagefindModule = "pagefind";
|
|
550
|
+
const [filesystem, paths, pagefindApi] = await Promise.all([
|
|
551
|
+
import(filesystemModule),
|
|
552
|
+
import(pathModule),
|
|
553
|
+
import(pagefindModule)
|
|
554
|
+
]);
|
|
555
|
+
return {
|
|
556
|
+
pagefindApi,
|
|
557
|
+
readFile: filesystem.readFile,
|
|
558
|
+
rm: filesystem.rm,
|
|
559
|
+
join: paths.join
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
async function writePagefindIndex(context, resolved, loadDependencies = loadPagefindDependencies) {
|
|
563
|
+
let pagefindApi;
|
|
564
|
+
let index;
|
|
565
|
+
try {
|
|
566
|
+
const dependencies = await loadDependencies();
|
|
567
|
+
pagefindApi = dependencies.pagefindApi;
|
|
568
|
+
const { join, readFile, rm } = dependencies;
|
|
569
|
+
const pagefindRoot = join(context.outputRoot, "pagefind");
|
|
570
|
+
await rm(pagefindRoot, { recursive: true, force: true });
|
|
571
|
+
await rm(join(context.outputRoot, "search"), { recursive: true, force: true });
|
|
572
|
+
const created = await pagefindApi.createIndex(resolved);
|
|
573
|
+
assertPagefind(created.errors, "initialization");
|
|
574
|
+
if (!created.index) throw new Error("Pagefind did not return an index");
|
|
575
|
+
index = created.index;
|
|
576
|
+
for (const page of context.graph.pages.filter((candidate) => candidate.search)) {
|
|
577
|
+
const htmlPath = context.resolveOutputPath(page.outputPath, `search page ${page.outputPath}`);
|
|
578
|
+
const result = await index.addHTMLFile({
|
|
579
|
+
url: context.publicPathFor(page.routePath),
|
|
580
|
+
content: await readFile(htmlPath, "utf8")
|
|
581
|
+
});
|
|
582
|
+
assertPagefind(result.errors, `indexing ${page.sourceRelativePath}`);
|
|
583
|
+
}
|
|
584
|
+
const written = await index.writeFiles({ outputPath: pagefindRoot });
|
|
585
|
+
assertPagefind(written.errors, "write");
|
|
586
|
+
await Promise.all(unusedPagefindUiFiles.map((file) => rm(join(pagefindRoot, file), { force: true })));
|
|
587
|
+
} finally {
|
|
588
|
+
try {
|
|
589
|
+
await index?.deleteIndex();
|
|
590
|
+
} finally {
|
|
591
|
+
await pagefindApi?.close();
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
function pagefind(options = {}) {
|
|
596
|
+
const resolved = {
|
|
597
|
+
includeCharacters: options.includeCharacters ?? "._-",
|
|
598
|
+
keepIndexUrl: options.keepIndexUrl ?? false,
|
|
599
|
+
writePlayground: options.writePlayground ?? false
|
|
600
|
+
};
|
|
601
|
+
const provider = Object.freeze({
|
|
602
|
+
id: "pagefind",
|
|
603
|
+
client: "pagefind",
|
|
604
|
+
version: PLUGIN_VERSION6,
|
|
605
|
+
cacheKey: resolved,
|
|
606
|
+
async write(context) {
|
|
607
|
+
await writePagefindIndex(context, resolved);
|
|
608
|
+
}
|
|
609
|
+
});
|
|
610
|
+
return provider;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// src/plantuml/index.ts
|
|
614
|
+
import { defineMarkdownPlugin as defineMarkdownPlugin6 } from "@docfuse/markdown";
|
|
615
|
+
import { deflateSync, strToU8 as strToU82 } from "fflate";
|
|
616
|
+
var PLUGIN_VERSION7 = "3";
|
|
617
|
+
function encode6Bit(value) {
|
|
618
|
+
const normalized = value & 63;
|
|
619
|
+
if (normalized < 10) return String.fromCharCode(48 + normalized);
|
|
620
|
+
if (normalized < 36) return String.fromCharCode(65 + normalized - 10);
|
|
621
|
+
if (normalized < 62) return String.fromCharCode(97 + normalized - 36);
|
|
622
|
+
return normalized === 62 ? "-" : "_";
|
|
623
|
+
}
|
|
624
|
+
function encodePlantUml(source) {
|
|
625
|
+
const normalized = /@start\w+/i.test(source) ? source : `@startuml
|
|
626
|
+
${source}
|
|
627
|
+
@enduml`;
|
|
628
|
+
const bytes = deflateSync(strToU82(normalized), { level: 9 });
|
|
629
|
+
let encoded = "";
|
|
630
|
+
for (let index = 0; index < bytes.length; index += 3) {
|
|
631
|
+
const first = bytes[index] ?? 0;
|
|
632
|
+
const second = bytes[index + 1] ?? 0;
|
|
633
|
+
const third = bytes[index + 2] ?? 0;
|
|
634
|
+
encoded += encode6Bit(first >> 2);
|
|
635
|
+
encoded += encode6Bit((first & 3) << 4 | second >> 4);
|
|
636
|
+
encoded += encode6Bit((second & 15) << 2 | third >> 6);
|
|
637
|
+
encoded += encode6Bit(third & 63);
|
|
638
|
+
}
|
|
639
|
+
return encoded;
|
|
640
|
+
}
|
|
641
|
+
function plantUml(options = {}) {
|
|
642
|
+
const server = options.server === false ? "" : options.server?.trim().replace(/\/+$/, "") ?? "";
|
|
643
|
+
return defineMarkdownPlugin6({
|
|
644
|
+
name: "plantuml",
|
|
645
|
+
version: PLUGIN_VERSION7,
|
|
646
|
+
cacheKey: { server },
|
|
647
|
+
browserCompiler: {
|
|
648
|
+
module: "@docfuse/plugins/plantuml",
|
|
649
|
+
exportName: "plantUml",
|
|
650
|
+
options: { server: server || false }
|
|
651
|
+
},
|
|
652
|
+
fenceLanguages: ["plantuml", "puml"],
|
|
653
|
+
appliesTo: ({ source }) => hasMarkdownFenceLanguage(source, /* @__PURE__ */ new Set(["plantuml", "puml"])),
|
|
654
|
+
assets: {
|
|
655
|
+
clients: [{ id: "plantuml", module: "@docfuse/plugins/client/plantuml" }],
|
|
656
|
+
styles: [{ id: "diagrams", module: "@docfuse/plugins/diagram.css" }]
|
|
657
|
+
},
|
|
658
|
+
rehypePlugins: [
|
|
659
|
+
diagramFence({
|
|
660
|
+
languages: /* @__PURE__ */ new Set(["plantuml", "puml"]),
|
|
661
|
+
kind: "plantuml",
|
|
662
|
+
filename: () => "diagram.puml",
|
|
663
|
+
...server ? { imageUrl: (source) => `${server}/${encodePlantUml(source)}` } : {}
|
|
664
|
+
})
|
|
665
|
+
]
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// src/reading-time/index.ts
|
|
670
|
+
import { defineMarkdownPlugin as defineMarkdownPlugin7 } from "@docfuse/markdown";
|
|
671
|
+
import { SKIP as SKIP2, visit as visit4 } from "unist-util-visit";
|
|
672
|
+
var PLUGIN_VERSION8 = "3";
|
|
673
|
+
var CJK = /[\u4e00-\u9fff\u3400-\u4dbf\u3040-\u30ff\uac00-\ud7af]/g;
|
|
674
|
+
var SKIP_TAGS = /* @__PURE__ */ new Set(["pre", "code", "script", "style", "svg"]);
|
|
675
|
+
var DEFAULT_LABEL = "{minutes} min read";
|
|
676
|
+
var DEFAULT_LABELS = { zh: "\u7EA6 {minutes} \u5206\u949F\u9605\u8BFB" };
|
|
677
|
+
function positiveRate(value, name) {
|
|
678
|
+
if (!Number.isFinite(value) || value <= 0) throw new TypeError(`${name} must be a positive finite number`);
|
|
679
|
+
return value;
|
|
680
|
+
}
|
|
681
|
+
function countReadingTime(text, wordsPerMinute, cjkWordsPerMinute) {
|
|
682
|
+
positiveRate(wordsPerMinute, "wordsPerMinute");
|
|
683
|
+
positiveRate(cjkWordsPerMinute, "cjkWordsPerMinute");
|
|
684
|
+
const cjkCharacters = text.match(CJK)?.length ?? 0;
|
|
685
|
+
const latinWords = text.replace(CJK, " ").match(/[A-Za-z0-9]+(?:['’][A-Za-z0-9]+)*/g)?.length ?? 0;
|
|
686
|
+
const minutes = Math.max(1, Math.round(latinWords / wordsPerMinute + cjkCharacters / cjkWordsPerMinute));
|
|
687
|
+
return { latinWords, cjkCharacters, minutes };
|
|
688
|
+
}
|
|
689
|
+
function collectText(tree, includeCode) {
|
|
690
|
+
const chunks = [];
|
|
691
|
+
visit4(tree, (node) => {
|
|
692
|
+
if (!includeCode && node.type === "element" && SKIP_TAGS.has(node.tagName)) return SKIP2;
|
|
693
|
+
if (node.type === "text") chunks.push(node.value);
|
|
694
|
+
});
|
|
695
|
+
return chunks.join(" ");
|
|
696
|
+
}
|
|
697
|
+
function localeFromFile(file) {
|
|
698
|
+
if (!file || typeof file !== "object" || !("data" in file)) return void 0;
|
|
699
|
+
const data = file.data;
|
|
700
|
+
if (!data || typeof data !== "object" || !("docfuseLocale" in data)) return void 0;
|
|
701
|
+
const locale = data.docfuseLocale;
|
|
702
|
+
return typeof locale === "string" ? locale.trim().toLowerCase() || void 0 : void 0;
|
|
703
|
+
}
|
|
704
|
+
function normalizeLabels(labels) {
|
|
705
|
+
return Object.fromEntries(
|
|
706
|
+
Object.entries(labels ?? {}).map(([locale, value]) => [locale.trim().toLowerCase(), value]).filter(([locale]) => locale.length > 0)
|
|
707
|
+
);
|
|
708
|
+
}
|
|
709
|
+
function labelForLocale(fallback, labels, locale) {
|
|
710
|
+
if (!locale) return fallback;
|
|
711
|
+
return labels[locale] ?? labels[locale.split("-")[0] ?? ""] ?? fallback;
|
|
712
|
+
}
|
|
713
|
+
function readingTime(options = {}) {
|
|
714
|
+
const wordsPerMinute = positiveRate(options.wordsPerMinute ?? 220, "wordsPerMinute");
|
|
715
|
+
const cjkWordsPerMinute = positiveRate(options.cjkWordsPerMinute ?? 300, "cjkWordsPerMinute");
|
|
716
|
+
const includeCode = options.includeCode === true;
|
|
717
|
+
const label = options.label ?? DEFAULT_LABEL;
|
|
718
|
+
const labels = { ...DEFAULT_LABELS, ...normalizeLabels(options.labels) };
|
|
719
|
+
return defineMarkdownPlugin7({
|
|
720
|
+
name: "reading-time",
|
|
721
|
+
version: PLUGIN_VERSION8,
|
|
722
|
+
cacheKey: { wordsPerMinute, cjkWordsPerMinute, includeCode, label, labels },
|
|
723
|
+
browserCompiler: {
|
|
724
|
+
module: "@docfuse/plugins/reading-time",
|
|
725
|
+
exportName: "readingTime",
|
|
726
|
+
options: { wordsPerMinute, cjkWordsPerMinute, includeCode, label, labels }
|
|
727
|
+
},
|
|
728
|
+
rehypePlugins: [
|
|
729
|
+
() => (tree, file) => {
|
|
730
|
+
const counts = countReadingTime(collectText(tree, includeCode), wordsPerMinute, cjkWordsPerMinute);
|
|
731
|
+
if (counts.latinWords + counts.cjkCharacters === 0) return;
|
|
732
|
+
const resolvedLabel = labelForLocale(label, labels, localeFromFile(file));
|
|
733
|
+
const readingTimeNode = element(
|
|
734
|
+
"p",
|
|
735
|
+
{
|
|
736
|
+
className: ["df-reading-time"],
|
|
737
|
+
dataDfReadingMinutes: String(counts.minutes),
|
|
738
|
+
dataDfWordCount: String(counts.latinWords + counts.cjkCharacters)
|
|
739
|
+
},
|
|
740
|
+
[{ type: "text", value: resolvedLabel.replaceAll("{minutes}", String(counts.minutes)) }]
|
|
741
|
+
);
|
|
742
|
+
const titleIndex = tree.children.findIndex((node) => node.type === "element" && node.tagName === "h1");
|
|
743
|
+
tree.children.splice(titleIndex >= 0 ? titleIndex + 1 : 0, 0, readingTimeNode);
|
|
744
|
+
}
|
|
745
|
+
]
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
export {
|
|
749
|
+
externalLinks,
|
|
750
|
+
kroki,
|
|
751
|
+
linkCard,
|
|
752
|
+
math,
|
|
753
|
+
mermaid,
|
|
754
|
+
pagefind,
|
|
755
|
+
plantUml,
|
|
756
|
+
readingTime
|
|
757
|
+
};
|