@scribdown/markdown-renderer 0.1.0 → 0.3.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/dist/index.d.ts +5 -0
- package/dist/index.js +1673 -1499
- package/dist/index.js.map +1 -1
- package/package.json +13 -3
package/dist/index.js
CHANGED
|
@@ -1,47 +1,277 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import {
|
|
3
|
-
CONTENT_WIDTH_STORAGE_KEY,
|
|
4
|
-
PROJECT_HOMEPAGE_URL,
|
|
5
|
-
SCRIBDOWN_MARKDOWN_CLASS_NAME,
|
|
6
|
-
SCRIBDOWN_TOOLBAR_BTN_CLASS_NAME,
|
|
7
|
-
SCRIBDOWN_TOOLBAR_CLASS_NAME,
|
|
8
|
-
SCRIBDOWN_TOOLBAR_MENU_CLASS_NAME,
|
|
9
|
-
SCRIBDOWN_TOOLBAR_MENU_GROUP_CLASS_NAME,
|
|
10
|
-
SCRIBDOWN_TOOLBAR_MENU_ITEM_CLASS_NAME,
|
|
11
|
-
SCRIBDOWN_TOOLBAR_MENU_SUB_ITEM_CLASS_NAME,
|
|
12
|
-
SCRIBDOWN_TOOLBAR_TOC_PANEL_CLASS_NAME,
|
|
13
|
-
SCRIBDOWN_TOOLBAR_CURRENT_CLASS_NAME,
|
|
14
|
-
SCRIBDOWN_TOC_HOST_CLASS_NAME,
|
|
15
|
-
SCRIBDOWN_CONTENT_AREA_CLASS_NAME,
|
|
16
|
-
SCRIBDOWN_CONTENT_SCROLL_CLASS_NAME,
|
|
17
|
-
SCRIBDOWN_THIN_SCROLLBAR_CLASS_NAME,
|
|
18
|
-
SOURCE_LINE_DATA_ATTRIBUTE
|
|
19
|
-
} from "@scribdown/shared";
|
|
20
|
-
import DOMPurify from "dompurify";
|
|
21
2
|
import rehypeRaw from "rehype-raw";
|
|
22
|
-
import rehypeSanitize
|
|
3
|
+
import rehypeSanitize from "rehype-sanitize";
|
|
23
4
|
import rehypeStringify from "rehype-stringify";
|
|
5
|
+
import remarkFrontmatter from "remark-frontmatter";
|
|
24
6
|
import remarkGfm from "remark-gfm";
|
|
25
7
|
import remarkParse from "remark-parse";
|
|
26
8
|
import remarkRehype from "remark-rehype";
|
|
9
|
+
import { unified } from "unified";
|
|
10
|
+
|
|
11
|
+
// src/code/code-block-chrome.ts
|
|
12
|
+
import {
|
|
13
|
+
CODE_BLOCK_BODY_CLASS_NAME,
|
|
14
|
+
CODE_BLOCK_CHROME_CLASS_NAME,
|
|
15
|
+
CODE_BLOCK_CLASS_NAME,
|
|
16
|
+
CODE_BLOCK_COPY_CLASS_NAME,
|
|
17
|
+
CODE_BLOCK_COPY_ICON_CHECK_CLASS_NAME,
|
|
18
|
+
CODE_BLOCK_COPY_ICON_CLASS_NAME,
|
|
19
|
+
CODE_BLOCK_COPY_ICON_COPY_CLASS_NAME,
|
|
20
|
+
CODE_BLOCK_GUTTER_CLASS_NAME,
|
|
21
|
+
CODE_BLOCK_GUTTER_LINE_CLASS_NAME,
|
|
22
|
+
CODE_BLOCK_LANG_CLASS_NAME,
|
|
23
|
+
CODE_BLOCK_LINE_CLASS_NAME,
|
|
24
|
+
SOURCE_LINE_DATA_ATTRIBUTE
|
|
25
|
+
} from "@scribdown/shared";
|
|
26
|
+
var CODE_BLOCK_EMPTY_LINE_PLACEHOLDER = "\u200B";
|
|
27
|
+
var CODE_BLOCK_HYDRATED_DATA_KEY = "scribdownCodeBlockHydrated";
|
|
28
|
+
var CODE_LANGUAGE_DISPLAY_NAMES = {
|
|
29
|
+
bash: "Bash",
|
|
30
|
+
c: "C",
|
|
31
|
+
cpp: "C++",
|
|
32
|
+
csharp: "C#",
|
|
33
|
+
css: "CSS",
|
|
34
|
+
diff: "Diff",
|
|
35
|
+
go: "Go",
|
|
36
|
+
graphql: "GraphQL",
|
|
37
|
+
html: "HTML",
|
|
38
|
+
java: "Java",
|
|
39
|
+
javascript: "JavaScript",
|
|
40
|
+
js: "JavaScript",
|
|
41
|
+
json: "JSON",
|
|
42
|
+
jsx: "JSX",
|
|
43
|
+
kotlin: "Kotlin",
|
|
44
|
+
less: "Less",
|
|
45
|
+
markdown: "Markdown",
|
|
46
|
+
md: "Markdown",
|
|
47
|
+
php: "PHP",
|
|
48
|
+
python: "Python",
|
|
49
|
+
py: "Python",
|
|
50
|
+
ruby: "Ruby",
|
|
51
|
+
rust: "Rust",
|
|
52
|
+
rs: "Rust",
|
|
53
|
+
sass: "Sass",
|
|
54
|
+
scss: "SCSS",
|
|
55
|
+
sh: "Shell",
|
|
56
|
+
shell: "Shell",
|
|
57
|
+
sql: "SQL",
|
|
58
|
+
swift: "Swift",
|
|
59
|
+
toml: "TOML",
|
|
60
|
+
ts: "TypeScript",
|
|
61
|
+
tsx: "TSX",
|
|
62
|
+
typescript: "TypeScript",
|
|
63
|
+
vue: "Vue",
|
|
64
|
+
xml: "XML",
|
|
65
|
+
yaml: "YAML",
|
|
66
|
+
yml: "YAML",
|
|
67
|
+
zsh: "Zsh"
|
|
68
|
+
};
|
|
69
|
+
var CODE_LANGUAGE_DEFAULT_LABEL = "Text";
|
|
70
|
+
var CODE_BLOCK_COPY_RESTORE_DELAY_MS = 1600;
|
|
71
|
+
var CODE_BLOCK_COPY_ARIA_LABEL = "\u590D\u5236\u4EE3\u7801";
|
|
72
|
+
var CODE_BLOCK_COPY_ARIA_LABEL_COPIED = "\u5DF2\u590D\u5236";
|
|
73
|
+
function hydrateCodeBlocks(rootElement) {
|
|
74
|
+
const preElements = rootElement.querySelectorAll("pre");
|
|
75
|
+
preElements.forEach(decorateCodeBlock);
|
|
76
|
+
}
|
|
77
|
+
function decorateCodeBlock(preElement) {
|
|
78
|
+
if (preElement.parentElement?.classList.contains(CODE_BLOCK_CLASS_NAME)) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (preElement.dataset[CODE_BLOCK_HYDRATED_DATA_KEY] === "true") {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const codeElement = preElement.querySelector(":scope > code");
|
|
85
|
+
if (!codeElement) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
preElement.dataset[CODE_BLOCK_HYDRATED_DATA_KEY] = "true";
|
|
89
|
+
const ownerDocument = preElement.ownerDocument;
|
|
90
|
+
const languageId = resolveCodeBlockLanguageId(codeElement);
|
|
91
|
+
const languageLabel = resolveCodeBlockLanguageLabel(languageId);
|
|
92
|
+
const originalCodeText = codeElement.textContent ?? "";
|
|
93
|
+
const codeLineCount = rewriteCodeBlockLines(codeElement, ownerDocument);
|
|
94
|
+
const chromeElement = ownerDocument.createElement("div");
|
|
95
|
+
chromeElement.className = CODE_BLOCK_CHROME_CLASS_NAME;
|
|
96
|
+
const langElement = ownerDocument.createElement("span");
|
|
97
|
+
langElement.className = CODE_BLOCK_LANG_CLASS_NAME;
|
|
98
|
+
langElement.textContent = languageLabel;
|
|
99
|
+
chromeElement.append(langElement);
|
|
100
|
+
const copyButtonElement = createCodeBlockCopyButton(ownerDocument);
|
|
101
|
+
chromeElement.append(copyButtonElement);
|
|
102
|
+
const figureElement = ownerDocument.createElement("figure");
|
|
103
|
+
figureElement.className = CODE_BLOCK_CLASS_NAME;
|
|
104
|
+
figureElement.dataset.scribdownCodeLang = languageId;
|
|
105
|
+
const bodyElement = ownerDocument.createElement("div");
|
|
106
|
+
bodyElement.className = CODE_BLOCK_BODY_CLASS_NAME;
|
|
107
|
+
const gutterElement = createCodeBlockLineNumberGutter(ownerDocument, codeLineCount);
|
|
108
|
+
preElement.replaceWith(figureElement);
|
|
109
|
+
bodyElement.append(gutterElement, preElement);
|
|
110
|
+
figureElement.append(chromeElement, bodyElement);
|
|
111
|
+
const codeBlockSourceLine = codeElement.getAttribute(SOURCE_LINE_DATA_ATTRIBUTE);
|
|
112
|
+
if (codeBlockSourceLine !== null) {
|
|
113
|
+
figureElement.setAttribute(SOURCE_LINE_DATA_ATTRIBUTE, codeBlockSourceLine);
|
|
114
|
+
codeElement.removeAttribute(SOURCE_LINE_DATA_ATTRIBUTE);
|
|
115
|
+
}
|
|
116
|
+
copyButtonElement.dataset.scribdownCodeSource = originalCodeText;
|
|
117
|
+
copyButtonElement.addEventListener("click", handleCodeBlockCopyClick);
|
|
118
|
+
}
|
|
119
|
+
function resolveCodeBlockLanguageId(codeElement) {
|
|
120
|
+
const languageClassName = Array.from(codeElement.classList).find(
|
|
121
|
+
(className) => className.startsWith("language-")
|
|
122
|
+
);
|
|
123
|
+
if (!languageClassName) {
|
|
124
|
+
return "";
|
|
125
|
+
}
|
|
126
|
+
return languageClassName.slice("language-".length).toLowerCase();
|
|
127
|
+
}
|
|
128
|
+
function resolveCodeBlockLanguageLabel(languageId) {
|
|
129
|
+
if (!languageId) {
|
|
130
|
+
return CODE_LANGUAGE_DEFAULT_LABEL;
|
|
131
|
+
}
|
|
132
|
+
const mappedLabel = CODE_LANGUAGE_DISPLAY_NAMES[languageId];
|
|
133
|
+
if (mappedLabel) {
|
|
134
|
+
return mappedLabel;
|
|
135
|
+
}
|
|
136
|
+
return languageId.charAt(0).toUpperCase() + languageId.slice(1);
|
|
137
|
+
}
|
|
138
|
+
function rewriteCodeBlockLines(codeElement, ownerDocument) {
|
|
139
|
+
const shikiLineElements = codeElement.querySelectorAll(":scope > span.line");
|
|
140
|
+
if (shikiLineElements.length > 0) {
|
|
141
|
+
const shikiLineNodes = Array.from(shikiLineElements);
|
|
142
|
+
shikiLineElements.forEach((lineElement) => {
|
|
143
|
+
lineElement.classList.add(CODE_BLOCK_LINE_CLASS_NAME);
|
|
144
|
+
if (lineElement.textContent === "") {
|
|
145
|
+
lineElement.textContent = CODE_BLOCK_EMPTY_LINE_PLACEHOLDER;
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
codeElement.replaceChildren(...shikiLineNodes);
|
|
149
|
+
return shikiLineNodes.length;
|
|
150
|
+
}
|
|
151
|
+
const codeText = codeElement.textContent ?? "";
|
|
152
|
+
const trimmedCodeText = codeText.replace(/\n+$/u, "");
|
|
153
|
+
const codeLines = trimmedCodeText.split("\n");
|
|
154
|
+
while (codeElement.firstChild) {
|
|
155
|
+
codeElement.removeChild(codeElement.firstChild);
|
|
156
|
+
}
|
|
157
|
+
codeLines.forEach((lineText) => {
|
|
158
|
+
const lineElement = ownerDocument.createElement("span");
|
|
159
|
+
lineElement.className = CODE_BLOCK_LINE_CLASS_NAME;
|
|
160
|
+
if (lineText.length === 0) {
|
|
161
|
+
lineElement.textContent = CODE_BLOCK_EMPTY_LINE_PLACEHOLDER;
|
|
162
|
+
} else {
|
|
163
|
+
lineElement.textContent = lineText;
|
|
164
|
+
}
|
|
165
|
+
codeElement.append(lineElement);
|
|
166
|
+
});
|
|
167
|
+
return codeLines.length;
|
|
168
|
+
}
|
|
169
|
+
function createCodeBlockLineNumberGutter(ownerDocument, lineCount) {
|
|
170
|
+
const gutterElement = ownerDocument.createElement("div");
|
|
171
|
+
gutterElement.className = CODE_BLOCK_GUTTER_CLASS_NAME;
|
|
172
|
+
for (let lineIndex = 0; lineIndex < lineCount; lineIndex += 1) {
|
|
173
|
+
const lineNumberText = String(lineIndex + 1);
|
|
174
|
+
const lineNumberElement = ownerDocument.createElement("span");
|
|
175
|
+
lineNumberElement.className = CODE_BLOCK_GUTTER_LINE_CLASS_NAME;
|
|
176
|
+
lineNumberElement.textContent = lineNumberText;
|
|
177
|
+
gutterElement.append(lineNumberElement);
|
|
178
|
+
}
|
|
179
|
+
return gutterElement;
|
|
180
|
+
}
|
|
181
|
+
var CODE_BLOCK_COPY_ICON_COPY_PATHS = '<path d="M8.4 6.8 C10.4 6.5 12.6 6.7 14.6 6.6 C16 6.6 16.5 7.2 16.6 8.6 C16.7 10.8 16.6 13.2 16.6 15.4 C16.5 16.6 15.8 17.2 14.6 17.2 C12.4 17.3 10.2 17.2 8 17.2 C6.6 17.2 6.2 16.6 6.2 15.4 C6.2 13.2 6.2 11 6.2 8.8 C6.2 7.4 6.8 6.8 8.4 6.8 Z" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round" stroke-linecap="round"/><path d="M8.6 7.4 C10.6 7.2 12.6 7.4 14.4 7.3" fill="none" stroke="currentColor" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" opacity="0.42"/><path d="M3.6 12.8 C3.4 11.0 3.4 9.0 3.5 7.0 C3.5 5.0 3.4 3.6 5.2 3.4 C7.2 3.2 9.2 3.4 11.2 3.4 C12.6 3.4 13.2 4.0 13.3 5.0" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round" stroke-linecap="round"/><path d="M4.4 12.4 C4.2 10.6 4.2 8.8 4.2 7.0" fill="none" stroke="currentColor" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" opacity="0.42"/>';
|
|
182
|
+
var CODE_BLOCK_COPY_ICON_CHECK_PATHS = '<path d="M4.2 10.6 C5.6 11.8 6.8 13.2 8.2 14.6 C10.4 11.6 13.0 8.6 16.0 5.6" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round" stroke-linecap="round"/><path d="M4.6 11.4 C5.6 12.4 6.8 13.6 7.8 14.6 C10.2 12.2 12.6 9.6 15.2 7.0" fill="none" stroke="currentColor" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" opacity="0.42"/>';
|
|
183
|
+
function createCodeBlockCopyButton(ownerDocument) {
|
|
184
|
+
const copyButtonElement = ownerDocument.createElement("button");
|
|
185
|
+
copyButtonElement.type = "button";
|
|
186
|
+
copyButtonElement.className = CODE_BLOCK_COPY_CLASS_NAME;
|
|
187
|
+
copyButtonElement.setAttribute("aria-label", CODE_BLOCK_COPY_ARIA_LABEL);
|
|
188
|
+
const copyIconElement = ownerDocument.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
189
|
+
copyIconElement.setAttribute(
|
|
190
|
+
"class",
|
|
191
|
+
`${CODE_BLOCK_COPY_ICON_CLASS_NAME} ${CODE_BLOCK_COPY_ICON_COPY_CLASS_NAME}`
|
|
192
|
+
);
|
|
193
|
+
copyIconElement.setAttribute("viewBox", "0 0 20 20");
|
|
194
|
+
copyIconElement.setAttribute("aria-hidden", "true");
|
|
195
|
+
copyIconElement.setAttribute("focusable", "false");
|
|
196
|
+
copyIconElement.innerHTML = CODE_BLOCK_COPY_ICON_COPY_PATHS;
|
|
197
|
+
copyButtonElement.append(copyIconElement);
|
|
198
|
+
const checkIconElement = ownerDocument.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
199
|
+
checkIconElement.setAttribute(
|
|
200
|
+
"class",
|
|
201
|
+
`${CODE_BLOCK_COPY_ICON_CLASS_NAME} ${CODE_BLOCK_COPY_ICON_CHECK_CLASS_NAME}`
|
|
202
|
+
);
|
|
203
|
+
checkIconElement.setAttribute("viewBox", "0 0 20 20");
|
|
204
|
+
checkIconElement.setAttribute("aria-hidden", "true");
|
|
205
|
+
checkIconElement.setAttribute("focusable", "false");
|
|
206
|
+
checkIconElement.innerHTML = CODE_BLOCK_COPY_ICON_CHECK_PATHS;
|
|
207
|
+
copyButtonElement.append(checkIconElement);
|
|
208
|
+
return copyButtonElement;
|
|
209
|
+
}
|
|
210
|
+
function handleCodeBlockCopyClick(event) {
|
|
211
|
+
const copyButtonElement = event.currentTarget;
|
|
212
|
+
const codeText = copyButtonElement.dataset.scribdownCodeSource ?? "";
|
|
213
|
+
void writeCodeBlockTextToClipboard(codeText).then((isSucceeded) => {
|
|
214
|
+
if (isSucceeded) {
|
|
215
|
+
flashCodeBlockCopyButton(copyButtonElement);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
async function writeCodeBlockTextToClipboard(codeText) {
|
|
220
|
+
if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
|
|
221
|
+
try {
|
|
222
|
+
await navigator.clipboard.writeText(codeText);
|
|
223
|
+
return true;
|
|
224
|
+
} catch {
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (typeof document === "undefined") {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
const fallbackTextarea = document.createElement("textarea");
|
|
231
|
+
fallbackTextarea.value = codeText;
|
|
232
|
+
fallbackTextarea.setAttribute("readonly", "");
|
|
233
|
+
fallbackTextarea.style.position = "fixed";
|
|
234
|
+
fallbackTextarea.style.opacity = "0";
|
|
235
|
+
fallbackTextarea.style.pointerEvents = "none";
|
|
236
|
+
document.body.append(fallbackTextarea);
|
|
237
|
+
fallbackTextarea.select();
|
|
238
|
+
const isSucceeded = document.execCommand("copy");
|
|
239
|
+
fallbackTextarea.remove();
|
|
240
|
+
return isSucceeded;
|
|
241
|
+
}
|
|
242
|
+
function flashCodeBlockCopyButton(copyButtonElement) {
|
|
243
|
+
copyButtonElement.dataset.scribdownCodeCopied = "true";
|
|
244
|
+
copyButtonElement.setAttribute("aria-label", CODE_BLOCK_COPY_ARIA_LABEL_COPIED);
|
|
245
|
+
window.setTimeout(() => {
|
|
246
|
+
delete copyButtonElement.dataset.scribdownCodeCopied;
|
|
247
|
+
copyButtonElement.setAttribute("aria-label", CODE_BLOCK_COPY_ARIA_LABEL);
|
|
248
|
+
}, CODE_BLOCK_COPY_RESTORE_DELAY_MS);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// src/code/code-highlight.ts
|
|
27
252
|
import githubLightTheme from "@shikijs/themes/github-light";
|
|
28
253
|
import { createHighlighterCore } from "shiki/core";
|
|
29
254
|
import { createOnigurumaEngine } from "shiki/engine/oniguruma";
|
|
30
|
-
import { unified } from "unified";
|
|
31
255
|
|
|
32
|
-
// src/code-highlighter-langs.ts
|
|
33
|
-
var CODE_HIGHLIGHTER_EAGER_LOADERS = [
|
|
34
|
-
() => import("@shikijs/langs/bash"),
|
|
35
|
-
() => import("@shikijs/langs/css"),
|
|
36
|
-
() => import("@shikijs/langs/diff"),
|
|
37
|
-
() => import("@shikijs/langs/html"),
|
|
38
|
-
() => import("@shikijs/langs/javascript"),
|
|
39
|
-
() => import("@shikijs/langs/json"),
|
|
40
|
-
() => import("@shikijs/langs/markdown"),
|
|
41
|
-
() => import("@shikijs/langs/typescript"),
|
|
42
|
-
() => import("@shikijs/langs/yaml")
|
|
43
|
-
];
|
|
256
|
+
// src/code/code-highlighter-langs.ts
|
|
257
|
+
var CODE_HIGHLIGHTER_EAGER_LOADERS = [];
|
|
44
258
|
var CODE_HIGHLIGHTER_LAZY_LANGS = {
|
|
259
|
+
// 高频基础语言
|
|
260
|
+
bash: () => import("@shikijs/langs/bash"),
|
|
261
|
+
sh: () => import("@shikijs/langs/bash"),
|
|
262
|
+
shell: () => import("@shikijs/langs/bash"),
|
|
263
|
+
css: () => import("@shikijs/langs/css"),
|
|
264
|
+
diff: () => import("@shikijs/langs/diff"),
|
|
265
|
+
html: () => import("@shikijs/langs/html"),
|
|
266
|
+
js: () => import("@shikijs/langs/javascript"),
|
|
267
|
+
javascript: () => import("@shikijs/langs/javascript"),
|
|
268
|
+
json: () => import("@shikijs/langs/json"),
|
|
269
|
+
markdown: () => import("@shikijs/langs/markdown"),
|
|
270
|
+
md: () => import("@shikijs/langs/markdown"),
|
|
271
|
+
ts: () => import("@shikijs/langs/typescript"),
|
|
272
|
+
typescript: () => import("@shikijs/langs/typescript"),
|
|
273
|
+
yaml: () => import("@shikijs/langs/yaml"),
|
|
274
|
+
yml: () => import("@shikijs/langs/yaml"),
|
|
45
275
|
// 前端框架与扩展
|
|
46
276
|
jsx: () => import("@shikijs/langs/jsx"),
|
|
47
277
|
tsx: () => import("@shikijs/langs/tsx"),
|
|
@@ -138,540 +368,59 @@ var CODE_HIGHLIGHTER_LAZY_LANGS = {
|
|
|
138
368
|
wat: () => import("@shikijs/langs/wasm")
|
|
139
369
|
};
|
|
140
370
|
|
|
141
|
-
// src/
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
var
|
|
176
|
-
var
|
|
177
|
-
var
|
|
178
|
-
var
|
|
179
|
-
var
|
|
180
|
-
var
|
|
181
|
-
var
|
|
182
|
-
var
|
|
183
|
-
var
|
|
184
|
-
var
|
|
185
|
-
var
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
var VIDEO_FALLBACK_DEFAULT_TEXT = "\u89C6\u9891\u52A0\u8F7D\u5931\u8D25";
|
|
189
|
-
var MERMAID_LANGUAGE_ID = "mermaid";
|
|
190
|
-
var MERMAID_FIGURE_CLASS_NAME = "scribdown-mermaid";
|
|
191
|
-
var MERMAID_CHROME_CLASS_NAME = "scribdown-mermaid__chrome";
|
|
192
|
-
var MERMAID_LABEL_CLASS_NAME = "scribdown-mermaid__label";
|
|
193
|
-
var MERMAID_BODY_CLASS_NAME = "scribdown-mermaid__body";
|
|
194
|
-
var MERMAID_CANVAS_CLASS_NAME = "scribdown-mermaid__canvas";
|
|
195
|
-
var MERMAID_FALLBACK_CLASS_NAME = "scribdown-mermaid__fallback";
|
|
196
|
-
var MERMAID_FALLBACK_ICON_CLASS_NAME = "scribdown-mermaid__fallback-icon";
|
|
197
|
-
var MERMAID_FALLBACK_TEXT_CLASS_NAME = "scribdown-mermaid__fallback-text";
|
|
198
|
-
var MERMAID_FALLBACK_SOURCE_CLASS_NAME = "scribdown-mermaid__fallback-source";
|
|
199
|
-
var MERMAID_FIGURE_LOADING_CLASS_NAME = "scribdown-mermaid--loading";
|
|
200
|
-
var MERMAID_FIGURE_FAILED_CLASS_NAME = "scribdown-mermaid--failed";
|
|
201
|
-
var MERMAID_FIGURE_LOADED_CLASS_NAME = "scribdown-mermaid--loaded";
|
|
202
|
-
var MERMAID_HYDRATED_DATA_KEY = "scribdownMermaidHydrated";
|
|
203
|
-
var MERMAID_RENDER_STARTED_DATA_KEY = "scribdownMermaidRenderStarted";
|
|
204
|
-
var MERMAID_SOURCE_DATA_KEY = "scribdownMermaidSourceText";
|
|
205
|
-
var MERMAID_LABEL_TEXT = "Mermaid";
|
|
206
|
-
var MERMAID_FALLBACK_DEFAULT_TEXT = "\u56FE\u8868\u6E32\u67D3\u5931\u8D25";
|
|
207
|
-
var MERMAID_RENDER_ID_PREFIX = "scribdown-mermaid-";
|
|
208
|
-
var mermaidRenderIdCounter = 0;
|
|
209
|
-
var MERMAID_FULLSCREEN_BUTTON_CLASS_NAME = "scribdown-mermaid__fullscreen";
|
|
210
|
-
var MERMAID_FULLSCREEN_BUTTON_ARIA_LABEL = "\u5168\u5C4F\u67E5\u770B\u56FE\u8868";
|
|
211
|
-
var MERMAID_VIEWER_DIALOG_CLASS_NAME = "scribdown-mermaid-viewer";
|
|
212
|
-
var MERMAID_VIEWER_ZOOMED_CLASS_NAME = "scribdown-mermaid-viewer--zoomed";
|
|
213
|
-
var MERMAID_VIEWER_DRAGGING_CLASS_NAME = "scribdown-mermaid-viewer--dragging";
|
|
214
|
-
var MERMAID_VIEWER_CHROME_CLASS_NAME = "scribdown-mermaid-viewer__chrome";
|
|
215
|
-
var MERMAID_VIEWER_CAPTION_CLASS_NAME = "scribdown-mermaid-viewer__caption";
|
|
216
|
-
var MERMAID_VIEWER_CONTROLS_CLASS_NAME = "scribdown-mermaid-viewer__controls";
|
|
217
|
-
var MERMAID_VIEWER_BUTTON_CLASS_NAME = "scribdown-mermaid-viewer__button";
|
|
218
|
-
var MERMAID_VIEWER_CLOSE_BUTTON_CLASS_NAME = "scribdown-mermaid-viewer__button--close";
|
|
219
|
-
var MERMAID_VIEWER_VIEWPORT_CLASS_NAME = "scribdown-mermaid-viewer__viewport";
|
|
220
|
-
var MERMAID_VIEWER_CANVAS_CLASS_NAME = "scribdown-mermaid-viewer__canvas";
|
|
221
|
-
var MERMAID_VIEWER_ZOOM_VALUE_CLASS_NAME = "scribdown-mermaid-viewer__zoom-value";
|
|
222
|
-
var MERMAID_FULLSCREEN_AVAILABLE_DATA_KEY = "scribdownMermaidFullscreenReady";
|
|
223
|
-
var MERMAID_VIEWER_SOURCE_DATA_KEY = "scribdownMermaidSource";
|
|
224
|
-
var mermaidViewerStateByDocument = /* @__PURE__ */ new WeakMap();
|
|
225
|
-
var mermaidViewerStateByDialogElement = /* @__PURE__ */ new WeakMap();
|
|
226
|
-
var IMAGE_VIEWER_DIALOG_CLASS_NAME = "scribdown-image-viewer";
|
|
227
|
-
var IMAGE_VIEWER_ZOOMED_CLASS_NAME = "scribdown-image-viewer--zoomed";
|
|
228
|
-
var IMAGE_VIEWER_DRAGGING_CLASS_NAME = "scribdown-image-viewer--dragging";
|
|
229
|
-
var IMAGE_VIEWER_CAPTION_GROUP_CLASS_NAME = "scribdown-image-viewer__caption-group";
|
|
230
|
-
var IMAGE_VIEWER_HINT_CLASS_NAME = "scribdown-image-viewer__hint";
|
|
231
|
-
var IMAGE_VIEWER_HINT_TEXT = "\u5FEB\u6377\u952E\uFF1A+/= \u653E\u5927 \xB7 - \u7F29\u5C0F \xB7 0 \u91CD\u7F6E \xB7 Esc \u5173\u95ED \xB7 \u9F20\u6807\u62D6\u62FD\u53EF\u5E73\u79FB \xB7 Ctrl/\u2318 + \u6EDA\u8F6E\u7F29\u653E";
|
|
232
|
-
var IMAGE_VIEWER_CHROME_CLASS_NAME = "scribdown-image-viewer__chrome";
|
|
233
|
-
var IMAGE_VIEWER_CONTROLS_CLASS_NAME = "scribdown-image-viewer__controls";
|
|
234
|
-
var IMAGE_VIEWER_BUTTON_CLASS_NAME = "scribdown-image-viewer__button";
|
|
235
|
-
var IMAGE_VIEWER_CLOSE_BUTTON_CLASS_NAME = "scribdown-image-viewer__button--close";
|
|
236
|
-
var IMAGE_VIEWER_VIEWPORT_CLASS_NAME = "scribdown-image-viewer__viewport";
|
|
237
|
-
var IMAGE_VIEWER_IMAGE_CLASS_NAME = "scribdown-image-viewer__image";
|
|
238
|
-
var IMAGE_VIEWER_CAPTION_CLASS_NAME = "scribdown-image-viewer__caption";
|
|
239
|
-
var IMAGE_VIEWER_ZOOM_VALUE_CLASS_NAME = "scribdown-image-viewer__zoom-value";
|
|
240
|
-
var IMAGE_VIEWER_DEFAULT_ZOOM = 1;
|
|
241
|
-
var IMAGE_VIEWER_MIN_ZOOM = 0.25;
|
|
242
|
-
var IMAGE_VIEWER_MAX_ZOOM = 4;
|
|
243
|
-
var IMAGE_VIEWER_ZOOM_STEP = 0.25;
|
|
244
|
-
var IMAGE_VIEWER_WHEEL_ZOOM_FACTOR = 0.01;
|
|
245
|
-
var IMAGE_VIEWER_WHEEL_ZOOM_MAX_DELTA = 0.1;
|
|
246
|
-
var IMAGE_VIEWER_FIT_RATIO = 0.92;
|
|
247
|
-
var IMAGE_VIEWER_FALLBACK_LABEL = "\u67E5\u770B\u5168\u56FE";
|
|
248
|
-
var IMAGE_VIEWER_CLOSE_TEXT = "x";
|
|
249
|
-
var IMAGE_VIEWER_ZOOM_IN_TEXT = "+";
|
|
250
|
-
var IMAGE_VIEWER_ZOOM_OUT_TEXT = "-";
|
|
251
|
-
var IMAGE_VIEWER_RESET_TEXT = "1:1";
|
|
252
|
-
var imageViewerStateByDocument = /* @__PURE__ */ new WeakMap();
|
|
253
|
-
var imageViewerStateByDialogElement = /* @__PURE__ */ new WeakMap();
|
|
254
|
-
var HIGHLIGHT_MARKER_PATTERN = /==([^=\n]+?)==/g;
|
|
255
|
-
var DEFINITION_LIST_PARAGRAPH_PATTERN = /^\s*([^\n:][^\n]*)\n\s*:\s+([^\n]+)\s*$/u;
|
|
256
|
-
var CODE_BLOCK_CLASS_NAME = "scribdown-code-block";
|
|
257
|
-
var CODE_BLOCK_CHROME_CLASS_NAME = "scribdown-code-block__chrome";
|
|
258
|
-
var CODE_BLOCK_BODY_CLASS_NAME = "scribdown-code-block__body";
|
|
259
|
-
var CODE_BLOCK_LANG_CLASS_NAME = "scribdown-code-block__lang";
|
|
260
|
-
var CODE_BLOCK_COPY_CLASS_NAME = "scribdown-code-block__copy";
|
|
261
|
-
var CODE_BLOCK_COPY_ICON_CLASS_NAME = "scribdown-code-block__copy-icon";
|
|
262
|
-
var CODE_BLOCK_COPY_ICON_COPY_CLASS_NAME = "scribdown-code-block__copy-icon--copy";
|
|
263
|
-
var CODE_BLOCK_COPY_ICON_CHECK_CLASS_NAME = "scribdown-code-block__copy-icon--check";
|
|
264
|
-
var CODE_BLOCK_LINE_CLASS_NAME = "scribdown-code-block__line";
|
|
265
|
-
var CODE_BLOCK_GUTTER_CLASS_NAME = "scribdown-code-block__gutter";
|
|
266
|
-
var CODE_BLOCK_GUTTER_LINE_CLASS_NAME = "scribdown-code-block__gutter-line";
|
|
267
|
-
var CODE_BLOCK_EMPTY_LINE_PLACEHOLDER = "\u200B";
|
|
268
|
-
var CODE_BLOCK_HYDRATED_DATA_KEY = "scribdownCodeBlockHydrated";
|
|
269
|
-
var CODE_LANGUAGE_DISPLAY_NAMES = {
|
|
270
|
-
bash: "Bash",
|
|
271
|
-
c: "C",
|
|
272
|
-
cpp: "C++",
|
|
273
|
-
csharp: "C#",
|
|
274
|
-
css: "CSS",
|
|
275
|
-
diff: "Diff",
|
|
276
|
-
go: "Go",
|
|
277
|
-
graphql: "GraphQL",
|
|
278
|
-
html: "HTML",
|
|
279
|
-
java: "Java",
|
|
280
|
-
javascript: "JavaScript",
|
|
281
|
-
js: "JavaScript",
|
|
282
|
-
json: "JSON",
|
|
283
|
-
jsx: "JSX",
|
|
284
|
-
kotlin: "Kotlin",
|
|
285
|
-
less: "Less",
|
|
286
|
-
markdown: "Markdown",
|
|
287
|
-
md: "Markdown",
|
|
288
|
-
php: "PHP",
|
|
289
|
-
python: "Python",
|
|
290
|
-
py: "Python",
|
|
291
|
-
ruby: "Ruby",
|
|
292
|
-
rust: "Rust",
|
|
293
|
-
rs: "Rust",
|
|
294
|
-
sass: "Sass",
|
|
295
|
-
scss: "SCSS",
|
|
296
|
-
sh: "Shell",
|
|
297
|
-
shell: "Shell",
|
|
298
|
-
sql: "SQL",
|
|
299
|
-
swift: "Swift",
|
|
300
|
-
toml: "TOML",
|
|
301
|
-
ts: "TypeScript",
|
|
302
|
-
tsx: "TSX",
|
|
303
|
-
typescript: "TypeScript",
|
|
304
|
-
vue: "Vue",
|
|
305
|
-
xml: "XML",
|
|
306
|
-
yaml: "YAML",
|
|
307
|
-
yml: "YAML",
|
|
308
|
-
zsh: "Zsh"
|
|
309
|
-
};
|
|
310
|
-
var CODE_LANGUAGE_DEFAULT_LABEL = "Text";
|
|
311
|
-
var CODE_BLOCK_COPY_RESTORE_DELAY_MS = 1600;
|
|
312
|
-
var CODE_BLOCK_COPY_ARIA_LABEL = "\u590D\u5236\u4EE3\u7801";
|
|
313
|
-
var CODE_BLOCK_COPY_ARIA_LABEL_COPIED = "\u5DF2\u590D\u5236";
|
|
314
|
-
var CODE_HIGHLIGHTER_THEME = "github-light";
|
|
315
|
-
var codeHighlighterPromise;
|
|
316
|
-
var codeHighlighterLazyLoadPromises = /* @__PURE__ */ new Map();
|
|
317
|
-
var CODE_BLOCK_HIGHLIGHT_PATTERN = /<pre([^>]*)><code class="language-([\w-]+)"([^>]*)>([\s\S]*?)<\/code><\/pre>/g;
|
|
318
|
-
var SHIKI_CODE_INNER_PATTERN = /<code[^>]*>([\s\S]*?)<\/code>/u;
|
|
319
|
-
async function renderMarkdown(markdownText) {
|
|
320
|
-
const processor = unified().use(remarkParse).use(remarkGfm).use(remarkHighlightMark).use(remarkDefinitionLists).use(remarkTableOfContents).use(remarkImageFigures).use(remarkSourceLine).use(remarkRehype, { allowDangerousHtml: true }).use(rehypeRaw).use(rehypeFrameClass).use(rehypeVideoFigures).use(rehypeSanitize, createScribdownSanitizeSchema()).use(rehypeStringify);
|
|
321
|
-
const renderedHtml = String(await processor.process(markdownText));
|
|
322
|
-
const sanitizedHtml = sanitizeHtmlWithDomPurify(renderedHtml);
|
|
323
|
-
return highlightMarkdownCodeBlocks(sanitizedHtml);
|
|
324
|
-
}
|
|
325
|
-
async function getCodeHighlighter() {
|
|
326
|
-
if (!codeHighlighterPromise) {
|
|
327
|
-
codeHighlighterPromise = createHighlighterCore({
|
|
328
|
-
themes: [githubLightTheme],
|
|
329
|
-
// eager loaders 通过动态 import 拆为独立 chunk,初始化时并发拉取;
|
|
330
|
-
// 主 bundle 不再内联 grammar JSON,体积更小,多个 grammar 也可并行加载。
|
|
331
|
-
langs: CODE_HIGHLIGHTER_EAGER_LOADERS,
|
|
332
|
-
// oniguruma 引擎 + wasm;shiki/wasm 内部走 base64 内联,扩展环境无需额外资源声明。
|
|
333
|
-
engine: createOnigurumaEngine(import("shiki/wasm"))
|
|
334
|
-
});
|
|
335
|
-
}
|
|
336
|
-
return codeHighlighterPromise;
|
|
337
|
-
}
|
|
338
|
-
async function highlightMarkdownCodeBlocks(html) {
|
|
339
|
-
if (!CODE_BLOCK_HIGHLIGHT_PATTERN.test(html)) {
|
|
340
|
-
return html;
|
|
341
|
-
}
|
|
342
|
-
CODE_BLOCK_HIGHLIGHT_PATTERN.lastIndex = 0;
|
|
343
|
-
const highlighter = await getCodeHighlighter();
|
|
344
|
-
let resultHtml = "";
|
|
345
|
-
let lastIndex = 0;
|
|
346
|
-
for (const match of html.matchAll(CODE_BLOCK_HIGHLIGHT_PATTERN)) {
|
|
347
|
-
const [matched, preAttributes, rawLanguage, codeAttributes, encodedCode] = match;
|
|
348
|
-
const matchStart = match.index ?? 0;
|
|
349
|
-
resultHtml += html.slice(lastIndex, matchStart);
|
|
350
|
-
resultHtml += await highlightSingleCodeBlock(highlighter, {
|
|
351
|
-
rawLanguage,
|
|
352
|
-
encodedCode,
|
|
353
|
-
matchedHtml: matched,
|
|
354
|
-
preAttributes,
|
|
355
|
-
codeAttributes
|
|
356
|
-
});
|
|
357
|
-
lastIndex = matchStart + matched.length;
|
|
358
|
-
}
|
|
359
|
-
resultHtml += html.slice(lastIndex);
|
|
360
|
-
return resultHtml;
|
|
361
|
-
}
|
|
362
|
-
async function highlightSingleCodeBlock(highlighter, input) {
|
|
363
|
-
const { rawLanguage, encodedCode, matchedHtml, preAttributes, codeAttributes } = input;
|
|
364
|
-
const normalizedLanguage = rawLanguage.toLowerCase();
|
|
365
|
-
if (normalizedLanguage === MERMAID_LANGUAGE_ID) {
|
|
366
|
-
return matchedHtml;
|
|
367
|
-
}
|
|
368
|
-
const resolvedLanguage = await ensureHighlighterLanguage(highlighter, normalizedLanguage);
|
|
369
|
-
const codeText = decodeHtmlEntities(encodedCode);
|
|
370
|
-
try {
|
|
371
|
-
const highlightedHtml = highlighter.codeToHtml(codeText, {
|
|
372
|
-
lang: resolvedLanguage,
|
|
373
|
-
theme: CODE_HIGHLIGHTER_THEME
|
|
374
|
-
});
|
|
375
|
-
const innerMatch = highlightedHtml.match(SHIKI_CODE_INNER_PATTERN);
|
|
376
|
-
const innerHtml = innerMatch?.[1] ?? encodedCode;
|
|
377
|
-
return `<pre${preAttributes}><code class="language-${rawLanguage}"${codeAttributes}>${innerHtml}</code></pre>`;
|
|
378
|
-
} catch {
|
|
379
|
-
return matchedHtml;
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
async function ensureHighlighterLanguage(highlighter, normalizedLanguage) {
|
|
383
|
-
if (normalizedLanguage.length === 0) {
|
|
384
|
-
return "text";
|
|
385
|
-
}
|
|
386
|
-
if (highlighter.getLoadedLanguages().includes(normalizedLanguage)) {
|
|
387
|
-
return normalizedLanguage;
|
|
388
|
-
}
|
|
389
|
-
const lazyLoader = CODE_HIGHLIGHTER_LAZY_LANGS[normalizedLanguage];
|
|
390
|
-
if (!lazyLoader) {
|
|
391
|
-
return "text";
|
|
392
|
-
}
|
|
393
|
-
let pending = codeHighlighterLazyLoadPromises.get(normalizedLanguage);
|
|
394
|
-
if (!pending) {
|
|
395
|
-
pending = (async () => {
|
|
396
|
-
const grammarModule = await lazyLoader();
|
|
397
|
-
await highlighter.loadLanguage(grammarModule.default);
|
|
398
|
-
})();
|
|
399
|
-
codeHighlighterLazyLoadPromises.set(normalizedLanguage, pending);
|
|
400
|
-
}
|
|
401
|
-
try {
|
|
402
|
-
await pending;
|
|
403
|
-
return normalizedLanguage;
|
|
404
|
-
} catch {
|
|
405
|
-
return "text";
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
var HTML_NAMED_ENTITIES = {
|
|
409
|
-
lt: "<",
|
|
410
|
-
gt: ">",
|
|
411
|
-
quot: '"',
|
|
412
|
-
apos: "'",
|
|
413
|
-
amp: "&"
|
|
414
|
-
};
|
|
415
|
-
var HTML_ENTITY_PATTERN = /&(?:(lt|gt|quot|apos|amp)|#x([0-9a-fA-F]+)|#(\d+));/g;
|
|
416
|
-
function decodeHtmlEntities(encodedText) {
|
|
417
|
-
return encodedText.replace(
|
|
418
|
-
HTML_ENTITY_PATTERN,
|
|
419
|
-
(matched, namedEntity, hexCodePoint, decimalCodePoint) => {
|
|
420
|
-
if (namedEntity) {
|
|
421
|
-
return HTML_NAMED_ENTITIES[namedEntity] ?? matched;
|
|
422
|
-
}
|
|
423
|
-
if (hexCodePoint) {
|
|
424
|
-
return safeFromCodePoint(parseInt(hexCodePoint, 16), matched);
|
|
425
|
-
}
|
|
426
|
-
if (decimalCodePoint) {
|
|
427
|
-
return safeFromCodePoint(parseInt(decimalCodePoint, 10), matched);
|
|
428
|
-
}
|
|
429
|
-
return matched;
|
|
430
|
-
}
|
|
431
|
-
);
|
|
432
|
-
}
|
|
433
|
-
function safeFromCodePoint(codePoint, fallback) {
|
|
434
|
-
if (!Number.isFinite(codePoint) || codePoint < 0 || codePoint > 1114111) {
|
|
435
|
-
return fallback;
|
|
436
|
-
}
|
|
437
|
-
try {
|
|
438
|
-
return String.fromCodePoint(codePoint);
|
|
439
|
-
} catch {
|
|
440
|
-
return fallback;
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
function sanitizeHtmlWithDomPurify(unsafeHtml) {
|
|
444
|
-
const domPurify = DOMPurify;
|
|
445
|
-
if (typeof domPurify.sanitize !== "function") {
|
|
446
|
-
return unsafeHtml;
|
|
447
|
-
}
|
|
448
|
-
return domPurify.sanitize(unsafeHtml);
|
|
449
|
-
}
|
|
450
|
-
function hydrateMarkdownImages(rootElement) {
|
|
451
|
-
const imageElements = rootElement.querySelectorAll(
|
|
452
|
-
`img.${IMAGE_ELEMENT_CLASS_NAME}`
|
|
453
|
-
);
|
|
454
|
-
imageElements.forEach(bindMarkdownImageState);
|
|
455
|
-
}
|
|
456
|
-
function hydrateMarkdownVideos(rootElement) {
|
|
457
|
-
const videoElements = rootElement.querySelectorAll(
|
|
458
|
-
`video.${VIDEO_ELEMENT_CLASS_NAME}`
|
|
459
|
-
);
|
|
460
|
-
videoElements.forEach(bindMarkdownVideoState);
|
|
461
|
-
}
|
|
462
|
-
function hydrateCodeBlocks(rootElement) {
|
|
463
|
-
const preElements = rootElement.querySelectorAll("pre");
|
|
464
|
-
preElements.forEach(decorateCodeBlock);
|
|
465
|
-
}
|
|
466
|
-
var mermaidLoaderPromise;
|
|
467
|
-
function hydrateMermaidBlocks(rootElement) {
|
|
468
|
-
const mermaidCodeElements = rootElement.querySelectorAll(
|
|
469
|
-
`pre > code.language-${MERMAID_LANGUAGE_ID}`
|
|
470
|
-
);
|
|
471
|
-
mermaidCodeElements.forEach((codeElement) => {
|
|
472
|
-
const preElement = codeElement.parentElement;
|
|
473
|
-
if (!preElement) {
|
|
474
|
-
return;
|
|
475
|
-
}
|
|
476
|
-
if (preElement.dataset[CODE_BLOCK_HYDRATED_DATA_KEY] === "true") {
|
|
477
|
-
return;
|
|
478
|
-
}
|
|
479
|
-
decorateMermaidBlock(preElement, codeElement);
|
|
480
|
-
});
|
|
481
|
-
kickOffPendingMermaidRenders(rootElement);
|
|
482
|
-
}
|
|
483
|
-
function kickOffPendingMermaidRenders(rootElement) {
|
|
484
|
-
const pendingFigures = rootElement.querySelectorAll(
|
|
485
|
-
`.${MERMAID_FIGURE_CLASS_NAME}.${MERMAID_FIGURE_LOADING_CLASS_NAME}`
|
|
486
|
-
);
|
|
487
|
-
pendingFigures.forEach((figureElement) => {
|
|
488
|
-
if (!figureElement.isConnected) {
|
|
489
|
-
return;
|
|
490
|
-
}
|
|
491
|
-
if (figureElement.dataset[MERMAID_RENDER_STARTED_DATA_KEY] === "true") {
|
|
492
|
-
return;
|
|
493
|
-
}
|
|
494
|
-
const canvasElement = figureElement.querySelector(
|
|
495
|
-
`.${MERMAID_CANVAS_CLASS_NAME}`
|
|
496
|
-
);
|
|
497
|
-
const mermaidSource = figureElement.dataset[MERMAID_SOURCE_DATA_KEY] ?? "";
|
|
498
|
-
if (!canvasElement || mermaidSource.length === 0) {
|
|
499
|
-
return;
|
|
500
|
-
}
|
|
501
|
-
figureElement.dataset[MERMAID_RENDER_STARTED_DATA_KEY] = "true";
|
|
502
|
-
void renderMermaidIntoCanvas(figureElement, canvasElement, mermaidSource);
|
|
503
|
-
});
|
|
504
|
-
}
|
|
505
|
-
function decorateMermaidBlock(preElement, codeElement) {
|
|
506
|
-
if (preElement.dataset[MERMAID_HYDRATED_DATA_KEY] === "true") {
|
|
507
|
-
return;
|
|
508
|
-
}
|
|
509
|
-
preElement.dataset[MERMAID_HYDRATED_DATA_KEY] = "true";
|
|
510
|
-
const ownerDocument = preElement.ownerDocument;
|
|
511
|
-
const mermaidSource = (codeElement.textContent ?? "").replace(/\n+$/u, "");
|
|
512
|
-
const figureElement = ownerDocument.createElement("figure");
|
|
513
|
-
figureElement.className = `${MERMAID_FIGURE_CLASS_NAME} ${MERMAID_FIGURE_LOADING_CLASS_NAME}`;
|
|
514
|
-
const chromeElement = ownerDocument.createElement("div");
|
|
515
|
-
chromeElement.className = MERMAID_CHROME_CLASS_NAME;
|
|
516
|
-
const labelElement = ownerDocument.createElement("span");
|
|
517
|
-
labelElement.className = MERMAID_LABEL_CLASS_NAME;
|
|
518
|
-
labelElement.textContent = MERMAID_LABEL_TEXT;
|
|
519
|
-
chromeElement.append(labelElement);
|
|
520
|
-
const bodyElement = ownerDocument.createElement("div");
|
|
521
|
-
bodyElement.className = MERMAID_BODY_CLASS_NAME;
|
|
522
|
-
const canvasElement = ownerDocument.createElement("div");
|
|
523
|
-
canvasElement.className = MERMAID_CANVAS_CLASS_NAME;
|
|
524
|
-
canvasElement.setAttribute("role", "img");
|
|
525
|
-
canvasElement.setAttribute("aria-label", MERMAID_LABEL_TEXT);
|
|
526
|
-
const fullscreenButtonElement = ownerDocument.createElement("button");
|
|
527
|
-
fullscreenButtonElement.type = "button";
|
|
528
|
-
fullscreenButtonElement.className = MERMAID_FULLSCREEN_BUTTON_CLASS_NAME;
|
|
529
|
-
fullscreenButtonElement.setAttribute("aria-label", MERMAID_FULLSCREEN_BUTTON_ARIA_LABEL);
|
|
530
|
-
fullscreenButtonElement.disabled = true;
|
|
531
|
-
fullscreenButtonElement.innerHTML = MERMAID_FULLSCREEN_ICON_SVG;
|
|
532
|
-
fullscreenButtonElement.addEventListener("click", handleMermaidFullscreenButtonClick);
|
|
533
|
-
bodyElement.append(canvasElement, fullscreenButtonElement);
|
|
534
|
-
figureElement.append(chromeElement, bodyElement);
|
|
535
|
-
const sourceLine = codeElement.getAttribute(SOURCE_LINE_DATA_ATTRIBUTE);
|
|
536
|
-
if (sourceLine !== null) {
|
|
537
|
-
figureElement.setAttribute(SOURCE_LINE_DATA_ATTRIBUTE, sourceLine);
|
|
538
|
-
}
|
|
539
|
-
figureElement.dataset[MERMAID_SOURCE_DATA_KEY] = mermaidSource;
|
|
540
|
-
preElement.replaceWith(figureElement);
|
|
541
|
-
}
|
|
542
|
-
async function loadMermaid() {
|
|
543
|
-
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
544
|
-
return void 0;
|
|
545
|
-
}
|
|
546
|
-
if (!mermaidLoaderPromise) {
|
|
547
|
-
mermaidLoaderPromise = (async () => {
|
|
548
|
-
const mermaidModule = await import("mermaid");
|
|
549
|
-
const mermaidApi = mermaidModule.default;
|
|
550
|
-
mermaidApi.initialize({
|
|
551
|
-
startOnLoad: false,
|
|
552
|
-
securityLevel: "strict",
|
|
553
|
-
theme: "default",
|
|
554
|
-
flowchart: { useMaxWidth: false },
|
|
555
|
-
sequence: { useMaxWidth: false },
|
|
556
|
-
class: { useMaxWidth: false },
|
|
557
|
-
state: { useMaxWidth: false },
|
|
558
|
-
gantt: { useMaxWidth: false },
|
|
559
|
-
er: { useMaxWidth: false },
|
|
560
|
-
journey: { useMaxWidth: false },
|
|
561
|
-
pie: { useMaxWidth: false },
|
|
562
|
-
requirement: { useMaxWidth: false },
|
|
563
|
-
c4: { useMaxWidth: false },
|
|
564
|
-
mindmap: { useMaxWidth: false },
|
|
565
|
-
timeline: { useMaxWidth: false },
|
|
566
|
-
gitGraph: { useMaxWidth: false },
|
|
567
|
-
quadrantChart: { useMaxWidth: false },
|
|
568
|
-
xyChart: { useMaxWidth: false },
|
|
569
|
-
sankey: { useMaxWidth: false },
|
|
570
|
-
block: { useMaxWidth: false }
|
|
571
|
-
});
|
|
572
|
-
return mermaidApi;
|
|
573
|
-
})().catch((loadError) => {
|
|
574
|
-
mermaidLoaderPromise = void 0;
|
|
575
|
-
throw loadError;
|
|
576
|
-
});
|
|
577
|
-
}
|
|
578
|
-
return mermaidLoaderPromise;
|
|
579
|
-
}
|
|
580
|
-
async function renderMermaidIntoCanvas(figureElement, canvasElement, mermaidSource) {
|
|
581
|
-
try {
|
|
582
|
-
const mermaidApi = await loadMermaid();
|
|
583
|
-
if (!mermaidApi) {
|
|
584
|
-
return;
|
|
585
|
-
}
|
|
586
|
-
mermaidRenderIdCounter += 1;
|
|
587
|
-
const renderId = `${MERMAID_RENDER_ID_PREFIX}${mermaidRenderIdCounter}`;
|
|
588
|
-
const { svg, bindFunctions } = await mermaidApi.render(renderId, mermaidSource);
|
|
589
|
-
canvasElement.innerHTML = svg;
|
|
590
|
-
bindFunctions?.(canvasElement);
|
|
591
|
-
figureElement.classList.remove(MERMAID_FIGURE_LOADING_CLASS_NAME);
|
|
592
|
-
figureElement.classList.add(MERMAID_FIGURE_LOADED_CLASS_NAME);
|
|
593
|
-
figureElement.dataset[MERMAID_VIEWER_SOURCE_DATA_KEY] = svg;
|
|
594
|
-
figureElement.dataset[MERMAID_FULLSCREEN_AVAILABLE_DATA_KEY] = "true";
|
|
595
|
-
const fullscreenButtonElement = figureElement.querySelector(
|
|
596
|
-
`.${MERMAID_FULLSCREEN_BUTTON_CLASS_NAME}`
|
|
597
|
-
);
|
|
598
|
-
if (fullscreenButtonElement) {
|
|
599
|
-
fullscreenButtonElement.disabled = false;
|
|
600
|
-
}
|
|
601
|
-
} catch (renderError) {
|
|
602
|
-
showMermaidFallback(figureElement, canvasElement, mermaidSource, renderError);
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
function showMermaidFallback(figureElement, canvasElement, mermaidSource, renderError) {
|
|
606
|
-
const ownerDocument = figureElement.ownerDocument;
|
|
607
|
-
figureElement.classList.remove(MERMAID_FIGURE_LOADING_CLASS_NAME);
|
|
608
|
-
figureElement.classList.add(MERMAID_FIGURE_FAILED_CLASS_NAME);
|
|
609
|
-
const fullscreenButtonElement = figureElement.querySelector(
|
|
610
|
-
`.${MERMAID_FULLSCREEN_BUTTON_CLASS_NAME}`
|
|
611
|
-
);
|
|
612
|
-
fullscreenButtonElement?.remove();
|
|
613
|
-
canvasElement.replaceChildren();
|
|
614
|
-
cleanupOrphanMermaidNodes(ownerDocument);
|
|
615
|
-
const fallbackElement = ownerDocument.createElement("div");
|
|
616
|
-
fallbackElement.className = MERMAID_FALLBACK_CLASS_NAME;
|
|
617
|
-
const iconElement = ownerDocument.createElement("span");
|
|
618
|
-
iconElement.className = MERMAID_FALLBACK_ICON_CLASS_NAME;
|
|
619
|
-
iconElement.setAttribute("aria-hidden", "true");
|
|
620
|
-
fallbackElement.append(iconElement);
|
|
621
|
-
const textElement = ownerDocument.createElement("p");
|
|
622
|
-
textElement.className = MERMAID_FALLBACK_TEXT_CLASS_NAME;
|
|
623
|
-
textElement.textContent = MERMAID_FALLBACK_DEFAULT_TEXT;
|
|
624
|
-
fallbackElement.append(textElement);
|
|
625
|
-
const errorMessage = extractErrorMessage(renderError);
|
|
626
|
-
if (errorMessage) {
|
|
627
|
-
const messageElement = ownerDocument.createElement("p");
|
|
628
|
-
messageElement.className = MERMAID_FALLBACK_SOURCE_CLASS_NAME;
|
|
629
|
-
messageElement.textContent = errorMessage;
|
|
630
|
-
fallbackElement.append(messageElement);
|
|
631
|
-
}
|
|
632
|
-
if (mermaidSource.length > 0) {
|
|
633
|
-
const sourceElement = ownerDocument.createElement("pre");
|
|
634
|
-
sourceElement.className = MERMAID_FALLBACK_SOURCE_CLASS_NAME;
|
|
635
|
-
sourceElement.textContent = mermaidSource;
|
|
636
|
-
fallbackElement.append(sourceElement);
|
|
637
|
-
}
|
|
638
|
-
canvasElement.replaceWith(fallbackElement);
|
|
639
|
-
}
|
|
640
|
-
function extractErrorMessage(renderError) {
|
|
641
|
-
if (renderError instanceof Error) {
|
|
642
|
-
return renderError.message;
|
|
643
|
-
}
|
|
644
|
-
if (typeof renderError === "string") {
|
|
645
|
-
return renderError;
|
|
646
|
-
}
|
|
647
|
-
return "";
|
|
648
|
-
}
|
|
649
|
-
function cleanupOrphanMermaidNodes(ownerDocument) {
|
|
650
|
-
const orphanNodes = ownerDocument.querySelectorAll(
|
|
651
|
-
`body > [id^="${MERMAID_RENDER_ID_PREFIX}"], body > div[id^="d"][id*="mermaid"]`
|
|
652
|
-
);
|
|
653
|
-
orphanNodes.forEach((orphanNode) => {
|
|
654
|
-
orphanNode.remove();
|
|
655
|
-
});
|
|
656
|
-
}
|
|
657
|
-
var MERMAID_FULLSCREEN_ICON_SVG = '<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" focusable="false"><path d="M4 9 V4 H9 M15 4 H20 V9 M20 15 V20 H15 M9 20 H4 V15" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>';
|
|
658
|
-
function handleMermaidFullscreenButtonClick(event) {
|
|
659
|
-
const buttonElement = event.currentTarget;
|
|
660
|
-
const figureElement = buttonElement.closest(`.${MERMAID_FIGURE_CLASS_NAME}`);
|
|
661
|
-
if (!figureElement) {
|
|
662
|
-
return;
|
|
663
|
-
}
|
|
664
|
-
if (figureElement.dataset[MERMAID_FULLSCREEN_AVAILABLE_DATA_KEY] !== "true") {
|
|
665
|
-
return;
|
|
666
|
-
}
|
|
667
|
-
const svgSource = figureElement.dataset[MERMAID_VIEWER_SOURCE_DATA_KEY] ?? "";
|
|
668
|
-
if (svgSource.length === 0) {
|
|
669
|
-
return;
|
|
670
|
-
}
|
|
671
|
-
event.preventDefault();
|
|
672
|
-
event.stopPropagation();
|
|
673
|
-
openMarkdownMermaidViewer(figureElement.ownerDocument, svgSource);
|
|
371
|
+
// src/code/mermaid.ts
|
|
372
|
+
import {
|
|
373
|
+
MERMAID_BODY_CLASS_NAME,
|
|
374
|
+
MERMAID_CANVAS_CLASS_NAME,
|
|
375
|
+
MERMAID_CHROME_CLASS_NAME,
|
|
376
|
+
MERMAID_FALLBACK_CLASS_NAME,
|
|
377
|
+
MERMAID_FALLBACK_ICON_CLASS_NAME,
|
|
378
|
+
MERMAID_FALLBACK_SOURCE_CLASS_NAME,
|
|
379
|
+
MERMAID_FALLBACK_TEXT_CLASS_NAME,
|
|
380
|
+
MERMAID_FIGURE_CLASS_NAME,
|
|
381
|
+
MERMAID_FIGURE_FAILED_CLASS_NAME,
|
|
382
|
+
MERMAID_FIGURE_LOADED_CLASS_NAME,
|
|
383
|
+
MERMAID_FIGURE_LOADING_CLASS_NAME,
|
|
384
|
+
MERMAID_FULLSCREEN_BUTTON_CLASS_NAME,
|
|
385
|
+
MERMAID_LABEL_CLASS_NAME,
|
|
386
|
+
SOURCE_LINE_DATA_ATTRIBUTE as SOURCE_LINE_DATA_ATTRIBUTE2
|
|
387
|
+
} from "@scribdown/shared";
|
|
388
|
+
|
|
389
|
+
// src/code/mermaid-viewer.ts
|
|
390
|
+
import {
|
|
391
|
+
MERMAID_VIEWER_BUTTON_CLASS_NAME,
|
|
392
|
+
MERMAID_VIEWER_CANVAS_CLASS_NAME,
|
|
393
|
+
MERMAID_VIEWER_CAPTION_CLASS_NAME,
|
|
394
|
+
MERMAID_VIEWER_CHROME_CLASS_NAME,
|
|
395
|
+
MERMAID_VIEWER_CLOSE_BUTTON_CLASS_NAME,
|
|
396
|
+
MERMAID_VIEWER_CONTROLS_CLASS_NAME,
|
|
397
|
+
MERMAID_VIEWER_DIALOG_CLASS_NAME,
|
|
398
|
+
MERMAID_VIEWER_DRAGGING_CLASS_NAME,
|
|
399
|
+
MERMAID_VIEWER_VIEWPORT_CLASS_NAME,
|
|
400
|
+
MERMAID_VIEWER_ZOOMED_CLASS_NAME,
|
|
401
|
+
MERMAID_VIEWER_ZOOM_VALUE_CLASS_NAME
|
|
402
|
+
} from "@scribdown/shared";
|
|
403
|
+
|
|
404
|
+
// src/core/viewer-shared.ts
|
|
405
|
+
var VIEWER_DEFAULT_ZOOM = 1;
|
|
406
|
+
var VIEWER_MIN_ZOOM = 0.25;
|
|
407
|
+
var VIEWER_MAX_ZOOM = 4;
|
|
408
|
+
var VIEWER_ZOOM_STEP = 0.25;
|
|
409
|
+
var VIEWER_WHEEL_ZOOM_FACTOR = 0.01;
|
|
410
|
+
var VIEWER_WHEEL_ZOOM_MAX_DELTA = 0.1;
|
|
411
|
+
var VIEWER_FIT_RATIO = 0.92;
|
|
412
|
+
var VIEWER_CLOSE_TEXT = "x";
|
|
413
|
+
var VIEWER_ZOOM_IN_TEXT = "+";
|
|
414
|
+
var VIEWER_ZOOM_OUT_TEXT = "-";
|
|
415
|
+
var VIEWER_RESET_TEXT = "1:1";
|
|
416
|
+
function clampMarkdownViewerZoom(zoomValue) {
|
|
417
|
+
return Math.min(VIEWER_MAX_ZOOM, Math.max(VIEWER_MIN_ZOOM, zoomValue));
|
|
674
418
|
}
|
|
419
|
+
|
|
420
|
+
// src/code/mermaid-viewer.ts
|
|
421
|
+
var MERMAID_LABEL_TEXT = "Mermaid";
|
|
422
|
+
var mermaidViewerStateByDocument = /* @__PURE__ */ new WeakMap();
|
|
423
|
+
var mermaidViewerStateByDialogElement = /* @__PURE__ */ new WeakMap();
|
|
675
424
|
function openMarkdownMermaidViewer(ownerDocument, svgSource) {
|
|
676
425
|
const viewerState = getOrCreateMarkdownMermaidViewerState(ownerDocument);
|
|
677
426
|
viewerState.canvasElement.innerHTML = svgSource;
|
|
@@ -680,7 +429,7 @@ function openMarkdownMermaidViewer(ownerDocument, svgSource) {
|
|
|
680
429
|
viewerState.naturalHeight = svgDimensions.height;
|
|
681
430
|
viewerState.viewportElement.scrollLeft = 0;
|
|
682
431
|
viewerState.viewportElement.scrollTop = 0;
|
|
683
|
-
viewerState.zoomValue =
|
|
432
|
+
viewerState.zoomValue = VIEWER_DEFAULT_ZOOM;
|
|
684
433
|
showMarkdownMermaidViewerDialog(viewerState);
|
|
685
434
|
requestMarkdownMermaidViewerLayout(viewerState);
|
|
686
435
|
}
|
|
@@ -719,21 +468,21 @@ function createMarkdownMermaidViewerState(ownerDocument) {
|
|
|
719
468
|
const controlsElement = ownerDocument.createElement("div");
|
|
720
469
|
const zoomOutButtonElement = createMarkdownMermaidViewerButton(ownerDocument, {
|
|
721
470
|
ariaLabel: "\u7F29\u5C0F\u56FE\u8868",
|
|
722
|
-
text:
|
|
471
|
+
text: VIEWER_ZOOM_OUT_TEXT
|
|
723
472
|
});
|
|
724
473
|
const zoomValueElement = ownerDocument.createElement("span");
|
|
725
474
|
const zoomInButtonElement = createMarkdownMermaidViewerButton(ownerDocument, {
|
|
726
475
|
ariaLabel: "\u653E\u5927\u56FE\u8868",
|
|
727
|
-
text:
|
|
476
|
+
text: VIEWER_ZOOM_IN_TEXT
|
|
728
477
|
});
|
|
729
478
|
const resetButtonElement = createMarkdownMermaidViewerButton(ownerDocument, {
|
|
730
479
|
ariaLabel: "\u91CD\u7F6E\u7F29\u653E",
|
|
731
|
-
text:
|
|
480
|
+
text: VIEWER_RESET_TEXT
|
|
732
481
|
});
|
|
733
482
|
const closeButtonElement = createMarkdownMermaidViewerButton(ownerDocument, {
|
|
734
483
|
ariaLabel: "\u5173\u95ED\u5168\u5C4F\u67E5\u770B",
|
|
735
484
|
className: MERMAID_VIEWER_CLOSE_BUTTON_CLASS_NAME,
|
|
736
|
-
text:
|
|
485
|
+
text: VIEWER_CLOSE_TEXT
|
|
737
486
|
});
|
|
738
487
|
const viewportElement = ownerDocument.createElement("div");
|
|
739
488
|
const canvasElement = ownerDocument.createElement("div");
|
|
@@ -749,7 +498,7 @@ function createMarkdownMermaidViewerState(ownerDocument) {
|
|
|
749
498
|
canvasElement,
|
|
750
499
|
naturalWidth: 720,
|
|
751
500
|
naturalHeight: 480,
|
|
752
|
-
zoomValue:
|
|
501
|
+
zoomValue: VIEWER_DEFAULT_ZOOM,
|
|
753
502
|
isDragging: false,
|
|
754
503
|
dragStartClientX: 0,
|
|
755
504
|
dragStartClientY: 0,
|
|
@@ -795,7 +544,7 @@ function createMarkdownMermaidViewerState(ownerDocument) {
|
|
|
795
544
|
if (ownerWindow) {
|
|
796
545
|
ownerWindow.addEventListener("resize", handleMarkdownMermaidViewerWindowResize);
|
|
797
546
|
}
|
|
798
|
-
updateMarkdownMermaidViewerZoom(viewerState,
|
|
547
|
+
updateMarkdownMermaidViewerZoom(viewerState, VIEWER_DEFAULT_ZOOM);
|
|
799
548
|
return viewerState;
|
|
800
549
|
}
|
|
801
550
|
function createMarkdownMermaidViewerButton(ownerDocument, options) {
|
|
@@ -850,7 +599,7 @@ function resetMarkdownMermaidViewerAfterClose(viewerState) {
|
|
|
850
599
|
viewerState.canvasElement.replaceChildren();
|
|
851
600
|
viewerState.viewportElement.scrollLeft = 0;
|
|
852
601
|
viewerState.viewportElement.scrollTop = 0;
|
|
853
|
-
viewerState.zoomValue =
|
|
602
|
+
viewerState.zoomValue = VIEWER_DEFAULT_ZOOM;
|
|
854
603
|
viewerState.isDragging = false;
|
|
855
604
|
viewerState.dialogElement.classList.remove(MERMAID_VIEWER_DRAGGING_CLASS_NAME);
|
|
856
605
|
viewerState.dialogElement.classList.remove(MERMAID_VIEWER_ZOOMED_CLASS_NAME);
|
|
@@ -862,17 +611,17 @@ function handleMarkdownMermaidViewerKeyDown(event) {
|
|
|
862
611
|
}
|
|
863
612
|
if (event.key === "+" || event.key === "=") {
|
|
864
613
|
event.preventDefault();
|
|
865
|
-
updateMarkdownMermaidViewerZoom(viewerState, viewerState.zoomValue +
|
|
614
|
+
updateMarkdownMermaidViewerZoom(viewerState, viewerState.zoomValue + VIEWER_ZOOM_STEP);
|
|
866
615
|
return;
|
|
867
616
|
}
|
|
868
617
|
if (event.key === "-") {
|
|
869
618
|
event.preventDefault();
|
|
870
|
-
updateMarkdownMermaidViewerZoom(viewerState, viewerState.zoomValue -
|
|
619
|
+
updateMarkdownMermaidViewerZoom(viewerState, viewerState.zoomValue - VIEWER_ZOOM_STEP);
|
|
871
620
|
return;
|
|
872
621
|
}
|
|
873
622
|
if (event.key === "0") {
|
|
874
623
|
event.preventDefault();
|
|
875
|
-
updateMarkdownMermaidViewerZoom(viewerState,
|
|
624
|
+
updateMarkdownMermaidViewerZoom(viewerState, VIEWER_DEFAULT_ZOOM);
|
|
876
625
|
return;
|
|
877
626
|
}
|
|
878
627
|
if (event.key === "Escape") {
|
|
@@ -888,10 +637,10 @@ function handleMarkdownMermaidViewerWheel(event) {
|
|
|
888
637
|
if (!viewerState) {
|
|
889
638
|
return;
|
|
890
639
|
}
|
|
891
|
-
const rawDelta = -event.deltaY *
|
|
640
|
+
const rawDelta = -event.deltaY * VIEWER_WHEEL_ZOOM_FACTOR;
|
|
892
641
|
const zoomDelta = Math.max(
|
|
893
|
-
-
|
|
894
|
-
Math.min(
|
|
642
|
+
-VIEWER_WHEEL_ZOOM_MAX_DELTA,
|
|
643
|
+
Math.min(VIEWER_WHEEL_ZOOM_MAX_DELTA, rawDelta)
|
|
895
644
|
);
|
|
896
645
|
event.preventDefault();
|
|
897
646
|
updateMarkdownMermaidViewerZoom(viewerState, viewerState.zoomValue + zoomDelta, {
|
|
@@ -960,21 +709,21 @@ function handleMarkdownMermaidViewerZoomOutClick(event) {
|
|
|
960
709
|
if (!viewerState) {
|
|
961
710
|
return;
|
|
962
711
|
}
|
|
963
|
-
updateMarkdownMermaidViewerZoom(viewerState, viewerState.zoomValue -
|
|
712
|
+
updateMarkdownMermaidViewerZoom(viewerState, viewerState.zoomValue - VIEWER_ZOOM_STEP);
|
|
964
713
|
}
|
|
965
714
|
function handleMarkdownMermaidViewerZoomInClick(event) {
|
|
966
715
|
const viewerState = getMarkdownMermaidViewerStateFromEvent(event);
|
|
967
716
|
if (!viewerState) {
|
|
968
717
|
return;
|
|
969
718
|
}
|
|
970
|
-
updateMarkdownMermaidViewerZoom(viewerState, viewerState.zoomValue +
|
|
719
|
+
updateMarkdownMermaidViewerZoom(viewerState, viewerState.zoomValue + VIEWER_ZOOM_STEP);
|
|
971
720
|
}
|
|
972
721
|
function handleMarkdownMermaidViewerResetClick(event) {
|
|
973
722
|
const viewerState = getMarkdownMermaidViewerStateFromEvent(event);
|
|
974
723
|
if (!viewerState) {
|
|
975
724
|
return;
|
|
976
725
|
}
|
|
977
|
-
updateMarkdownMermaidViewerZoom(viewerState,
|
|
726
|
+
updateMarkdownMermaidViewerZoom(viewerState, VIEWER_DEFAULT_ZOOM);
|
|
978
727
|
}
|
|
979
728
|
function handleMarkdownMermaidViewerCloseClick(event) {
|
|
980
729
|
const viewerState = getMarkdownMermaidViewerStateFromEvent(event);
|
|
@@ -1003,17 +752,17 @@ function isMarkdownMermaidViewerOpen(viewerState) {
|
|
|
1003
752
|
return viewerState.dialogElement.open === true;
|
|
1004
753
|
}
|
|
1005
754
|
function updateMarkdownMermaidViewerZoom(viewerState, nextZoom, zoomAnchor) {
|
|
1006
|
-
const normalizedZoom =
|
|
755
|
+
const normalizedZoom = clampMarkdownViewerZoom(nextZoom);
|
|
1007
756
|
const focalPoint = isMarkdownMermaidViewerOpen(viewerState) ? captureMarkdownMermaidViewerFocalPoint(viewerState, zoomAnchor) : void 0;
|
|
1008
757
|
viewerState.zoomValue = normalizedZoom;
|
|
1009
758
|
viewerState.dialogElement.classList.toggle(
|
|
1010
759
|
MERMAID_VIEWER_ZOOMED_CLASS_NAME,
|
|
1011
|
-
normalizedZoom >
|
|
760
|
+
normalizedZoom > VIEWER_DEFAULT_ZOOM
|
|
1012
761
|
);
|
|
1013
762
|
viewerState.zoomValueElement.textContent = `${Math.round(normalizedZoom * 100)}%`;
|
|
1014
|
-
viewerState.zoomOutButtonElement.disabled = normalizedZoom <=
|
|
1015
|
-
viewerState.zoomInButtonElement.disabled = normalizedZoom >=
|
|
1016
|
-
viewerState.resetButtonElement.disabled = normalizedZoom ===
|
|
763
|
+
viewerState.zoomOutButtonElement.disabled = normalizedZoom <= VIEWER_MIN_ZOOM;
|
|
764
|
+
viewerState.zoomInButtonElement.disabled = normalizedZoom >= VIEWER_MAX_ZOOM;
|
|
765
|
+
viewerState.resetButtonElement.disabled = normalizedZoom === VIEWER_DEFAULT_ZOOM;
|
|
1017
766
|
updateMarkdownMermaidViewerCanvasSize(viewerState);
|
|
1018
767
|
if (focalPoint) {
|
|
1019
768
|
applyMarkdownMermaidViewerFocalPoint(viewerState, focalPoint);
|
|
@@ -1036,319 +785,471 @@ function applyMarkdownMermaidViewerFocalPoint(viewerState, focalPoint) {
|
|
|
1036
785
|
viewerState.viewportElement.scrollTop = 0;
|
|
1037
786
|
const postCanvasRect = viewerState.canvasElement.getBoundingClientRect();
|
|
1038
787
|
if (postCanvasRect.width <= 0 || postCanvasRect.height <= 0) {
|
|
1039
|
-
return;
|
|
1040
|
-
}
|
|
1041
|
-
const targetCanvasClientLeft = focalPoint.anchorClientX - focalPoint.normalizedX * postCanvasRect.width;
|
|
1042
|
-
const targetCanvasClientTop = focalPoint.anchorClientY - focalPoint.normalizedY * postCanvasRect.height;
|
|
1043
|
-
viewerState.viewportElement.scrollLeft = postCanvasRect.left - targetCanvasClientLeft;
|
|
1044
|
-
viewerState.viewportElement.scrollTop = postCanvasRect.top - targetCanvasClientTop;
|
|
1045
|
-
}
|
|
1046
|
-
function updateMarkdownMermaidViewerCanvasSize(viewerState) {
|
|
1047
|
-
const viewportWidth = Math.max(
|
|
1048
|
-
viewerState.viewportElement.clientWidth * IMAGE_VIEWER_FIT_RATIO,
|
|
1049
|
-
1
|
|
1050
|
-
);
|
|
1051
|
-
const viewportHeight = Math.max(
|
|
1052
|
-
viewerState.viewportElement.clientHeight * IMAGE_VIEWER_FIT_RATIO,
|
|
1053
|
-
1
|
|
1054
|
-
);
|
|
1055
|
-
const naturalWidth = Math.max(viewerState.naturalWidth, 1);
|
|
1056
|
-
const naturalHeight = Math.max(viewerState.naturalHeight, 1);
|
|
1057
|
-
const fitScale = Math.min(1, viewportWidth / naturalWidth, viewportHeight / naturalHeight);
|
|
1058
|
-
const displayWidth = Math.max(1, Math.round(naturalWidth * fitScale * viewerState.zoomValue));
|
|
1059
|
-
const displayHeight = Math.max(1, Math.round(naturalHeight * fitScale * viewerState.zoomValue));
|
|
1060
|
-
viewerState.canvasElement.style.width = `${displayWidth}px`;
|
|
1061
|
-
viewerState.canvasElement.style.height = `${displayHeight}px`;
|
|
1062
|
-
const innerSvgElement = viewerState.canvasElement.querySelector("svg");
|
|
1063
|
-
if (innerSvgElement) {
|
|
1064
|
-
innerSvgElement.removeAttribute("width");
|
|
1065
|
-
innerSvgElement.removeAttribute("height");
|
|
1066
|
-
innerSvgElement.style.width = "100%";
|
|
1067
|
-
innerSvgElement.style.height = "100%";
|
|
1068
|
-
innerSvgElement.style.maxWidth = "none";
|
|
1069
|
-
innerSvgElement.style.display = "block";
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1072
|
-
function defaultScrollToHeading(targetElement) {
|
|
1073
|
-
targetElement.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
1074
|
-
}
|
|
1075
|
-
function hydrateMarkdown(rootElement, options = {}) {
|
|
1076
|
-
hydrateMarkdownImages(rootElement);
|
|
1077
|
-
hydrateMarkdownVideos(rootElement);
|
|
1078
|
-
hydrateMermaidBlocks(rootElement);
|
|
1079
|
-
hydrateCodeBlocks(rootElement);
|
|
1080
|
-
hydrateToc(rootElement, options.scrollToHeading ?? defaultScrollToHeading);
|
|
1081
|
-
}
|
|
1082
|
-
function mountMarkdownToolbar(container, options = {}) {
|
|
1083
|
-
const ownerDocument = container.ownerDocument;
|
|
1084
|
-
if (!ownerDocument) {
|
|
1085
|
-
return;
|
|
1086
|
-
}
|
|
1087
|
-
if (typeof ownerDocument.defaultView?.scrollTo !== "function") {
|
|
1088
|
-
return;
|
|
1089
|
-
}
|
|
1090
|
-
applyContentWidth(ownerDocument, loadContentWidth());
|
|
1091
|
-
mountPageToolbar(ownerDocument, container, options.scrollToHeading ?? defaultScrollToHeading);
|
|
1092
|
-
}
|
|
1093
|
-
function decorateCodeBlock(preElement) {
|
|
1094
|
-
if (preElement.parentElement?.classList.contains(CODE_BLOCK_CLASS_NAME)) {
|
|
1095
|
-
return;
|
|
1096
|
-
}
|
|
1097
|
-
if (preElement.dataset[CODE_BLOCK_HYDRATED_DATA_KEY] === "true") {
|
|
1098
|
-
return;
|
|
1099
|
-
}
|
|
1100
|
-
const codeElement = preElement.querySelector(":scope > code");
|
|
1101
|
-
if (!codeElement) {
|
|
1102
|
-
return;
|
|
1103
|
-
}
|
|
1104
|
-
preElement.dataset[CODE_BLOCK_HYDRATED_DATA_KEY] = "true";
|
|
1105
|
-
const ownerDocument = preElement.ownerDocument;
|
|
1106
|
-
const languageId = resolveCodeBlockLanguageId(codeElement);
|
|
1107
|
-
const languageLabel = resolveCodeBlockLanguageLabel(languageId);
|
|
1108
|
-
const originalCodeText = codeElement.textContent ?? "";
|
|
1109
|
-
const codeLineCount = rewriteCodeBlockLines(codeElement, ownerDocument);
|
|
1110
|
-
const chromeElement = ownerDocument.createElement("div");
|
|
1111
|
-
chromeElement.className = CODE_BLOCK_CHROME_CLASS_NAME;
|
|
1112
|
-
const langElement = ownerDocument.createElement("span");
|
|
1113
|
-
langElement.className = CODE_BLOCK_LANG_CLASS_NAME;
|
|
1114
|
-
langElement.textContent = languageLabel;
|
|
1115
|
-
chromeElement.append(langElement);
|
|
1116
|
-
const copyButtonElement = createCodeBlockCopyButton(ownerDocument);
|
|
1117
|
-
chromeElement.append(copyButtonElement);
|
|
1118
|
-
const figureElement = ownerDocument.createElement("figure");
|
|
1119
|
-
figureElement.className = CODE_BLOCK_CLASS_NAME;
|
|
1120
|
-
figureElement.dataset.scribdownCodeLang = languageId;
|
|
1121
|
-
const bodyElement = ownerDocument.createElement("div");
|
|
1122
|
-
bodyElement.className = CODE_BLOCK_BODY_CLASS_NAME;
|
|
1123
|
-
const gutterElement = createCodeBlockLineNumberGutter(ownerDocument, codeLineCount);
|
|
1124
|
-
preElement.replaceWith(figureElement);
|
|
1125
|
-
bodyElement.append(gutterElement, preElement);
|
|
1126
|
-
figureElement.append(chromeElement, bodyElement);
|
|
1127
|
-
const codeBlockSourceLine = codeElement.getAttribute(SOURCE_LINE_DATA_ATTRIBUTE);
|
|
1128
|
-
if (codeBlockSourceLine !== null) {
|
|
1129
|
-
figureElement.setAttribute(SOURCE_LINE_DATA_ATTRIBUTE, codeBlockSourceLine);
|
|
1130
|
-
codeElement.removeAttribute(SOURCE_LINE_DATA_ATTRIBUTE);
|
|
1131
|
-
}
|
|
1132
|
-
copyButtonElement.dataset.scribdownCodeSource = originalCodeText;
|
|
1133
|
-
copyButtonElement.addEventListener("click", handleCodeBlockCopyClick);
|
|
1134
|
-
}
|
|
1135
|
-
function resolveCodeBlockLanguageId(codeElement) {
|
|
1136
|
-
const languageClassName = Array.from(codeElement.classList).find(
|
|
1137
|
-
(className) => className.startsWith("language-")
|
|
1138
|
-
);
|
|
1139
|
-
if (!languageClassName) {
|
|
1140
|
-
return "";
|
|
1141
|
-
}
|
|
1142
|
-
return languageClassName.slice("language-".length).toLowerCase();
|
|
1143
|
-
}
|
|
1144
|
-
function resolveCodeBlockLanguageLabel(languageId) {
|
|
1145
|
-
if (!languageId) {
|
|
1146
|
-
return CODE_LANGUAGE_DEFAULT_LABEL;
|
|
1147
|
-
}
|
|
1148
|
-
const mappedLabel = CODE_LANGUAGE_DISPLAY_NAMES[languageId];
|
|
1149
|
-
if (mappedLabel) {
|
|
1150
|
-
return mappedLabel;
|
|
1151
|
-
}
|
|
1152
|
-
return languageId.charAt(0).toUpperCase() + languageId.slice(1);
|
|
1153
|
-
}
|
|
1154
|
-
function rewriteCodeBlockLines(codeElement, ownerDocument) {
|
|
1155
|
-
const shikiLineElements = codeElement.querySelectorAll(":scope > span.line");
|
|
1156
|
-
if (shikiLineElements.length > 0) {
|
|
1157
|
-
const shikiLineNodes = Array.from(shikiLineElements);
|
|
1158
|
-
shikiLineElements.forEach((lineElement) => {
|
|
1159
|
-
lineElement.classList.add(CODE_BLOCK_LINE_CLASS_NAME);
|
|
1160
|
-
if (lineElement.textContent === "") {
|
|
1161
|
-
lineElement.textContent = CODE_BLOCK_EMPTY_LINE_PLACEHOLDER;
|
|
1162
|
-
}
|
|
1163
|
-
});
|
|
1164
|
-
codeElement.replaceChildren(...shikiLineNodes);
|
|
1165
|
-
return shikiLineNodes.length;
|
|
1166
|
-
}
|
|
1167
|
-
const codeText = codeElement.textContent ?? "";
|
|
1168
|
-
const trimmedCodeText = codeText.replace(/\n+$/u, "");
|
|
1169
|
-
const codeLines = trimmedCodeText.split("\n");
|
|
1170
|
-
while (codeElement.firstChild) {
|
|
1171
|
-
codeElement.removeChild(codeElement.firstChild);
|
|
1172
|
-
}
|
|
1173
|
-
codeLines.forEach((lineText) => {
|
|
1174
|
-
const lineElement = ownerDocument.createElement("span");
|
|
1175
|
-
lineElement.className = CODE_BLOCK_LINE_CLASS_NAME;
|
|
1176
|
-
if (lineText.length === 0) {
|
|
1177
|
-
lineElement.textContent = CODE_BLOCK_EMPTY_LINE_PLACEHOLDER;
|
|
1178
|
-
} else {
|
|
1179
|
-
lineElement.textContent = lineText;
|
|
1180
|
-
}
|
|
1181
|
-
codeElement.append(lineElement);
|
|
1182
|
-
});
|
|
1183
|
-
return codeLines.length;
|
|
1184
|
-
}
|
|
1185
|
-
function createCodeBlockLineNumberGutter(ownerDocument, lineCount) {
|
|
1186
|
-
const gutterElement = ownerDocument.createElement("div");
|
|
1187
|
-
gutterElement.className = CODE_BLOCK_GUTTER_CLASS_NAME;
|
|
1188
|
-
for (let lineIndex = 0; lineIndex < lineCount; lineIndex += 1) {
|
|
1189
|
-
const lineNumberText = String(lineIndex + 1);
|
|
1190
|
-
const lineNumberElement = ownerDocument.createElement("span");
|
|
1191
|
-
lineNumberElement.className = CODE_BLOCK_GUTTER_LINE_CLASS_NAME;
|
|
1192
|
-
lineNumberElement.textContent = lineNumberText;
|
|
1193
|
-
gutterElement.append(lineNumberElement);
|
|
788
|
+
return;
|
|
1194
789
|
}
|
|
1195
|
-
|
|
790
|
+
const targetCanvasClientLeft = focalPoint.anchorClientX - focalPoint.normalizedX * postCanvasRect.width;
|
|
791
|
+
const targetCanvasClientTop = focalPoint.anchorClientY - focalPoint.normalizedY * postCanvasRect.height;
|
|
792
|
+
viewerState.viewportElement.scrollLeft = postCanvasRect.left - targetCanvasClientLeft;
|
|
793
|
+
viewerState.viewportElement.scrollTop = postCanvasRect.top - targetCanvasClientTop;
|
|
1196
794
|
}
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
copyButtonElement.type = "button";
|
|
1202
|
-
copyButtonElement.className = CODE_BLOCK_COPY_CLASS_NAME;
|
|
1203
|
-
copyButtonElement.setAttribute("aria-label", CODE_BLOCK_COPY_ARIA_LABEL);
|
|
1204
|
-
const copyIconElement = ownerDocument.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
1205
|
-
copyIconElement.setAttribute(
|
|
1206
|
-
"class",
|
|
1207
|
-
`${CODE_BLOCK_COPY_ICON_CLASS_NAME} ${CODE_BLOCK_COPY_ICON_COPY_CLASS_NAME}`
|
|
795
|
+
function updateMarkdownMermaidViewerCanvasSize(viewerState) {
|
|
796
|
+
const viewportWidth = Math.max(
|
|
797
|
+
viewerState.viewportElement.clientWidth * VIEWER_FIT_RATIO,
|
|
798
|
+
1
|
|
1208
799
|
);
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
copyIconElement.innerHTML = CODE_BLOCK_COPY_ICON_COPY_PATHS;
|
|
1213
|
-
copyButtonElement.append(copyIconElement);
|
|
1214
|
-
const checkIconElement = ownerDocument.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
1215
|
-
checkIconElement.setAttribute(
|
|
1216
|
-
"class",
|
|
1217
|
-
`${CODE_BLOCK_COPY_ICON_CLASS_NAME} ${CODE_BLOCK_COPY_ICON_CHECK_CLASS_NAME}`
|
|
800
|
+
const viewportHeight = Math.max(
|
|
801
|
+
viewerState.viewportElement.clientHeight * VIEWER_FIT_RATIO,
|
|
802
|
+
1
|
|
1218
803
|
);
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
804
|
+
const naturalWidth = Math.max(viewerState.naturalWidth, 1);
|
|
805
|
+
const naturalHeight = Math.max(viewerState.naturalHeight, 1);
|
|
806
|
+
const fitScale = Math.min(1, viewportWidth / naturalWidth, viewportHeight / naturalHeight);
|
|
807
|
+
const displayWidth = Math.max(1, Math.round(naturalWidth * fitScale * viewerState.zoomValue));
|
|
808
|
+
const displayHeight = Math.max(1, Math.round(naturalHeight * fitScale * viewerState.zoomValue));
|
|
809
|
+
viewerState.canvasElement.style.width = `${displayWidth}px`;
|
|
810
|
+
viewerState.canvasElement.style.height = `${displayHeight}px`;
|
|
811
|
+
const innerSvgElement = viewerState.canvasElement.querySelector("svg");
|
|
812
|
+
if (innerSvgElement) {
|
|
813
|
+
innerSvgElement.removeAttribute("width");
|
|
814
|
+
innerSvgElement.removeAttribute("height");
|
|
815
|
+
innerSvgElement.style.width = "100%";
|
|
816
|
+
innerSvgElement.style.height = "100%";
|
|
817
|
+
innerSvgElement.style.maxWidth = "none";
|
|
818
|
+
innerSvgElement.style.display = "block";
|
|
819
|
+
}
|
|
1225
820
|
}
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
821
|
+
|
|
822
|
+
// src/code/mermaid.ts
|
|
823
|
+
var MERMAID_LANGUAGE_ID = "mermaid";
|
|
824
|
+
var MERMAID_HYDRATED_DATA_KEY = "scribdownMermaidHydrated";
|
|
825
|
+
var MERMAID_RENDER_STARTED_DATA_KEY = "scribdownMermaidRenderStarted";
|
|
826
|
+
var MERMAID_SOURCE_DATA_KEY = "scribdownMermaidSourceText";
|
|
827
|
+
var MERMAID_FALLBACK_DEFAULT_TEXT = "\u56FE\u8868\u6E32\u67D3\u5931\u8D25";
|
|
828
|
+
var MERMAID_RENDER_ID_PREFIX = "scribdown-mermaid-";
|
|
829
|
+
var mermaidRenderIdCounter = 0;
|
|
830
|
+
var MERMAID_FULLSCREEN_BUTTON_ARIA_LABEL = "\u5168\u5C4F\u67E5\u770B\u56FE\u8868";
|
|
831
|
+
var MERMAID_FULLSCREEN_AVAILABLE_DATA_KEY = "scribdownMermaidFullscreenReady";
|
|
832
|
+
var MERMAID_VIEWER_SOURCE_DATA_KEY = "scribdownMermaidSource";
|
|
833
|
+
var mermaidLoaderPromise;
|
|
834
|
+
function hydrateMermaidBlocks(rootElement) {
|
|
835
|
+
const mermaidCodeElements = rootElement.querySelectorAll(
|
|
836
|
+
`pre > code.language-${MERMAID_LANGUAGE_ID}`
|
|
837
|
+
);
|
|
838
|
+
mermaidCodeElements.forEach((codeElement) => {
|
|
839
|
+
const preElement = codeElement.parentElement;
|
|
840
|
+
if (!preElement) {
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
if (preElement.dataset[CODE_BLOCK_HYDRATED_DATA_KEY] === "true") {
|
|
844
|
+
return;
|
|
1232
845
|
}
|
|
846
|
+
decorateMermaidBlock(preElement, codeElement);
|
|
1233
847
|
});
|
|
848
|
+
kickOffPendingMermaidRenders(rootElement);
|
|
1234
849
|
}
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
850
|
+
function kickOffPendingMermaidRenders(rootElement) {
|
|
851
|
+
const pendingFigures = rootElement.querySelectorAll(
|
|
852
|
+
`.${MERMAID_FIGURE_CLASS_NAME}.${MERMAID_FIGURE_LOADING_CLASS_NAME}`
|
|
853
|
+
);
|
|
854
|
+
pendingFigures.forEach((figureElement) => {
|
|
855
|
+
if (!figureElement.isConnected) {
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
if (figureElement.dataset[MERMAID_RENDER_STARTED_DATA_KEY] === "true") {
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
const canvasElement = figureElement.querySelector(
|
|
862
|
+
`.${MERMAID_CANVAS_CLASS_NAME}`
|
|
863
|
+
);
|
|
864
|
+
const mermaidSource = figureElement.dataset[MERMAID_SOURCE_DATA_KEY] ?? "";
|
|
865
|
+
if (!canvasElement || mermaidSource.length === 0) {
|
|
866
|
+
return;
|
|
1241
867
|
}
|
|
868
|
+
figureElement.dataset[MERMAID_RENDER_STARTED_DATA_KEY] = "true";
|
|
869
|
+
void renderMermaidIntoCanvas(figureElement, canvasElement, mermaidSource);
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
function decorateMermaidBlock(preElement, codeElement) {
|
|
873
|
+
if (preElement.dataset[MERMAID_HYDRATED_DATA_KEY] === "true") {
|
|
874
|
+
return;
|
|
1242
875
|
}
|
|
1243
|
-
|
|
1244
|
-
|
|
876
|
+
preElement.dataset[MERMAID_HYDRATED_DATA_KEY] = "true";
|
|
877
|
+
const ownerDocument = preElement.ownerDocument;
|
|
878
|
+
const mermaidSource = (codeElement.textContent ?? "").replace(/\n+$/u, "");
|
|
879
|
+
const figureElement = ownerDocument.createElement("figure");
|
|
880
|
+
figureElement.className = `${MERMAID_FIGURE_CLASS_NAME} ${MERMAID_FIGURE_LOADING_CLASS_NAME}`;
|
|
881
|
+
const chromeElement = ownerDocument.createElement("div");
|
|
882
|
+
chromeElement.className = MERMAID_CHROME_CLASS_NAME;
|
|
883
|
+
const labelElement = ownerDocument.createElement("span");
|
|
884
|
+
labelElement.className = MERMAID_LABEL_CLASS_NAME;
|
|
885
|
+
labelElement.textContent = MERMAID_LABEL_TEXT;
|
|
886
|
+
chromeElement.append(labelElement);
|
|
887
|
+
const bodyElement = ownerDocument.createElement("div");
|
|
888
|
+
bodyElement.className = MERMAID_BODY_CLASS_NAME;
|
|
889
|
+
const canvasElement = ownerDocument.createElement("div");
|
|
890
|
+
canvasElement.className = MERMAID_CANVAS_CLASS_NAME;
|
|
891
|
+
canvasElement.setAttribute("role", "img");
|
|
892
|
+
canvasElement.setAttribute("aria-label", MERMAID_LABEL_TEXT);
|
|
893
|
+
const fullscreenButtonElement = ownerDocument.createElement("button");
|
|
894
|
+
fullscreenButtonElement.type = "button";
|
|
895
|
+
fullscreenButtonElement.className = MERMAID_FULLSCREEN_BUTTON_CLASS_NAME;
|
|
896
|
+
fullscreenButtonElement.setAttribute("aria-label", MERMAID_FULLSCREEN_BUTTON_ARIA_LABEL);
|
|
897
|
+
fullscreenButtonElement.disabled = true;
|
|
898
|
+
fullscreenButtonElement.innerHTML = MERMAID_FULLSCREEN_ICON_SVG;
|
|
899
|
+
fullscreenButtonElement.addEventListener("click", handleMermaidFullscreenButtonClick);
|
|
900
|
+
bodyElement.append(canvasElement, fullscreenButtonElement);
|
|
901
|
+
figureElement.append(chromeElement, bodyElement);
|
|
902
|
+
const sourceLine = codeElement.getAttribute(SOURCE_LINE_DATA_ATTRIBUTE2);
|
|
903
|
+
if (sourceLine !== null) {
|
|
904
|
+
figureElement.setAttribute(SOURCE_LINE_DATA_ATTRIBUTE2, sourceLine);
|
|
1245
905
|
}
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
fallbackTextarea.setAttribute("readonly", "");
|
|
1249
|
-
fallbackTextarea.style.position = "fixed";
|
|
1250
|
-
fallbackTextarea.style.opacity = "0";
|
|
1251
|
-
fallbackTextarea.style.pointerEvents = "none";
|
|
1252
|
-
document.body.append(fallbackTextarea);
|
|
1253
|
-
fallbackTextarea.select();
|
|
1254
|
-
const isSucceeded = document.execCommand("copy");
|
|
1255
|
-
fallbackTextarea.remove();
|
|
1256
|
-
return isSucceeded;
|
|
906
|
+
figureElement.dataset[MERMAID_SOURCE_DATA_KEY] = mermaidSource;
|
|
907
|
+
preElement.replaceWith(figureElement);
|
|
1257
908
|
}
|
|
1258
|
-
function
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
909
|
+
async function loadMermaid() {
|
|
910
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
911
|
+
return void 0;
|
|
912
|
+
}
|
|
913
|
+
if (!mermaidLoaderPromise) {
|
|
914
|
+
mermaidLoaderPromise = (async () => {
|
|
915
|
+
const mermaidModule = await import("mermaid");
|
|
916
|
+
const mermaidApi = mermaidModule.default;
|
|
917
|
+
mermaidApi.initialize({
|
|
918
|
+
startOnLoad: false,
|
|
919
|
+
securityLevel: "strict",
|
|
920
|
+
theme: "default",
|
|
921
|
+
flowchart: { useMaxWidth: false },
|
|
922
|
+
sequence: { useMaxWidth: false },
|
|
923
|
+
class: { useMaxWidth: false },
|
|
924
|
+
state: { useMaxWidth: false },
|
|
925
|
+
gantt: { useMaxWidth: false },
|
|
926
|
+
er: { useMaxWidth: false },
|
|
927
|
+
journey: { useMaxWidth: false },
|
|
928
|
+
pie: { useMaxWidth: false },
|
|
929
|
+
requirement: { useMaxWidth: false },
|
|
930
|
+
c4: { useMaxWidth: false },
|
|
931
|
+
mindmap: { useMaxWidth: false },
|
|
932
|
+
timeline: { useMaxWidth: false },
|
|
933
|
+
gitGraph: { useMaxWidth: false },
|
|
934
|
+
quadrantChart: { useMaxWidth: false },
|
|
935
|
+
xyChart: { useMaxWidth: false },
|
|
936
|
+
sankey: { useMaxWidth: false },
|
|
937
|
+
block: { useMaxWidth: false }
|
|
938
|
+
});
|
|
939
|
+
return mermaidApi;
|
|
940
|
+
})().catch((loadError) => {
|
|
941
|
+
mermaidLoaderPromise = void 0;
|
|
942
|
+
throw loadError;
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
return mermaidLoaderPromise;
|
|
1265
946
|
}
|
|
1266
|
-
function
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
947
|
+
async function renderMermaidIntoCanvas(figureElement, canvasElement, mermaidSource) {
|
|
948
|
+
try {
|
|
949
|
+
const mermaidApi = await loadMermaid();
|
|
950
|
+
if (!mermaidApi) {
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
mermaidRenderIdCounter += 1;
|
|
954
|
+
const renderId = `${MERMAID_RENDER_ID_PREFIX}${mermaidRenderIdCounter}`;
|
|
955
|
+
const { svg, bindFunctions } = await mermaidApi.render(renderId, mermaidSource);
|
|
956
|
+
canvasElement.innerHTML = svg;
|
|
957
|
+
bindFunctions?.(canvasElement);
|
|
958
|
+
figureElement.classList.remove(MERMAID_FIGURE_LOADING_CLASS_NAME);
|
|
959
|
+
figureElement.classList.add(MERMAID_FIGURE_LOADED_CLASS_NAME);
|
|
960
|
+
figureElement.dataset[MERMAID_VIEWER_SOURCE_DATA_KEY] = svg;
|
|
961
|
+
figureElement.dataset[MERMAID_FULLSCREEN_AVAILABLE_DATA_KEY] = "true";
|
|
962
|
+
const fullscreenButtonElement = figureElement.querySelector(
|
|
963
|
+
`.${MERMAID_FULLSCREEN_BUTTON_CLASS_NAME}`
|
|
964
|
+
);
|
|
965
|
+
if (fullscreenButtonElement) {
|
|
966
|
+
fullscreenButtonElement.disabled = false;
|
|
967
|
+
}
|
|
968
|
+
} catch (renderError) {
|
|
969
|
+
showMermaidFallback(figureElement, canvasElement, mermaidSource, renderError);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
function showMermaidFallback(figureElement, canvasElement, mermaidSource, renderError) {
|
|
973
|
+
const ownerDocument = figureElement.ownerDocument;
|
|
974
|
+
figureElement.classList.remove(MERMAID_FIGURE_LOADING_CLASS_NAME);
|
|
975
|
+
figureElement.classList.add(MERMAID_FIGURE_FAILED_CLASS_NAME);
|
|
976
|
+
const fullscreenButtonElement = figureElement.querySelector(
|
|
977
|
+
`.${MERMAID_FULLSCREEN_BUTTON_CLASS_NAME}`
|
|
978
|
+
);
|
|
979
|
+
fullscreenButtonElement?.remove();
|
|
980
|
+
canvasElement.replaceChildren();
|
|
981
|
+
cleanupOrphanMermaidNodes(ownerDocument);
|
|
982
|
+
const fallbackElement = ownerDocument.createElement("div");
|
|
983
|
+
fallbackElement.className = MERMAID_FALLBACK_CLASS_NAME;
|
|
984
|
+
const iconElement = ownerDocument.createElement("span");
|
|
985
|
+
iconElement.className = MERMAID_FALLBACK_ICON_CLASS_NAME;
|
|
986
|
+
iconElement.setAttribute("aria-hidden", "true");
|
|
987
|
+
fallbackElement.append(iconElement);
|
|
988
|
+
const textElement = ownerDocument.createElement("p");
|
|
989
|
+
textElement.className = MERMAID_FALLBACK_TEXT_CLASS_NAME;
|
|
990
|
+
textElement.textContent = MERMAID_FALLBACK_DEFAULT_TEXT;
|
|
991
|
+
fallbackElement.append(textElement);
|
|
992
|
+
const errorMessage = extractErrorMessage(renderError);
|
|
993
|
+
if (errorMessage) {
|
|
994
|
+
const messageElement = ownerDocument.createElement("p");
|
|
995
|
+
messageElement.className = MERMAID_FALLBACK_SOURCE_CLASS_NAME;
|
|
996
|
+
messageElement.textContent = errorMessage;
|
|
997
|
+
fallbackElement.append(messageElement);
|
|
998
|
+
}
|
|
999
|
+
if (mermaidSource.length > 0) {
|
|
1000
|
+
const sourceElement = ownerDocument.createElement("pre");
|
|
1001
|
+
sourceElement.className = MERMAID_FALLBACK_SOURCE_CLASS_NAME;
|
|
1002
|
+
sourceElement.textContent = mermaidSource;
|
|
1003
|
+
fallbackElement.append(sourceElement);
|
|
1270
1004
|
}
|
|
1271
|
-
|
|
1272
|
-
imageElement.addEventListener("load", handleMarkdownImageLoad);
|
|
1273
|
-
imageElement.addEventListener("error", handleMarkdownImageError);
|
|
1274
|
-
bindMarkdownImageViewer(imageElement);
|
|
1005
|
+
canvasElement.replaceWith(fallbackElement);
|
|
1275
1006
|
}
|
|
1276
|
-
function
|
|
1277
|
-
|
|
1278
|
-
|
|
1007
|
+
function extractErrorMessage(renderError) {
|
|
1008
|
+
if (renderError instanceof Error) {
|
|
1009
|
+
return renderError.message;
|
|
1010
|
+
}
|
|
1011
|
+
if (typeof renderError === "string") {
|
|
1012
|
+
return renderError;
|
|
1013
|
+
}
|
|
1014
|
+
return "";
|
|
1279
1015
|
}
|
|
1280
|
-
function
|
|
1281
|
-
const
|
|
1282
|
-
|
|
1016
|
+
function cleanupOrphanMermaidNodes(ownerDocument) {
|
|
1017
|
+
const orphanNodes = ownerDocument.querySelectorAll(
|
|
1018
|
+
`body > [id^="${MERMAID_RENDER_ID_PREFIX}"], body > div[id^="d"][id*="mermaid"]`
|
|
1019
|
+
);
|
|
1020
|
+
orphanNodes.forEach((orphanNode) => {
|
|
1021
|
+
orphanNode.remove();
|
|
1022
|
+
});
|
|
1283
1023
|
}
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1024
|
+
var MERMAID_FULLSCREEN_ICON_SVG = '<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" focusable="false"><path d="M4 9 V4 H9 M15 4 H20 V9 M20 15 V20 H15 M9 20 H4 V15" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>';
|
|
1025
|
+
function handleMermaidFullscreenButtonClick(event) {
|
|
1026
|
+
const buttonElement = event.currentTarget;
|
|
1027
|
+
const figureElement = buttonElement.closest(`.${MERMAID_FIGURE_CLASS_NAME}`);
|
|
1028
|
+
if (!figureElement) {
|
|
1287
1029
|
return;
|
|
1288
1030
|
}
|
|
1289
|
-
|
|
1290
|
-
const isLoaded = imageElement.complete && imageElement.naturalWidth > 0;
|
|
1291
|
-
frameElement.classList.toggle(IMAGE_FRAME_FAILED_CLASS_NAME, isFailed);
|
|
1292
|
-
frameElement.classList.toggle(IMAGE_FRAME_LOADED_CLASS_NAME, isLoaded);
|
|
1293
|
-
}
|
|
1294
|
-
function bindMarkdownVideoState(videoElement) {
|
|
1295
|
-
updateMarkdownVideoState(videoElement);
|
|
1296
|
-
if (videoElement.dataset[VIDEO_HYDRATED_DATA_KEY] === "true") {
|
|
1031
|
+
if (figureElement.dataset[MERMAID_FULLSCREEN_AVAILABLE_DATA_KEY] !== "true") {
|
|
1297
1032
|
return;
|
|
1298
1033
|
}
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1034
|
+
const svgSource = figureElement.dataset[MERMAID_VIEWER_SOURCE_DATA_KEY] ?? "";
|
|
1035
|
+
if (svgSource.length === 0) {
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
event.preventDefault();
|
|
1039
|
+
event.stopPropagation();
|
|
1040
|
+
openMarkdownMermaidViewer(figureElement.ownerDocument, svgSource);
|
|
1305
1041
|
}
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1042
|
+
|
|
1043
|
+
// src/code/code-highlight.ts
|
|
1044
|
+
var CODE_HIGHLIGHTER_THEME = "github-light";
|
|
1045
|
+
var codeHighlighterPromise;
|
|
1046
|
+
var codeHighlighterLazyLoadPromises = /* @__PURE__ */ new Map();
|
|
1047
|
+
var CODE_BLOCK_HIGHLIGHT_PATTERN = /<pre([^>]*)><code class="language-([\w-]+)"([^>]*)>([\s\S]*?)<\/code><\/pre>/g;
|
|
1048
|
+
var SHIKI_CODE_INNER_PATTERN = /<code[^>]*>([\s\S]*?)<\/code>/u;
|
|
1049
|
+
async function getCodeHighlighter() {
|
|
1050
|
+
if (!codeHighlighterPromise) {
|
|
1051
|
+
codeHighlighterPromise = createHighlighterCore({
|
|
1052
|
+
themes: [githubLightTheme],
|
|
1053
|
+
// eager loaders 通过动态 import 拆为独立 chunk,初始化时并发拉取;
|
|
1054
|
+
// 主 bundle 不再内联 grammar JSON,体积更小,多个 grammar 也可并行加载。
|
|
1055
|
+
langs: CODE_HIGHLIGHTER_EAGER_LOADERS,
|
|
1056
|
+
// oniguruma 引擎 + wasm;shiki/wasm 内部走 base64 内联,扩展环境无需额外资源声明。
|
|
1057
|
+
engine: createOnigurumaEngine(import("shiki/wasm"))
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
return codeHighlighterPromise;
|
|
1309
1061
|
}
|
|
1310
|
-
function
|
|
1311
|
-
|
|
1312
|
-
|
|
1062
|
+
async function highlightMarkdownCodeBlocks(html) {
|
|
1063
|
+
if (!CODE_BLOCK_HIGHLIGHT_PATTERN.test(html)) {
|
|
1064
|
+
return html;
|
|
1065
|
+
}
|
|
1066
|
+
CODE_BLOCK_HIGHLIGHT_PATTERN.lastIndex = 0;
|
|
1067
|
+
const highlighter = await getCodeHighlighter();
|
|
1068
|
+
let resultHtml = "";
|
|
1069
|
+
let lastIndex = 0;
|
|
1070
|
+
for (const match of html.matchAll(CODE_BLOCK_HIGHLIGHT_PATTERN)) {
|
|
1071
|
+
const [matched, preAttributes, rawLanguage, codeAttributes, encodedCode] = match;
|
|
1072
|
+
const matchStart = match.index ?? 0;
|
|
1073
|
+
resultHtml += html.slice(lastIndex, matchStart);
|
|
1074
|
+
resultHtml += await highlightSingleCodeBlock(highlighter, {
|
|
1075
|
+
rawLanguage,
|
|
1076
|
+
encodedCode,
|
|
1077
|
+
matchedHtml: matched,
|
|
1078
|
+
preAttributes,
|
|
1079
|
+
codeAttributes
|
|
1080
|
+
});
|
|
1081
|
+
lastIndex = matchStart + matched.length;
|
|
1082
|
+
}
|
|
1083
|
+
resultHtml += html.slice(lastIndex);
|
|
1084
|
+
return resultHtml;
|
|
1313
1085
|
}
|
|
1314
|
-
function
|
|
1315
|
-
const
|
|
1316
|
-
const
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
if (!videoElement) {
|
|
1320
|
-
return;
|
|
1086
|
+
async function highlightSingleCodeBlock(highlighter, input) {
|
|
1087
|
+
const { rawLanguage, encodedCode, matchedHtml, preAttributes, codeAttributes } = input;
|
|
1088
|
+
const normalizedLanguage = rawLanguage.toLowerCase();
|
|
1089
|
+
if (normalizedLanguage === MERMAID_LANGUAGE_ID) {
|
|
1090
|
+
return matchedHtml;
|
|
1321
1091
|
}
|
|
1322
|
-
|
|
1323
|
-
|
|
1092
|
+
const resolvedLanguage = await ensureHighlighterLanguage(highlighter, normalizedLanguage);
|
|
1093
|
+
const codeText = decodeHtmlEntities(encodedCode);
|
|
1094
|
+
try {
|
|
1095
|
+
const highlightedHtml = highlighter.codeToHtml(codeText, {
|
|
1096
|
+
lang: resolvedLanguage,
|
|
1097
|
+
theme: CODE_HIGHLIGHTER_THEME
|
|
1098
|
+
});
|
|
1099
|
+
const innerMatch = highlightedHtml.match(SHIKI_CODE_INNER_PATTERN);
|
|
1100
|
+
const innerHtml = innerMatch?.[1] ?? encodedCode;
|
|
1101
|
+
return `<pre${preAttributes}><code class="language-${rawLanguage}"${codeAttributes}>${innerHtml}</code></pre>`;
|
|
1102
|
+
} catch {
|
|
1103
|
+
return matchedHtml;
|
|
1324
1104
|
}
|
|
1325
|
-
markMarkdownVideoFailed(videoElement);
|
|
1326
1105
|
}
|
|
1327
|
-
function
|
|
1328
|
-
|
|
1329
|
-
|
|
1106
|
+
async function ensureHighlighterLanguage(highlighter, normalizedLanguage) {
|
|
1107
|
+
if (normalizedLanguage.length === 0) {
|
|
1108
|
+
return "text";
|
|
1109
|
+
}
|
|
1110
|
+
if (highlighter.getLoadedLanguages().includes(normalizedLanguage)) {
|
|
1111
|
+
return normalizedLanguage;
|
|
1112
|
+
}
|
|
1113
|
+
const lazyLoader = CODE_HIGHLIGHTER_LAZY_LANGS[normalizedLanguage];
|
|
1114
|
+
if (!lazyLoader) {
|
|
1115
|
+
return "text";
|
|
1116
|
+
}
|
|
1117
|
+
let pending = codeHighlighterLazyLoadPromises.get(normalizedLanguage);
|
|
1118
|
+
if (!pending) {
|
|
1119
|
+
pending = (async () => {
|
|
1120
|
+
const grammarModule = await lazyLoader();
|
|
1121
|
+
await highlighter.loadLanguage(grammarModule.default);
|
|
1122
|
+
})();
|
|
1123
|
+
codeHighlighterLazyLoadPromises.set(normalizedLanguage, pending);
|
|
1124
|
+
}
|
|
1125
|
+
try {
|
|
1126
|
+
await pending;
|
|
1127
|
+
return normalizedLanguage;
|
|
1128
|
+
} catch {
|
|
1129
|
+
codeHighlighterLazyLoadPromises.delete(normalizedLanguage);
|
|
1130
|
+
return "text";
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
var HTML_NAMED_ENTITIES = {
|
|
1134
|
+
lt: "<",
|
|
1135
|
+
gt: ">",
|
|
1136
|
+
quot: '"',
|
|
1137
|
+
apos: "'",
|
|
1138
|
+
amp: "&"
|
|
1139
|
+
};
|
|
1140
|
+
var HTML_ENTITY_PATTERN = /&(?:(lt|gt|quot|apos|amp)|#x([0-9a-fA-F]+)|#(\d+));/g;
|
|
1141
|
+
function decodeHtmlEntities(encodedText) {
|
|
1142
|
+
return encodedText.replace(
|
|
1143
|
+
HTML_ENTITY_PATTERN,
|
|
1144
|
+
(matched, namedEntity, hexCodePoint, decimalCodePoint) => {
|
|
1145
|
+
if (namedEntity) {
|
|
1146
|
+
return HTML_NAMED_ENTITIES[namedEntity] ?? matched;
|
|
1147
|
+
}
|
|
1148
|
+
if (hexCodePoint) {
|
|
1149
|
+
return safeFromCodePoint(parseInt(hexCodePoint, 16), matched);
|
|
1150
|
+
}
|
|
1151
|
+
if (decimalCodePoint) {
|
|
1152
|
+
return safeFromCodePoint(parseInt(decimalCodePoint, 10), matched);
|
|
1153
|
+
}
|
|
1154
|
+
return matched;
|
|
1155
|
+
}
|
|
1330
1156
|
);
|
|
1331
|
-
|
|
1157
|
+
}
|
|
1158
|
+
function safeFromCodePoint(codePoint, fallback) {
|
|
1159
|
+
if (!Number.isFinite(codePoint) || codePoint < 0 || codePoint > 1114111) {
|
|
1160
|
+
return fallback;
|
|
1161
|
+
}
|
|
1162
|
+
try {
|
|
1163
|
+
return String.fromCodePoint(codePoint);
|
|
1164
|
+
} catch {
|
|
1165
|
+
return fallback;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
// src/core/source-line.ts
|
|
1170
|
+
import { SOURCE_LINE_DATA_ATTRIBUTE as SOURCE_LINE_DATA_ATTRIBUTE3 } from "@scribdown/shared";
|
|
1171
|
+
var SOURCE_LINE_HAST_PROPERTY = SOURCE_LINE_DATA_ATTRIBUTE3.replace(
|
|
1172
|
+
/-([a-z])/gu,
|
|
1173
|
+
(_match, letter) => letter.toUpperCase()
|
|
1174
|
+
);
|
|
1175
|
+
function isBlockContainerNode(node) {
|
|
1176
|
+
return node.type === "blockquote" || node.type === "list" || node.type === "listItem";
|
|
1177
|
+
}
|
|
1178
|
+
function remarkSourceLine() {
|
|
1179
|
+
return (tree) => {
|
|
1180
|
+
const blockNodes = tree.children;
|
|
1181
|
+
blockNodes.forEach((blockNode) => {
|
|
1182
|
+
annotateSourceLine(blockNode);
|
|
1183
|
+
annotateNestedBlockSourceLines(blockNode);
|
|
1184
|
+
});
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
function annotateSourceLine(node) {
|
|
1188
|
+
const startLine = node.position?.start.line;
|
|
1189
|
+
if (typeof startLine !== "number") {
|
|
1332
1190
|
return;
|
|
1333
1191
|
}
|
|
1334
|
-
const
|
|
1335
|
-
const
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
isLoaded && !isFailed
|
|
1340
|
-
);
|
|
1192
|
+
const nodeData = node.data ?? {};
|
|
1193
|
+
const hProperties = nodeData.hProperties ?? {};
|
|
1194
|
+
hProperties[SOURCE_LINE_HAST_PROPERTY] = startLine;
|
|
1195
|
+
nodeData.hProperties = hProperties;
|
|
1196
|
+
node.data = nodeData;
|
|
1341
1197
|
}
|
|
1342
|
-
function
|
|
1343
|
-
|
|
1344
|
-
`.${VIDEO_FRAME_CLASS_NAME}`
|
|
1345
|
-
);
|
|
1346
|
-
if (!frameElement) {
|
|
1198
|
+
function annotateNestedBlockSourceLines(node) {
|
|
1199
|
+
if (!isBlockContainerNode(node)) {
|
|
1347
1200
|
return;
|
|
1348
1201
|
}
|
|
1349
|
-
|
|
1350
|
-
|
|
1202
|
+
const childNodes = node.children;
|
|
1203
|
+
childNodes.forEach((childNode) => {
|
|
1204
|
+
annotateSourceLine(childNode);
|
|
1205
|
+
annotateNestedBlockSourceLines(childNode);
|
|
1206
|
+
});
|
|
1351
1207
|
}
|
|
1208
|
+
|
|
1209
|
+
// src/media/images.ts
|
|
1210
|
+
import {
|
|
1211
|
+
IMAGE_CAPTION_CLASS_NAME,
|
|
1212
|
+
IMAGE_ELEMENT_CLASS_NAME,
|
|
1213
|
+
IMAGE_FALLBACK_CLASS_NAME,
|
|
1214
|
+
IMAGE_FALLBACK_ICON_CLASS_NAME,
|
|
1215
|
+
IMAGE_FALLBACK_SOURCE_CLASS_NAME,
|
|
1216
|
+
IMAGE_FALLBACK_TEXT_CLASS_NAME,
|
|
1217
|
+
IMAGE_FIGURE_CLASS_NAME,
|
|
1218
|
+
IMAGE_FRAME_CLASS_NAME,
|
|
1219
|
+
IMAGE_FRAME_FAILED_CLASS_NAME,
|
|
1220
|
+
IMAGE_FRAME_LOADED_CLASS_NAME
|
|
1221
|
+
} from "@scribdown/shared";
|
|
1222
|
+
import { SKIP, visit } from "unist-util-visit";
|
|
1223
|
+
|
|
1224
|
+
// src/core/ast.ts
|
|
1225
|
+
import { toString as mdastToString } from "mdast-util-to-string";
|
|
1226
|
+
function extractNodeText(node) {
|
|
1227
|
+
return mdastToString(node, { includeHtml: false });
|
|
1228
|
+
}
|
|
1229
|
+
function isImageNode(node) {
|
|
1230
|
+
return node.type === "image" || node.type === "imageReference";
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
// src/media/image-viewer.ts
|
|
1234
|
+
import {
|
|
1235
|
+
IMAGE_VIEWER_BUTTON_CLASS_NAME,
|
|
1236
|
+
IMAGE_VIEWER_CAPTION_CLASS_NAME,
|
|
1237
|
+
IMAGE_VIEWER_CAPTION_GROUP_CLASS_NAME,
|
|
1238
|
+
IMAGE_VIEWER_CHROME_CLASS_NAME,
|
|
1239
|
+
IMAGE_VIEWER_CLOSE_BUTTON_CLASS_NAME,
|
|
1240
|
+
IMAGE_VIEWER_CONTROLS_CLASS_NAME,
|
|
1241
|
+
IMAGE_VIEWER_DIALOG_CLASS_NAME,
|
|
1242
|
+
IMAGE_VIEWER_DRAGGING_CLASS_NAME,
|
|
1243
|
+
IMAGE_VIEWER_HINT_CLASS_NAME,
|
|
1244
|
+
IMAGE_VIEWER_IMAGE_CLASS_NAME,
|
|
1245
|
+
IMAGE_VIEWER_VIEWPORT_CLASS_NAME,
|
|
1246
|
+
IMAGE_VIEWER_ZOOMED_CLASS_NAME,
|
|
1247
|
+
IMAGE_VIEWER_ZOOM_VALUE_CLASS_NAME
|
|
1248
|
+
} from "@scribdown/shared";
|
|
1249
|
+
var IMAGE_VIEWER_HINT_TEXT = "\u5FEB\u6377\u952E\uFF1A+/= \u653E\u5927 \xB7 - \u7F29\u5C0F \xB7 0 \u91CD\u7F6E \xB7 Esc \u5173\u95ED \xB7 \u9F20\u6807\u62D6\u62FD\u53EF\u5E73\u79FB \xB7 Ctrl/\u2318 + \u6EDA\u8F6E\u7F29\u653E";
|
|
1250
|
+
var IMAGE_VIEWER_FALLBACK_LABEL = "\u67E5\u770B\u5168\u56FE";
|
|
1251
|
+
var imageViewerStateByDocument = /* @__PURE__ */ new WeakMap();
|
|
1252
|
+
var imageViewerStateByDialogElement = /* @__PURE__ */ new WeakMap();
|
|
1352
1253
|
function bindMarkdownImageViewer(imageElement) {
|
|
1353
1254
|
const linkElement = imageElement.closest("a[href]");
|
|
1354
1255
|
if (linkElement) {
|
|
@@ -1402,7 +1303,7 @@ function openMarkdownImageViewer(sourceImageElement) {
|
|
|
1402
1303
|
);
|
|
1403
1304
|
viewerState.viewportElement.scrollLeft = 0;
|
|
1404
1305
|
viewerState.viewportElement.scrollTop = 0;
|
|
1405
|
-
viewerState.zoomValue =
|
|
1306
|
+
viewerState.zoomValue = VIEWER_DEFAULT_ZOOM;
|
|
1406
1307
|
showMarkdownImageViewerDialog(viewerState);
|
|
1407
1308
|
requestMarkdownImageViewerLayout(viewerState);
|
|
1408
1309
|
}
|
|
@@ -1424,21 +1325,21 @@ function createMarkdownImageViewerState(ownerDocument) {
|
|
|
1424
1325
|
const controlsElement = ownerDocument.createElement("div");
|
|
1425
1326
|
const zoomOutButtonElement = createMarkdownImageViewerButton(ownerDocument, {
|
|
1426
1327
|
ariaLabel: "\u7F29\u5C0F\u56FE\u7247",
|
|
1427
|
-
text:
|
|
1328
|
+
text: VIEWER_ZOOM_OUT_TEXT
|
|
1428
1329
|
});
|
|
1429
1330
|
const zoomValueElement = ownerDocument.createElement("span");
|
|
1430
1331
|
const zoomInButtonElement = createMarkdownImageViewerButton(ownerDocument, {
|
|
1431
1332
|
ariaLabel: "\u653E\u5927\u56FE\u7247",
|
|
1432
|
-
text:
|
|
1333
|
+
text: VIEWER_ZOOM_IN_TEXT
|
|
1433
1334
|
});
|
|
1434
1335
|
const resetButtonElement = createMarkdownImageViewerButton(ownerDocument, {
|
|
1435
1336
|
ariaLabel: "\u91CD\u7F6E\u7F29\u653E",
|
|
1436
|
-
text:
|
|
1337
|
+
text: VIEWER_RESET_TEXT
|
|
1437
1338
|
});
|
|
1438
1339
|
const closeButtonElement = createMarkdownImageViewerButton(ownerDocument, {
|
|
1439
1340
|
ariaLabel: "\u5173\u95ED\u5168\u56FE\u67E5\u770B",
|
|
1440
1341
|
className: IMAGE_VIEWER_CLOSE_BUTTON_CLASS_NAME,
|
|
1441
|
-
text:
|
|
1342
|
+
text: VIEWER_CLOSE_TEXT
|
|
1442
1343
|
});
|
|
1443
1344
|
const viewportElement = ownerDocument.createElement("div");
|
|
1444
1345
|
const imageElement = ownerDocument.createElement("img");
|
|
@@ -1459,7 +1360,7 @@ function createMarkdownImageViewerState(ownerDocument) {
|
|
|
1459
1360
|
viewportElement,
|
|
1460
1361
|
zoomInButtonElement,
|
|
1461
1362
|
zoomOutButtonElement,
|
|
1462
|
-
zoomValue:
|
|
1363
|
+
zoomValue: VIEWER_DEFAULT_ZOOM,
|
|
1463
1364
|
zoomValueElement
|
|
1464
1365
|
};
|
|
1465
1366
|
const ownerWindow = ownerDocument.defaultView;
|
|
@@ -1506,7 +1407,7 @@ function createMarkdownImageViewerState(ownerDocument) {
|
|
|
1506
1407
|
if (ownerWindow) {
|
|
1507
1408
|
ownerWindow.addEventListener("resize", handleMarkdownImageViewerWindowResize);
|
|
1508
1409
|
}
|
|
1509
|
-
updateMarkdownImageViewerZoom(viewerState,
|
|
1410
|
+
updateMarkdownImageViewerZoom(viewerState, VIEWER_DEFAULT_ZOOM);
|
|
1510
1411
|
return viewerState;
|
|
1511
1412
|
}
|
|
1512
1413
|
function createMarkdownImageViewerButton(ownerDocument, options) {
|
|
@@ -1564,17 +1465,17 @@ function handleMarkdownImageViewerKeyDown(event) {
|
|
|
1564
1465
|
}
|
|
1565
1466
|
if (event.key === "+" || event.key === "=") {
|
|
1566
1467
|
event.preventDefault();
|
|
1567
|
-
updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue +
|
|
1468
|
+
updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue + VIEWER_ZOOM_STEP);
|
|
1568
1469
|
return;
|
|
1569
1470
|
}
|
|
1570
1471
|
if (event.key === "-") {
|
|
1571
1472
|
event.preventDefault();
|
|
1572
|
-
updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue -
|
|
1473
|
+
updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue - VIEWER_ZOOM_STEP);
|
|
1573
1474
|
return;
|
|
1574
1475
|
}
|
|
1575
1476
|
if (event.key === "0") {
|
|
1576
1477
|
event.preventDefault();
|
|
1577
|
-
updateMarkdownImageViewerZoom(viewerState,
|
|
1478
|
+
updateMarkdownImageViewerZoom(viewerState, VIEWER_DEFAULT_ZOOM);
|
|
1578
1479
|
return;
|
|
1579
1480
|
}
|
|
1580
1481
|
if (event.key === "Escape") {
|
|
@@ -1590,10 +1491,10 @@ function handleMarkdownImageViewerWheel(event) {
|
|
|
1590
1491
|
if (!viewerState) {
|
|
1591
1492
|
return;
|
|
1592
1493
|
}
|
|
1593
|
-
const rawDelta = -event.deltaY *
|
|
1494
|
+
const rawDelta = -event.deltaY * VIEWER_WHEEL_ZOOM_FACTOR;
|
|
1594
1495
|
const zoomDelta = Math.max(
|
|
1595
|
-
-
|
|
1596
|
-
Math.min(
|
|
1496
|
+
-VIEWER_WHEEL_ZOOM_MAX_DELTA,
|
|
1497
|
+
Math.min(VIEWER_WHEEL_ZOOM_MAX_DELTA, rawDelta)
|
|
1597
1498
|
);
|
|
1598
1499
|
event.preventDefault();
|
|
1599
1500
|
updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue + zoomDelta, {
|
|
@@ -1652,177 +1553,552 @@ function handleMarkdownImageViewerPointerUp(event) {
|
|
|
1652
1553
|
function handleMarkdownImageViewerImageDragStart(event) {
|
|
1653
1554
|
event.preventDefault();
|
|
1654
1555
|
}
|
|
1655
|
-
function handleMarkdownImageViewerImageLoad(event) {
|
|
1656
|
-
const imageElement = event.currentTarget;
|
|
1657
|
-
const dialogElement = imageElement.closest(
|
|
1658
|
-
`.${IMAGE_VIEWER_DIALOG_CLASS_NAME}`
|
|
1556
|
+
function handleMarkdownImageViewerImageLoad(event) {
|
|
1557
|
+
const imageElement = event.currentTarget;
|
|
1558
|
+
const dialogElement = imageElement.closest(
|
|
1559
|
+
`.${IMAGE_VIEWER_DIALOG_CLASS_NAME}`
|
|
1560
|
+
);
|
|
1561
|
+
if (!dialogElement) {
|
|
1562
|
+
return;
|
|
1563
|
+
}
|
|
1564
|
+
const viewerState = imageViewerStateByDialogElement.get(dialogElement);
|
|
1565
|
+
if (!viewerState) {
|
|
1566
|
+
return;
|
|
1567
|
+
}
|
|
1568
|
+
viewerState.naturalWidth = imageElement.naturalWidth || viewerState.naturalWidth;
|
|
1569
|
+
viewerState.naturalHeight = imageElement.naturalHeight || viewerState.naturalHeight;
|
|
1570
|
+
updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue);
|
|
1571
|
+
}
|
|
1572
|
+
function handleMarkdownImageViewerWindowResize(event) {
|
|
1573
|
+
const ownerWindow = event.currentTarget;
|
|
1574
|
+
const viewerState = imageViewerStateByDocument.get(ownerWindow.document);
|
|
1575
|
+
if (!viewerState || !isMarkdownImageViewerOpen(viewerState)) {
|
|
1576
|
+
return;
|
|
1577
|
+
}
|
|
1578
|
+
updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue);
|
|
1579
|
+
}
|
|
1580
|
+
function handleMarkdownImageViewerZoomOutClick(event) {
|
|
1581
|
+
const viewerState = getMarkdownImageViewerStateFromEvent(event);
|
|
1582
|
+
if (!viewerState) {
|
|
1583
|
+
return;
|
|
1584
|
+
}
|
|
1585
|
+
updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue - VIEWER_ZOOM_STEP);
|
|
1586
|
+
}
|
|
1587
|
+
function handleMarkdownImageViewerZoomInClick(event) {
|
|
1588
|
+
const viewerState = getMarkdownImageViewerStateFromEvent(event);
|
|
1589
|
+
if (!viewerState) {
|
|
1590
|
+
return;
|
|
1591
|
+
}
|
|
1592
|
+
updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue + VIEWER_ZOOM_STEP);
|
|
1593
|
+
}
|
|
1594
|
+
function handleMarkdownImageViewerResetClick(event) {
|
|
1595
|
+
const viewerState = getMarkdownImageViewerStateFromEvent(event);
|
|
1596
|
+
if (!viewerState) {
|
|
1597
|
+
return;
|
|
1598
|
+
}
|
|
1599
|
+
updateMarkdownImageViewerZoom(viewerState, VIEWER_DEFAULT_ZOOM);
|
|
1600
|
+
}
|
|
1601
|
+
function handleMarkdownImageViewerCloseClick(event) {
|
|
1602
|
+
const viewerState = getMarkdownImageViewerStateFromEvent(event);
|
|
1603
|
+
if (!viewerState) {
|
|
1604
|
+
return;
|
|
1605
|
+
}
|
|
1606
|
+
closeMarkdownImageViewer(viewerState);
|
|
1607
|
+
}
|
|
1608
|
+
function getMarkdownImageViewerStateFromEvent(event) {
|
|
1609
|
+
const currentTargetElement = event.currentTarget;
|
|
1610
|
+
if (!currentTargetElement) {
|
|
1611
|
+
return void 0;
|
|
1612
|
+
}
|
|
1613
|
+
const dialogElement = currentTargetElement.closest(
|
|
1614
|
+
`.${IMAGE_VIEWER_DIALOG_CLASS_NAME}`
|
|
1615
|
+
);
|
|
1616
|
+
if (!dialogElement) {
|
|
1617
|
+
return void 0;
|
|
1618
|
+
}
|
|
1619
|
+
return imageViewerStateByDialogElement.get(dialogElement);
|
|
1620
|
+
}
|
|
1621
|
+
function updateMarkdownImageViewerZoom(viewerState, nextZoom, zoomAnchor) {
|
|
1622
|
+
const normalizedZoom = clampMarkdownViewerZoom(nextZoom);
|
|
1623
|
+
const focalPoint = isMarkdownImageViewerOpen(viewerState) ? captureMarkdownImageViewerFocalPoint(viewerState, zoomAnchor) : void 0;
|
|
1624
|
+
viewerState.zoomValue = normalizedZoom;
|
|
1625
|
+
viewerState.dialogElement.classList.toggle(
|
|
1626
|
+
IMAGE_VIEWER_ZOOMED_CLASS_NAME,
|
|
1627
|
+
normalizedZoom > VIEWER_DEFAULT_ZOOM
|
|
1628
|
+
);
|
|
1629
|
+
viewerState.zoomValueElement.textContent = `${Math.round(normalizedZoom * 100)}%`;
|
|
1630
|
+
viewerState.zoomOutButtonElement.disabled = normalizedZoom <= VIEWER_MIN_ZOOM;
|
|
1631
|
+
viewerState.zoomInButtonElement.disabled = normalizedZoom >= VIEWER_MAX_ZOOM;
|
|
1632
|
+
viewerState.resetButtonElement.disabled = normalizedZoom === VIEWER_DEFAULT_ZOOM;
|
|
1633
|
+
updateMarkdownImageViewerImageSize(viewerState);
|
|
1634
|
+
if (focalPoint) {
|
|
1635
|
+
applyMarkdownImageViewerFocalPoint(viewerState, focalPoint);
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
function captureMarkdownImageViewerFocalPoint(viewerState, zoomAnchor) {
|
|
1639
|
+
const preImageRect = viewerState.imageElement.getBoundingClientRect();
|
|
1640
|
+
if (preImageRect.width <= 0 || preImageRect.height <= 0) {
|
|
1641
|
+
return void 0;
|
|
1642
|
+
}
|
|
1643
|
+
const viewportRect = viewerState.viewportElement.getBoundingClientRect();
|
|
1644
|
+
const anchorClientX = zoomAnchor?.x ?? viewportRect.left + viewportRect.width / 2;
|
|
1645
|
+
const anchorClientY = zoomAnchor?.y ?? viewportRect.top + viewportRect.height / 2;
|
|
1646
|
+
const normalizedX = (anchorClientX - preImageRect.left) / preImageRect.width;
|
|
1647
|
+
const normalizedY = (anchorClientY - preImageRect.top) / preImageRect.height;
|
|
1648
|
+
return {
|
|
1649
|
+
anchorClientX,
|
|
1650
|
+
anchorClientY,
|
|
1651
|
+
normalizedX,
|
|
1652
|
+
normalizedY
|
|
1653
|
+
};
|
|
1654
|
+
}
|
|
1655
|
+
function applyMarkdownImageViewerFocalPoint(viewerState, focalPoint) {
|
|
1656
|
+
viewerState.viewportElement.scrollLeft = 0;
|
|
1657
|
+
viewerState.viewportElement.scrollTop = 0;
|
|
1658
|
+
const postImageRect = viewerState.imageElement.getBoundingClientRect();
|
|
1659
|
+
if (postImageRect.width <= 0 || postImageRect.height <= 0) {
|
|
1660
|
+
return;
|
|
1661
|
+
}
|
|
1662
|
+
const targetImageClientLeft = focalPoint.anchorClientX - focalPoint.normalizedX * postImageRect.width;
|
|
1663
|
+
const targetImageClientTop = focalPoint.anchorClientY - focalPoint.normalizedY * postImageRect.height;
|
|
1664
|
+
viewerState.viewportElement.scrollLeft = postImageRect.left - targetImageClientLeft;
|
|
1665
|
+
viewerState.viewportElement.scrollTop = postImageRect.top - targetImageClientTop;
|
|
1666
|
+
}
|
|
1667
|
+
function updateMarkdownImageViewerImageSize(viewerState) {
|
|
1668
|
+
const viewportWidth = Math.max(
|
|
1669
|
+
viewerState.viewportElement.clientWidth * VIEWER_FIT_RATIO,
|
|
1670
|
+
1
|
|
1671
|
+
);
|
|
1672
|
+
const viewportHeight = Math.max(
|
|
1673
|
+
viewerState.viewportElement.clientHeight * VIEWER_FIT_RATIO,
|
|
1674
|
+
1
|
|
1675
|
+
);
|
|
1676
|
+
const naturalWidth = Math.max(viewerState.naturalWidth, 1);
|
|
1677
|
+
const naturalHeight = Math.max(viewerState.naturalHeight, 1);
|
|
1678
|
+
const fitScale = Math.min(1, viewportWidth / naturalWidth, viewportHeight / naturalHeight);
|
|
1679
|
+
const displayWidth = Math.max(1, Math.round(naturalWidth * fitScale * viewerState.zoomValue));
|
|
1680
|
+
const displayHeight = Math.max(1, Math.round(naturalHeight * fitScale * viewerState.zoomValue));
|
|
1681
|
+
viewerState.imageElement.style.width = `${displayWidth}px`;
|
|
1682
|
+
viewerState.imageElement.style.height = `${displayHeight}px`;
|
|
1683
|
+
}
|
|
1684
|
+
function closeMarkdownImageViewer(viewerState) {
|
|
1685
|
+
if (typeof viewerState.dialogElement.close === "function" && viewerState.dialogElement.open) {
|
|
1686
|
+
viewerState.dialogElement.close();
|
|
1687
|
+
return;
|
|
1688
|
+
}
|
|
1689
|
+
viewerState.dialogElement.removeAttribute("open");
|
|
1690
|
+
resetMarkdownImageViewerAfterClose(viewerState);
|
|
1691
|
+
}
|
|
1692
|
+
function resetMarkdownImageViewerAfterClose(viewerState) {
|
|
1693
|
+
viewerState.dialogElement.classList.remove(IMAGE_VIEWER_ZOOMED_CLASS_NAME);
|
|
1694
|
+
viewerState.dialogElement.classList.remove(IMAGE_VIEWER_DRAGGING_CLASS_NAME);
|
|
1695
|
+
viewerState.isDragging = false;
|
|
1696
|
+
viewerState.imageElement.removeAttribute("src");
|
|
1697
|
+
viewerState.imageElement.removeAttribute("alt");
|
|
1698
|
+
viewerState.imageElement.removeAttribute("style");
|
|
1699
|
+
viewerState.captionElement.textContent = "";
|
|
1700
|
+
viewerState.viewportElement.scrollLeft = 0;
|
|
1701
|
+
viewerState.viewportElement.scrollTop = 0;
|
|
1702
|
+
viewerState.zoomValue = VIEWER_DEFAULT_ZOOM;
|
|
1703
|
+
if (viewerState.sourceImageElement?.isConnected) {
|
|
1704
|
+
viewerState.sourceImageElement.focus();
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
function isMarkdownImageViewerOpen(viewerState) {
|
|
1708
|
+
return viewerState.dialogElement.open || viewerState.dialogElement.hasAttribute("open");
|
|
1709
|
+
}
|
|
1710
|
+
function getMarkdownImageSource(imageElement) {
|
|
1711
|
+
return imageElement.currentSrc || imageElement.getAttribute("src") || imageElement.src || "";
|
|
1712
|
+
}
|
|
1713
|
+
function createMarkdownImageViewerAriaLabel(imageElement) {
|
|
1714
|
+
const imageAltText = imageElement.alt.trim();
|
|
1715
|
+
if (imageAltText.length === 0) {
|
|
1716
|
+
return IMAGE_VIEWER_FALLBACK_LABEL;
|
|
1717
|
+
}
|
|
1718
|
+
return `${IMAGE_VIEWER_FALLBACK_LABEL}\uFF1A${imageAltText}`;
|
|
1719
|
+
}
|
|
1720
|
+
function createMarkdownImageViewerCaption(imageElement, imageSource) {
|
|
1721
|
+
const imageAltText = imageElement.alt.trim();
|
|
1722
|
+
const imageTitleText = imageElement.title.trim();
|
|
1723
|
+
return imageAltText || imageTitleText || imageSource;
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
// src/media/images.ts
|
|
1727
|
+
var IMAGE_HYDRATED_DATA_KEY = "scribdownImageHydrated";
|
|
1728
|
+
function remarkImageFigures() {
|
|
1729
|
+
return (tree) => {
|
|
1730
|
+
visit(tree, "paragraph", (node, index, parent) => {
|
|
1731
|
+
if (parent === void 0 || index === void 0) {
|
|
1732
|
+
return;
|
|
1733
|
+
}
|
|
1734
|
+
const imageFigureNode = createImageFigureNode(node);
|
|
1735
|
+
if (imageFigureNode) {
|
|
1736
|
+
parent.children[index] = imageFigureNode;
|
|
1737
|
+
return SKIP;
|
|
1738
|
+
}
|
|
1739
|
+
});
|
|
1740
|
+
visit(tree, isImageNode, (node) => {
|
|
1741
|
+
decorateImageNode(node);
|
|
1742
|
+
});
|
|
1743
|
+
};
|
|
1744
|
+
}
|
|
1745
|
+
function createImageFigureNode(node) {
|
|
1746
|
+
if (node.children.length !== 1) {
|
|
1747
|
+
return void 0;
|
|
1748
|
+
}
|
|
1749
|
+
const onlyChild = node.children[0];
|
|
1750
|
+
if (!isImageNode(onlyChild)) {
|
|
1751
|
+
return void 0;
|
|
1752
|
+
}
|
|
1753
|
+
decorateImageNode(onlyChild);
|
|
1754
|
+
return {
|
|
1755
|
+
type: "imageFigure",
|
|
1756
|
+
// 关键步骤:保留原段落源码位置,使 remarkSourceLine 能为 figure 标注 data-source-line。
|
|
1757
|
+
position: node.position,
|
|
1758
|
+
data: {
|
|
1759
|
+
hName: "figure",
|
|
1760
|
+
hProperties: {
|
|
1761
|
+
className: [IMAGE_FIGURE_CLASS_NAME]
|
|
1762
|
+
}
|
|
1763
|
+
},
|
|
1764
|
+
children: [createImageFrameNode(onlyChild), ...createImageCaptionNodes(onlyChild)]
|
|
1765
|
+
};
|
|
1766
|
+
}
|
|
1767
|
+
function decorateImageNode(imageNode) {
|
|
1768
|
+
imageNode.data = {
|
|
1769
|
+
...imageNode.data,
|
|
1770
|
+
hProperties: {
|
|
1771
|
+
...imageNode.data?.hProperties,
|
|
1772
|
+
className: [IMAGE_ELEMENT_CLASS_NAME]
|
|
1773
|
+
}
|
|
1774
|
+
};
|
|
1775
|
+
}
|
|
1776
|
+
function createImageFrameNode(imageNode) {
|
|
1777
|
+
return {
|
|
1778
|
+
type: "imageFrame",
|
|
1779
|
+
data: {
|
|
1780
|
+
hName: "span",
|
|
1781
|
+
hProperties: {
|
|
1782
|
+
className: [IMAGE_FRAME_CLASS_NAME]
|
|
1783
|
+
}
|
|
1784
|
+
},
|
|
1785
|
+
children: [imageNode, createImageFallbackNode(imageNode)]
|
|
1786
|
+
};
|
|
1787
|
+
}
|
|
1788
|
+
function createImageCaptionNodes(imageNode) {
|
|
1789
|
+
const imageTitle = imageNode.type === "image" && typeof imageNode.title === "string" ? imageNode.title.trim() : "";
|
|
1790
|
+
if (imageTitle.length === 0) {
|
|
1791
|
+
return [];
|
|
1792
|
+
}
|
|
1793
|
+
return [
|
|
1794
|
+
{
|
|
1795
|
+
type: "imageCaption",
|
|
1796
|
+
data: {
|
|
1797
|
+
hName: "figcaption",
|
|
1798
|
+
hProperties: {
|
|
1799
|
+
className: [IMAGE_CAPTION_CLASS_NAME]
|
|
1800
|
+
}
|
|
1801
|
+
},
|
|
1802
|
+
children: [{ type: "text", value: imageTitle }]
|
|
1803
|
+
}
|
|
1804
|
+
];
|
|
1805
|
+
}
|
|
1806
|
+
function createImageFallbackNode(imageNode) {
|
|
1807
|
+
const fallbackText = imageNode.alt?.trim() || "\u56FE\u7247\u52A0\u8F7D\u5931\u8D25";
|
|
1808
|
+
const fallbackSource = imageNode.type === "image" ? imageNode.url : imageNode.identifier;
|
|
1809
|
+
return {
|
|
1810
|
+
type: "imageFallback",
|
|
1811
|
+
data: {
|
|
1812
|
+
hName: "span",
|
|
1813
|
+
hProperties: {
|
|
1814
|
+
className: [IMAGE_FALLBACK_CLASS_NAME]
|
|
1815
|
+
}
|
|
1816
|
+
},
|
|
1817
|
+
children: [
|
|
1818
|
+
{
|
|
1819
|
+
type: "imageFallbackIcon",
|
|
1820
|
+
data: {
|
|
1821
|
+
hName: "span",
|
|
1822
|
+
hProperties: {
|
|
1823
|
+
className: [IMAGE_FALLBACK_ICON_CLASS_NAME]
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
},
|
|
1827
|
+
{
|
|
1828
|
+
type: "imageFallbackText",
|
|
1829
|
+
data: {
|
|
1830
|
+
hName: "span",
|
|
1831
|
+
hProperties: {
|
|
1832
|
+
className: [IMAGE_FALLBACK_TEXT_CLASS_NAME]
|
|
1833
|
+
}
|
|
1834
|
+
},
|
|
1835
|
+
children: [{ type: "text", value: fallbackText }]
|
|
1836
|
+
},
|
|
1837
|
+
{
|
|
1838
|
+
type: "imageFallbackSource",
|
|
1839
|
+
data: {
|
|
1840
|
+
hName: "span",
|
|
1841
|
+
hProperties: {
|
|
1842
|
+
className: [IMAGE_FALLBACK_SOURCE_CLASS_NAME]
|
|
1843
|
+
}
|
|
1844
|
+
},
|
|
1845
|
+
children: [{ type: "text", value: fallbackSource }]
|
|
1846
|
+
}
|
|
1847
|
+
]
|
|
1848
|
+
};
|
|
1849
|
+
}
|
|
1850
|
+
function hydrateMarkdownImages(rootElement) {
|
|
1851
|
+
const imageElements = rootElement.querySelectorAll(
|
|
1852
|
+
`img.${IMAGE_ELEMENT_CLASS_NAME}`
|
|
1659
1853
|
);
|
|
1660
|
-
|
|
1661
|
-
return;
|
|
1662
|
-
}
|
|
1663
|
-
const viewerState = imageViewerStateByDialogElement.get(dialogElement);
|
|
1664
|
-
if (!viewerState) {
|
|
1665
|
-
return;
|
|
1666
|
-
}
|
|
1667
|
-
viewerState.naturalWidth = imageElement.naturalWidth || viewerState.naturalWidth;
|
|
1668
|
-
viewerState.naturalHeight = imageElement.naturalHeight || viewerState.naturalHeight;
|
|
1669
|
-
updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue);
|
|
1854
|
+
imageElements.forEach(bindMarkdownImageState);
|
|
1670
1855
|
}
|
|
1671
|
-
function
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
if (!viewerState || !isMarkdownImageViewerOpen(viewerState)) {
|
|
1856
|
+
function bindMarkdownImageState(imageElement) {
|
|
1857
|
+
updateMarkdownImageState(imageElement);
|
|
1858
|
+
if (imageElement.dataset[IMAGE_HYDRATED_DATA_KEY] === "true") {
|
|
1675
1859
|
return;
|
|
1676
1860
|
}
|
|
1677
|
-
|
|
1861
|
+
imageElement.dataset[IMAGE_HYDRATED_DATA_KEY] = "true";
|
|
1862
|
+
imageElement.addEventListener("load", handleMarkdownImageLoad);
|
|
1863
|
+
imageElement.addEventListener("error", handleMarkdownImageError);
|
|
1864
|
+
bindMarkdownImageViewer(imageElement);
|
|
1678
1865
|
}
|
|
1679
|
-
function
|
|
1680
|
-
const
|
|
1681
|
-
|
|
1682
|
-
return;
|
|
1683
|
-
}
|
|
1684
|
-
updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue - IMAGE_VIEWER_ZOOM_STEP);
|
|
1866
|
+
function handleMarkdownImageLoad(event) {
|
|
1867
|
+
const imageElement = event.currentTarget;
|
|
1868
|
+
updateMarkdownImageState(imageElement);
|
|
1685
1869
|
}
|
|
1686
|
-
function
|
|
1687
|
-
const
|
|
1688
|
-
|
|
1689
|
-
return;
|
|
1690
|
-
}
|
|
1691
|
-
updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue + IMAGE_VIEWER_ZOOM_STEP);
|
|
1870
|
+
function handleMarkdownImageError(event) {
|
|
1871
|
+
const imageElement = event.currentTarget;
|
|
1872
|
+
updateMarkdownImageState(imageElement);
|
|
1692
1873
|
}
|
|
1693
|
-
function
|
|
1694
|
-
const
|
|
1695
|
-
if (!
|
|
1874
|
+
function updateMarkdownImageState(imageElement) {
|
|
1875
|
+
const frameElement = imageElement.closest(`.${IMAGE_FRAME_CLASS_NAME}`);
|
|
1876
|
+
if (!frameElement) {
|
|
1696
1877
|
return;
|
|
1697
1878
|
}
|
|
1698
|
-
|
|
1879
|
+
const isFailed = imageElement.complete && imageElement.naturalWidth === 0;
|
|
1880
|
+
const isLoaded = imageElement.complete && imageElement.naturalWidth > 0;
|
|
1881
|
+
frameElement.classList.toggle(IMAGE_FRAME_FAILED_CLASS_NAME, isFailed);
|
|
1882
|
+
frameElement.classList.toggle(IMAGE_FRAME_LOADED_CLASS_NAME, isLoaded);
|
|
1699
1883
|
}
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1884
|
+
|
|
1885
|
+
// src/media/videos.ts
|
|
1886
|
+
import {
|
|
1887
|
+
VIDEO_ELEMENT_CLASS_NAME,
|
|
1888
|
+
VIDEO_FALLBACK_CLASS_NAME,
|
|
1889
|
+
VIDEO_FALLBACK_ICON_CLASS_NAME,
|
|
1890
|
+
VIDEO_FALLBACK_SOURCE_CLASS_NAME,
|
|
1891
|
+
VIDEO_FALLBACK_TEXT_CLASS_NAME,
|
|
1892
|
+
VIDEO_FIGURE_CLASS_NAME,
|
|
1893
|
+
VIDEO_FRAME_CLASS_NAME,
|
|
1894
|
+
VIDEO_FRAME_FAILED_CLASS_NAME,
|
|
1895
|
+
VIDEO_FRAME_LOADED_CLASS_NAME
|
|
1896
|
+
} from "@scribdown/shared";
|
|
1897
|
+
import { classnames } from "hast-util-classnames";
|
|
1898
|
+
import { SKIP as SKIP2, visit as visit2 } from "unist-util-visit";
|
|
1899
|
+
var VIDEO_HYDRATED_DATA_KEY = "scribdownVideoHydrated";
|
|
1900
|
+
var VIDEO_FALLBACK_DEFAULT_TEXT = "\u89C6\u9891\u52A0\u8F7D\u5931\u8D25";
|
|
1901
|
+
function rehypeVideoFigures() {
|
|
1902
|
+
return (tree) => {
|
|
1903
|
+
visit2(tree, "element", (node, index, parent) => {
|
|
1904
|
+
if (parent === void 0 || index === void 0) {
|
|
1905
|
+
return;
|
|
1906
|
+
}
|
|
1907
|
+
if (isHastVideoElement(node)) {
|
|
1908
|
+
parent.children[index] = createVideoFigureHast(node);
|
|
1909
|
+
return SKIP2;
|
|
1910
|
+
}
|
|
1911
|
+
const standaloneVideo = extractStandaloneParagraphVideo(node);
|
|
1912
|
+
if (standaloneVideo) {
|
|
1913
|
+
parent.children[index] = createVideoFigureHast(standaloneVideo);
|
|
1914
|
+
return SKIP2;
|
|
1915
|
+
}
|
|
1916
|
+
});
|
|
1917
|
+
};
|
|
1706
1918
|
}
|
|
1707
|
-
function
|
|
1708
|
-
|
|
1709
|
-
|
|
1919
|
+
function isHastVideoElement(node) {
|
|
1920
|
+
return node.type === "element" && node.tagName === "video";
|
|
1921
|
+
}
|
|
1922
|
+
function extractStandaloneParagraphVideo(node) {
|
|
1923
|
+
if (node.tagName !== "p") {
|
|
1710
1924
|
return void 0;
|
|
1711
1925
|
}
|
|
1712
|
-
const
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
return
|
|
1926
|
+
const significantChildren = node.children.filter((childNode) => {
|
|
1927
|
+
if (childNode.type === "text") {
|
|
1928
|
+
return childNode.value.trim().length > 0;
|
|
1929
|
+
}
|
|
1930
|
+
return true;
|
|
1931
|
+
});
|
|
1932
|
+
if (significantChildren.length === 1 && isHastVideoElement(significantChildren[0])) {
|
|
1933
|
+
return significantChildren[0];
|
|
1717
1934
|
}
|
|
1718
|
-
return
|
|
1935
|
+
return void 0;
|
|
1719
1936
|
}
|
|
1720
|
-
function
|
|
1721
|
-
|
|
1722
|
-
const
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
}
|
|
1937
|
+
function createVideoFigureHast(videoNode) {
|
|
1938
|
+
decorateHastVideoElement(videoNode);
|
|
1939
|
+
const sourceUrl = readHastVideoSourceUrl(videoNode);
|
|
1940
|
+
return {
|
|
1941
|
+
type: "element",
|
|
1942
|
+
tagName: "figure",
|
|
1943
|
+
properties: { className: [VIDEO_FIGURE_CLASS_NAME] },
|
|
1944
|
+
children: [
|
|
1945
|
+
{
|
|
1946
|
+
type: "element",
|
|
1947
|
+
tagName: "span",
|
|
1948
|
+
properties: { className: [VIDEO_FRAME_CLASS_NAME] },
|
|
1949
|
+
children: [videoNode, createVideoFallbackHast(sourceUrl)]
|
|
1950
|
+
}
|
|
1951
|
+
]
|
|
1952
|
+
};
|
|
1736
1953
|
}
|
|
1737
|
-
function
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1954
|
+
function decorateHastVideoElement(videoNode) {
|
|
1955
|
+
classnames(videoNode, VIDEO_ELEMENT_CLASS_NAME);
|
|
1956
|
+
}
|
|
1957
|
+
function readHastVideoSourceUrl(videoNode) {
|
|
1958
|
+
if (typeof videoNode.properties.src === "string") {
|
|
1959
|
+
return videoNode.properties.src;
|
|
1741
1960
|
}
|
|
1742
|
-
const
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1961
|
+
for (const childNode of videoNode.children) {
|
|
1962
|
+
if (childNode.type === "element" && childNode.tagName === "source" && typeof childNode.properties.src === "string") {
|
|
1963
|
+
return childNode.properties.src;
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
return "";
|
|
1967
|
+
}
|
|
1968
|
+
function createVideoFallbackHast(sourceUrl) {
|
|
1747
1969
|
return {
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1970
|
+
type: "element",
|
|
1971
|
+
tagName: "span",
|
|
1972
|
+
properties: { className: [VIDEO_FALLBACK_CLASS_NAME] },
|
|
1973
|
+
children: [
|
|
1974
|
+
{
|
|
1975
|
+
type: "element",
|
|
1976
|
+
tagName: "span",
|
|
1977
|
+
properties: { className: [VIDEO_FALLBACK_ICON_CLASS_NAME] },
|
|
1978
|
+
children: []
|
|
1979
|
+
},
|
|
1980
|
+
{
|
|
1981
|
+
type: "element",
|
|
1982
|
+
tagName: "span",
|
|
1983
|
+
properties: { className: [VIDEO_FALLBACK_TEXT_CLASS_NAME] },
|
|
1984
|
+
children: [{ type: "text", value: VIDEO_FALLBACK_DEFAULT_TEXT }]
|
|
1985
|
+
},
|
|
1986
|
+
{
|
|
1987
|
+
type: "element",
|
|
1988
|
+
tagName: "span",
|
|
1989
|
+
properties: { className: [VIDEO_FALLBACK_SOURCE_CLASS_NAME] },
|
|
1990
|
+
children: [{ type: "text", value: sourceUrl }]
|
|
1991
|
+
}
|
|
1992
|
+
]
|
|
1752
1993
|
};
|
|
1753
1994
|
}
|
|
1754
|
-
function
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1995
|
+
function hydrateMarkdownVideos(rootElement) {
|
|
1996
|
+
const videoElements = rootElement.querySelectorAll(
|
|
1997
|
+
`video.${VIDEO_ELEMENT_CLASS_NAME}`
|
|
1998
|
+
);
|
|
1999
|
+
videoElements.forEach(bindMarkdownVideoState);
|
|
2000
|
+
}
|
|
2001
|
+
function bindMarkdownVideoState(videoElement) {
|
|
2002
|
+
updateMarkdownVideoState(videoElement);
|
|
2003
|
+
if (videoElement.dataset[VIDEO_HYDRATED_DATA_KEY] === "true") {
|
|
1759
2004
|
return;
|
|
1760
2005
|
}
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
2006
|
+
videoElement.dataset[VIDEO_HYDRATED_DATA_KEY] = "true";
|
|
2007
|
+
videoElement.addEventListener("loadeddata", handleMarkdownVideoLoaded);
|
|
2008
|
+
videoElement.addEventListener("error", handleMarkdownVideoError);
|
|
2009
|
+
videoElement.querySelectorAll("source").forEach((sourceElement) => {
|
|
2010
|
+
sourceElement.addEventListener("error", handleMarkdownVideoSourceError);
|
|
2011
|
+
});
|
|
1765
2012
|
}
|
|
1766
|
-
function
|
|
1767
|
-
const
|
|
1768
|
-
|
|
1769
|
-
1
|
|
1770
|
-
);
|
|
1771
|
-
const viewportHeight = Math.max(
|
|
1772
|
-
viewerState.viewportElement.clientHeight * IMAGE_VIEWER_FIT_RATIO,
|
|
1773
|
-
1
|
|
1774
|
-
);
|
|
1775
|
-
const naturalWidth = Math.max(viewerState.naturalWidth, 1);
|
|
1776
|
-
const naturalHeight = Math.max(viewerState.naturalHeight, 1);
|
|
1777
|
-
const fitScale = Math.min(1, viewportWidth / naturalWidth, viewportHeight / naturalHeight);
|
|
1778
|
-
const displayWidth = Math.max(1, Math.round(naturalWidth * fitScale * viewerState.zoomValue));
|
|
1779
|
-
const displayHeight = Math.max(1, Math.round(naturalHeight * fitScale * viewerState.zoomValue));
|
|
1780
|
-
viewerState.imageElement.style.width = `${displayWidth}px`;
|
|
1781
|
-
viewerState.imageElement.style.height = `${displayHeight}px`;
|
|
2013
|
+
function handleMarkdownVideoLoaded(event) {
|
|
2014
|
+
const videoElement = event.currentTarget;
|
|
2015
|
+
updateMarkdownVideoState(videoElement);
|
|
1782
2016
|
}
|
|
1783
|
-
function
|
|
1784
|
-
|
|
2017
|
+
function handleMarkdownVideoError(event) {
|
|
2018
|
+
const videoElement = event.currentTarget;
|
|
2019
|
+
markMarkdownVideoFailed(videoElement);
|
|
1785
2020
|
}
|
|
1786
|
-
function
|
|
1787
|
-
|
|
1788
|
-
|
|
2021
|
+
function handleMarkdownVideoSourceError(event) {
|
|
2022
|
+
const sourceElement = event.currentTarget;
|
|
2023
|
+
const videoElement = sourceElement.closest(
|
|
2024
|
+
`video.${VIDEO_ELEMENT_CLASS_NAME}`
|
|
2025
|
+
);
|
|
2026
|
+
if (!videoElement) {
|
|
1789
2027
|
return;
|
|
1790
2028
|
}
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
}
|
|
1794
|
-
function resetMarkdownImageViewerAfterClose(viewerState) {
|
|
1795
|
-
viewerState.dialogElement.classList.remove(IMAGE_VIEWER_ZOOMED_CLASS_NAME);
|
|
1796
|
-
viewerState.dialogElement.classList.remove(IMAGE_VIEWER_DRAGGING_CLASS_NAME);
|
|
1797
|
-
viewerState.isDragging = false;
|
|
1798
|
-
viewerState.imageElement.removeAttribute("src");
|
|
1799
|
-
viewerState.imageElement.removeAttribute("alt");
|
|
1800
|
-
viewerState.imageElement.removeAttribute("style");
|
|
1801
|
-
viewerState.captionElement.textContent = "";
|
|
1802
|
-
viewerState.viewportElement.scrollLeft = 0;
|
|
1803
|
-
viewerState.viewportElement.scrollTop = 0;
|
|
1804
|
-
viewerState.zoomValue = IMAGE_VIEWER_DEFAULT_ZOOM;
|
|
1805
|
-
if (viewerState.sourceImageElement?.isConnected) {
|
|
1806
|
-
viewerState.sourceImageElement.focus();
|
|
2029
|
+
if (videoElement.networkState !== videoElement.NETWORK_NO_SOURCE) {
|
|
2030
|
+
return;
|
|
1807
2031
|
}
|
|
2032
|
+
markMarkdownVideoFailed(videoElement);
|
|
1808
2033
|
}
|
|
1809
|
-
function
|
|
1810
|
-
|
|
1811
|
-
}
|
|
1812
|
-
|
|
1813
|
-
|
|
2034
|
+
function updateMarkdownVideoState(videoElement) {
|
|
2035
|
+
const frameElement = videoElement.closest(
|
|
2036
|
+
`.${VIDEO_FRAME_CLASS_NAME}`
|
|
2037
|
+
);
|
|
2038
|
+
if (!frameElement) {
|
|
2039
|
+
return;
|
|
2040
|
+
}
|
|
2041
|
+
const isFailed = videoElement.error !== null || videoElement.networkState === videoElement.NETWORK_NO_SOURCE;
|
|
2042
|
+
const isLoaded = videoElement.readyState >= videoElement.HAVE_CURRENT_DATA;
|
|
2043
|
+
frameElement.classList.toggle(VIDEO_FRAME_FAILED_CLASS_NAME, isFailed);
|
|
2044
|
+
frameElement.classList.toggle(
|
|
2045
|
+
VIDEO_FRAME_LOADED_CLASS_NAME,
|
|
2046
|
+
isLoaded && !isFailed
|
|
2047
|
+
);
|
|
1814
2048
|
}
|
|
1815
|
-
function
|
|
1816
|
-
const
|
|
1817
|
-
|
|
1818
|
-
|
|
2049
|
+
function markMarkdownVideoFailed(videoElement) {
|
|
2050
|
+
const frameElement = videoElement.closest(
|
|
2051
|
+
`.${VIDEO_FRAME_CLASS_NAME}`
|
|
2052
|
+
);
|
|
2053
|
+
if (!frameElement) {
|
|
2054
|
+
return;
|
|
1819
2055
|
}
|
|
1820
|
-
|
|
2056
|
+
frameElement.classList.add(VIDEO_FRAME_FAILED_CLASS_NAME);
|
|
2057
|
+
frameElement.classList.remove(VIDEO_FRAME_LOADED_CLASS_NAME);
|
|
1821
2058
|
}
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
2059
|
+
|
|
2060
|
+
// src/sanitize.ts
|
|
2061
|
+
import {
|
|
2062
|
+
DETAILS_CLASS_NAME,
|
|
2063
|
+
FRAME_CLASS_NAME,
|
|
2064
|
+
FRONTMATTER_CHROME_CLASS_NAME,
|
|
2065
|
+
FRONTMATTER_CLASS_NAME,
|
|
2066
|
+
FRONTMATTER_LABEL_CLASS_NAME,
|
|
2067
|
+
FRONTMATTER_LIST_CLASS_NAME,
|
|
2068
|
+
FRONTMATTER_LIST_NESTED_CLASS_NAME,
|
|
2069
|
+
HEADING_MARK_CLASS_NAME,
|
|
2070
|
+
IMAGE_CAPTION_CLASS_NAME as IMAGE_CAPTION_CLASS_NAME2,
|
|
2071
|
+
IMAGE_ELEMENT_CLASS_NAME as IMAGE_ELEMENT_CLASS_NAME2,
|
|
2072
|
+
IMAGE_FALLBACK_CLASS_NAME as IMAGE_FALLBACK_CLASS_NAME2,
|
|
2073
|
+
IMAGE_FALLBACK_ICON_CLASS_NAME as IMAGE_FALLBACK_ICON_CLASS_NAME2,
|
|
2074
|
+
IMAGE_FALLBACK_SOURCE_CLASS_NAME as IMAGE_FALLBACK_SOURCE_CLASS_NAME2,
|
|
2075
|
+
IMAGE_FALLBACK_TEXT_CLASS_NAME as IMAGE_FALLBACK_TEXT_CLASS_NAME2,
|
|
2076
|
+
IMAGE_FIGURE_CLASS_NAME as IMAGE_FIGURE_CLASS_NAME2,
|
|
2077
|
+
IMAGE_FRAME_CLASS_NAME as IMAGE_FRAME_CLASS_NAME2,
|
|
2078
|
+
TOC_CLASS_NAME,
|
|
2079
|
+
TOC_ITEM_CLASS_PREFIX,
|
|
2080
|
+
TOC_LINK_CLASS_NAME,
|
|
2081
|
+
TOC_LIST_CLASS_NAME,
|
|
2082
|
+
TOC_LIST_NESTED_CLASS_NAME,
|
|
2083
|
+
TOC_NAV_CLASS_NAME,
|
|
2084
|
+
TOC_SUMMARY_CLASS_NAME,
|
|
2085
|
+
TOC_TOGGLE_CLASS_NAME,
|
|
2086
|
+
VIDEO_ELEMENT_CLASS_NAME as VIDEO_ELEMENT_CLASS_NAME2,
|
|
2087
|
+
VIDEO_FALLBACK_CLASS_NAME as VIDEO_FALLBACK_CLASS_NAME2,
|
|
2088
|
+
VIDEO_FALLBACK_ICON_CLASS_NAME as VIDEO_FALLBACK_ICON_CLASS_NAME2,
|
|
2089
|
+
VIDEO_FALLBACK_SOURCE_CLASS_NAME as VIDEO_FALLBACK_SOURCE_CLASS_NAME2,
|
|
2090
|
+
VIDEO_FALLBACK_TEXT_CLASS_NAME as VIDEO_FALLBACK_TEXT_CLASS_NAME2,
|
|
2091
|
+
VIDEO_FIGURE_CLASS_NAME as VIDEO_FIGURE_CLASS_NAME2,
|
|
2092
|
+
VIDEO_FRAME_CLASS_NAME as VIDEO_FRAME_CLASS_NAME2
|
|
2093
|
+
} from "@scribdown/shared";
|
|
2094
|
+
import DOMPurify from "dompurify";
|
|
2095
|
+
import { defaultSchema } from "rehype-sanitize";
|
|
2096
|
+
function sanitizeHtmlWithDomPurify(unsafeHtml) {
|
|
2097
|
+
const domPurify = DOMPurify;
|
|
2098
|
+
if (typeof domPurify.sanitize !== "function") {
|
|
2099
|
+
return unsafeHtml;
|
|
2100
|
+
}
|
|
2101
|
+
return domPurify.sanitize(unsafeHtml);
|
|
1826
2102
|
}
|
|
1827
2103
|
function createScribdownSanitizeSchema() {
|
|
1828
2104
|
const defaultTagNames = defaultSchema.tagNames ?? [];
|
|
@@ -1835,7 +2111,7 @@ function createScribdownSanitizeSchema() {
|
|
|
1835
2111
|
...defaultAttributes.summary ?? [],
|
|
1836
2112
|
["className", TOC_SUMMARY_CLASS_NAME]
|
|
1837
2113
|
];
|
|
1838
|
-
const navAttributes = ["ariaLabel", ["className",
|
|
2114
|
+
const navAttributes = ["ariaLabel", ["className", TOC_NAV_CLASS_NAME]];
|
|
1839
2115
|
const linkAttributes = [
|
|
1840
2116
|
"ariaDescribedBy",
|
|
1841
2117
|
"ariaLabel",
|
|
@@ -1855,32 +2131,40 @@ function createScribdownSanitizeSchema() {
|
|
|
1855
2131
|
"ariaDescribedBy",
|
|
1856
2132
|
"ariaLabel",
|
|
1857
2133
|
"ariaLabelledBy",
|
|
1858
|
-
["className", "contains-task-list",
|
|
2134
|
+
["className", "contains-task-list", TOC_LIST_CLASS_NAME, TOC_LIST_NESTED_CLASS_NAME]
|
|
2135
|
+
];
|
|
2136
|
+
const divAttributes = [
|
|
2137
|
+
...defaultAttributes.div ?? [],
|
|
2138
|
+
["className", FRONTMATTER_CLASS_NAME, FRAME_CLASS_NAME, FRONTMATTER_CHROME_CLASS_NAME]
|
|
2139
|
+
];
|
|
2140
|
+
const definitionListAttributes = [
|
|
2141
|
+
["className", FRONTMATTER_LIST_CLASS_NAME, FRONTMATTER_LIST_NESTED_CLASS_NAME]
|
|
1859
2142
|
];
|
|
1860
2143
|
const spanAttributes = [
|
|
1861
2144
|
...defaultAttributes.span ?? [],
|
|
1862
2145
|
[
|
|
1863
2146
|
"className",
|
|
1864
2147
|
HEADING_MARK_CLASS_NAME,
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
2148
|
+
FRONTMATTER_LABEL_CLASS_NAME,
|
|
2149
|
+
IMAGE_FRAME_CLASS_NAME2,
|
|
2150
|
+
IMAGE_FALLBACK_CLASS_NAME2,
|
|
2151
|
+
IMAGE_FALLBACK_ICON_CLASS_NAME2,
|
|
2152
|
+
IMAGE_FALLBACK_TEXT_CLASS_NAME2,
|
|
2153
|
+
IMAGE_FALLBACK_SOURCE_CLASS_NAME2,
|
|
2154
|
+
VIDEO_FRAME_CLASS_NAME2,
|
|
2155
|
+
VIDEO_FALLBACK_CLASS_NAME2,
|
|
2156
|
+
VIDEO_FALLBACK_ICON_CLASS_NAME2,
|
|
2157
|
+
VIDEO_FALLBACK_TEXT_CLASS_NAME2,
|
|
2158
|
+
VIDEO_FALLBACK_SOURCE_CLASS_NAME2
|
|
1875
2159
|
]
|
|
1876
2160
|
];
|
|
1877
2161
|
const figureAttributes = [
|
|
1878
|
-
["className",
|
|
2162
|
+
["className", IMAGE_FIGURE_CLASS_NAME2, VIDEO_FIGURE_CLASS_NAME2]
|
|
1879
2163
|
];
|
|
1880
|
-
const figcaptionAttributes = [["className",
|
|
2164
|
+
const figcaptionAttributes = [["className", IMAGE_CAPTION_CLASS_NAME2]];
|
|
1881
2165
|
const imageAttributes = [
|
|
1882
2166
|
...defaultAttributes.img ?? [],
|
|
1883
|
-
["className",
|
|
2167
|
+
["className", IMAGE_ELEMENT_CLASS_NAME2]
|
|
1884
2168
|
];
|
|
1885
2169
|
const videoAttributes = [
|
|
1886
2170
|
"src",
|
|
@@ -1895,13 +2179,17 @@ function createScribdownSanitizeSchema() {
|
|
|
1895
2179
|
"loop",
|
|
1896
2180
|
"autoPlay",
|
|
1897
2181
|
"crossOrigin",
|
|
1898
|
-
["className",
|
|
2182
|
+
["className", VIDEO_ELEMENT_CLASS_NAME2]
|
|
1899
2183
|
];
|
|
1900
2184
|
const sourceAttributes = ["src", "type", "media"];
|
|
1901
2185
|
const abbrAttributes = ["title"];
|
|
1902
2186
|
const listItemAttributes = [
|
|
1903
2187
|
"dataTocIndex",
|
|
1904
|
-
[
|
|
2188
|
+
[
|
|
2189
|
+
"className",
|
|
2190
|
+
"task-list-item",
|
|
2191
|
+
new RegExp(`^${TOC_ITEM_CLASS_PREFIX}(?:--(?:branch|depth-[1-6]))?$`, "u")
|
|
2192
|
+
]
|
|
1905
2193
|
];
|
|
1906
2194
|
const wildcardAttributes = [...defaultAttributes["*"] ?? [], SOURCE_LINE_HAST_PROPERTY];
|
|
1907
2195
|
return {
|
|
@@ -1940,6 +2228,8 @@ function createScribdownSanitizeSchema() {
|
|
|
1940
2228
|
a: linkAttributes,
|
|
1941
2229
|
ol: orderedListAttributes,
|
|
1942
2230
|
li: listItemAttributes,
|
|
2231
|
+
div: divAttributes,
|
|
2232
|
+
dl: definitionListAttributes,
|
|
1943
2233
|
span: spanAttributes,
|
|
1944
2234
|
abbr: abbrAttributes,
|
|
1945
2235
|
figure: figureAttributes,
|
|
@@ -1950,108 +2240,24 @@ function createScribdownSanitizeSchema() {
|
|
|
1950
2240
|
}
|
|
1951
2241
|
};
|
|
1952
2242
|
}
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
}
|
|
1958
|
-
var BLOCK_CONTAINER_NODE_TYPES = /* @__PURE__ */ new Set(["list", "listItem", "blockquote"]);
|
|
1959
|
-
function remarkSourceLine() {
|
|
1960
|
-
return (tree) => {
|
|
1961
|
-
const blockNodes = tree.children ?? [];
|
|
1962
|
-
blockNodes.forEach((blockNode) => {
|
|
1963
|
-
annotateSourceLine(blockNode);
|
|
1964
|
-
annotateNestedBlockSourceLines(blockNode);
|
|
1965
|
-
});
|
|
1966
|
-
};
|
|
1967
|
-
}
|
|
1968
|
-
function annotateSourceLine(node) {
|
|
1969
|
-
const startLine = node.position?.start.line;
|
|
1970
|
-
if (typeof startLine !== "number") {
|
|
1971
|
-
return;
|
|
1972
|
-
}
|
|
1973
|
-
const nodeData = node.data ?? {};
|
|
1974
|
-
const hProperties = nodeData.hProperties ?? {};
|
|
1975
|
-
hProperties[SOURCE_LINE_HAST_PROPERTY] = startLine;
|
|
1976
|
-
nodeData.hProperties = hProperties;
|
|
1977
|
-
node.data = nodeData;
|
|
1978
|
-
}
|
|
1979
|
-
function annotateNestedBlockSourceLines(node) {
|
|
1980
|
-
if (!BLOCK_CONTAINER_NODE_TYPES.has(node.type)) {
|
|
1981
|
-
return;
|
|
1982
|
-
}
|
|
1983
|
-
const childNodes = node.children ?? [];
|
|
1984
|
-
childNodes.forEach((childNode) => {
|
|
1985
|
-
annotateSourceLine(childNode);
|
|
1986
|
-
annotateNestedBlockSourceLines(childNode);
|
|
1987
|
-
});
|
|
1988
|
-
}
|
|
1989
|
-
function transformHighlightMarks(node) {
|
|
1990
|
-
if (!node.children) {
|
|
1991
|
-
return;
|
|
1992
|
-
}
|
|
1993
|
-
const nextChildren = [];
|
|
1994
|
-
for (let childIndex = 0; childIndex < node.children.length; childIndex += 1) {
|
|
1995
|
-
const childNode = node.children[childIndex];
|
|
1996
|
-
if (childNode.type === "text" && typeof childNode.value === "string" && childNode.value.includes("==")) {
|
|
1997
|
-
nextChildren.push(...splitHighlightMarks(childNode.value));
|
|
1998
|
-
continue;
|
|
1999
|
-
}
|
|
2000
|
-
transformHighlightMarks(childNode);
|
|
2001
|
-
nextChildren.push(childNode);
|
|
2002
|
-
}
|
|
2003
|
-
node.children = nextChildren;
|
|
2004
|
-
}
|
|
2005
|
-
function splitHighlightMarks(textValue) {
|
|
2006
|
-
const segments = [];
|
|
2007
|
-
let cursor = 0;
|
|
2008
|
-
HIGHLIGHT_MARKER_PATTERN.lastIndex = 0;
|
|
2009
|
-
let match = HIGHLIGHT_MARKER_PATTERN.exec(textValue);
|
|
2010
|
-
while (match !== null) {
|
|
2011
|
-
const leadingText = textValue.slice(cursor, match.index);
|
|
2012
|
-
if (leadingText.length > 0) {
|
|
2013
|
-
segments.push({ type: "text", value: leadingText });
|
|
2014
|
-
}
|
|
2015
|
-
segments.push({
|
|
2016
|
-
type: "highlightMark",
|
|
2017
|
-
data: {
|
|
2018
|
-
hName: "mark"
|
|
2019
|
-
},
|
|
2020
|
-
children: [{ type: "text", value: match[1] }]
|
|
2021
|
-
});
|
|
2022
|
-
cursor = match.index + match[0].length;
|
|
2023
|
-
match = HIGHLIGHT_MARKER_PATTERN.exec(textValue);
|
|
2024
|
-
}
|
|
2025
|
-
const trailingText = textValue.slice(cursor);
|
|
2026
|
-
if (trailingText.length > 0) {
|
|
2027
|
-
segments.push({ type: "text", value: trailingText });
|
|
2028
|
-
}
|
|
2029
|
-
return segments;
|
|
2030
|
-
}
|
|
2243
|
+
|
|
2244
|
+
// src/syntax/definition-lists.ts
|
|
2245
|
+
import { visit as visit3 } from "unist-util-visit";
|
|
2246
|
+
var DEFINITION_LIST_PARAGRAPH_PATTERN = /^\s*([^\n:][^\n]*)\n\s*:\s+([^\n]+)\s*$/u;
|
|
2031
2247
|
function remarkDefinitionLists() {
|
|
2032
2248
|
return (tree) => {
|
|
2033
|
-
|
|
2249
|
+
visit3(tree, "paragraph", (node, index, parent) => {
|
|
2250
|
+
if (parent === void 0 || index === void 0) {
|
|
2251
|
+
return;
|
|
2252
|
+
}
|
|
2253
|
+
const definitionListNode = createDefinitionListNode(node);
|
|
2254
|
+
if (definitionListNode) {
|
|
2255
|
+
parent.children[index] = definitionListNode;
|
|
2256
|
+
}
|
|
2257
|
+
});
|
|
2034
2258
|
};
|
|
2035
2259
|
}
|
|
2036
|
-
function transformDefinitionLists(node) {
|
|
2037
|
-
if (!node.children) {
|
|
2038
|
-
return;
|
|
2039
|
-
}
|
|
2040
|
-
const childNodes = node.children;
|
|
2041
|
-
for (let childIndex = 0; childIndex < childNodes.length; childIndex += 1) {
|
|
2042
|
-
const childNode = childNodes[childIndex];
|
|
2043
|
-
const definitionListNode = createDefinitionListNode(childNode);
|
|
2044
|
-
if (definitionListNode) {
|
|
2045
|
-
childNodes[childIndex] = definitionListNode;
|
|
2046
|
-
continue;
|
|
2047
|
-
}
|
|
2048
|
-
transformDefinitionLists(childNode);
|
|
2049
|
-
}
|
|
2050
|
-
}
|
|
2051
2260
|
function createDefinitionListNode(node) {
|
|
2052
|
-
if (node.type !== "paragraph") {
|
|
2053
|
-
return void 0;
|
|
2054
|
-
}
|
|
2055
2261
|
const paragraphText = extractNodeText(node);
|
|
2056
2262
|
const definitionMatch = DEFINITION_LIST_PARAGRAPH_PATTERN.exec(paragraphText);
|
|
2057
2263
|
if (!definitionMatch) {
|
|
@@ -2087,138 +2293,221 @@ function createDefinitionListNode(node) {
|
|
|
2087
2293
|
]
|
|
2088
2294
|
};
|
|
2089
2295
|
}
|
|
2090
|
-
|
|
2296
|
+
|
|
2297
|
+
// src/syntax/frame.ts
|
|
2298
|
+
import { DETAILS_CLASS_NAME as DETAILS_CLASS_NAME2, FRAME_CLASS_NAME as FRAME_CLASS_NAME2, TOC_CLASS_NAME as TOC_CLASS_NAME2 } from "@scribdown/shared";
|
|
2299
|
+
import { classnames as classnames2 } from "hast-util-classnames";
|
|
2300
|
+
import { visit as visit4 } from "unist-util-visit";
|
|
2301
|
+
function rehypeFrameClass() {
|
|
2091
2302
|
return (tree) => {
|
|
2092
|
-
|
|
2303
|
+
visit4(tree, "element", (node) => {
|
|
2304
|
+
if (node.tagName !== "details") {
|
|
2305
|
+
return;
|
|
2306
|
+
}
|
|
2307
|
+
const classNames = normalizeClassNames(node.properties.className);
|
|
2308
|
+
if (!classNames.includes(TOC_CLASS_NAME2)) {
|
|
2309
|
+
classnames2(node, DETAILS_CLASS_NAME2, FRAME_CLASS_NAME2);
|
|
2310
|
+
}
|
|
2311
|
+
});
|
|
2093
2312
|
};
|
|
2094
2313
|
}
|
|
2095
|
-
function
|
|
2096
|
-
if (
|
|
2097
|
-
return;
|
|
2314
|
+
function normalizeClassNames(className) {
|
|
2315
|
+
if (Array.isArray(className)) {
|
|
2316
|
+
return className.filter((item) => typeof item === "string");
|
|
2098
2317
|
}
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
const childNode = childNodes[childIndex];
|
|
2102
|
-
const imageFigureNode = createImageFigureNode(childNode);
|
|
2103
|
-
if (imageFigureNode) {
|
|
2104
|
-
childNodes[childIndex] = imageFigureNode;
|
|
2105
|
-
continue;
|
|
2106
|
-
}
|
|
2107
|
-
if (isImageNode(childNode)) {
|
|
2108
|
-
decorateImageNode(childNode);
|
|
2109
|
-
continue;
|
|
2110
|
-
}
|
|
2111
|
-
transformImageFigures(childNode);
|
|
2318
|
+
if (typeof className === "string") {
|
|
2319
|
+
return className.split(/\s+/u).filter(Boolean);
|
|
2112
2320
|
}
|
|
2321
|
+
return [];
|
|
2113
2322
|
}
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2323
|
+
|
|
2324
|
+
// src/syntax/frontmatter.ts
|
|
2325
|
+
import {
|
|
2326
|
+
FRAME_CLASS_NAME as FRAME_CLASS_NAME3,
|
|
2327
|
+
FRONTMATTER_CHROME_CLASS_NAME as FRONTMATTER_CHROME_CLASS_NAME2,
|
|
2328
|
+
FRONTMATTER_CLASS_NAME as FRONTMATTER_CLASS_NAME2,
|
|
2329
|
+
FRONTMATTER_LABEL_CLASS_NAME as FRONTMATTER_LABEL_CLASS_NAME2,
|
|
2330
|
+
FRONTMATTER_LIST_CLASS_NAME as FRONTMATTER_LIST_CLASS_NAME2,
|
|
2331
|
+
FRONTMATTER_LIST_NESTED_CLASS_NAME as FRONTMATTER_LIST_NESTED_CLASS_NAME2
|
|
2332
|
+
} from "@scribdown/shared";
|
|
2333
|
+
import { parse as parseYamlSource } from "yaml";
|
|
2334
|
+
var FRONTMATTER_LABEL_TEXT = "\u5143\u6570\u636E";
|
|
2335
|
+
var FRONTMATTER_ARRAY_SEPARATOR = ", ";
|
|
2336
|
+
var FRONTMATTER_FALLBACK_LANGUAGE_ID = "yaml";
|
|
2337
|
+
function remarkFrontmatterMetadata() {
|
|
2338
|
+
return (tree) => {
|
|
2339
|
+
const childNodes = tree.children;
|
|
2340
|
+
const yamlNode = childNodes[0];
|
|
2341
|
+
if (!yamlNode || yamlNode.type !== "yaml") {
|
|
2342
|
+
return;
|
|
2343
|
+
}
|
|
2344
|
+
childNodes[0] = createFrontmatterNode(yamlNode);
|
|
2345
|
+
};
|
|
2346
|
+
}
|
|
2347
|
+
function createFrontmatterNode(yamlNode) {
|
|
2348
|
+
const yamlSource = yamlNode.value;
|
|
2349
|
+
let parsedValue;
|
|
2350
|
+
try {
|
|
2351
|
+
parsedValue = parseYamlSource(yamlSource);
|
|
2352
|
+
} catch {
|
|
2353
|
+
parsedValue = void 0;
|
|
2117
2354
|
}
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2355
|
+
if (!isPlainRecord(parsedValue)) {
|
|
2356
|
+
return {
|
|
2357
|
+
type: "code",
|
|
2358
|
+
lang: FRONTMATTER_FALLBACK_LANGUAGE_ID,
|
|
2359
|
+
position: yamlNode.position,
|
|
2360
|
+
value: yamlSource
|
|
2361
|
+
};
|
|
2121
2362
|
}
|
|
2122
|
-
decorateImageNode(onlyChild);
|
|
2123
2363
|
return {
|
|
2124
|
-
type: "
|
|
2125
|
-
//
|
|
2126
|
-
position:
|
|
2364
|
+
type: "frontmatterMetadata",
|
|
2365
|
+
// 关键步骤:保留原节点源码位置,使 remarkSourceLine 能标注 data-source-line。
|
|
2366
|
+
position: yamlNode.position,
|
|
2127
2367
|
data: {
|
|
2128
|
-
hName: "
|
|
2129
|
-
hProperties: {
|
|
2130
|
-
className: [IMAGE_FIGURE_CLASS_NAME]
|
|
2131
|
-
}
|
|
2368
|
+
hName: "div",
|
|
2369
|
+
hProperties: { className: [FRONTMATTER_CLASS_NAME2, FRAME_CLASS_NAME3] }
|
|
2132
2370
|
},
|
|
2133
|
-
children: [
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2371
|
+
children: [
|
|
2372
|
+
{
|
|
2373
|
+
type: "frontmatterChrome",
|
|
2374
|
+
data: {
|
|
2375
|
+
hName: "div",
|
|
2376
|
+
hProperties: { className: [FRONTMATTER_CHROME_CLASS_NAME2] }
|
|
2377
|
+
},
|
|
2378
|
+
children: [
|
|
2379
|
+
{
|
|
2380
|
+
type: "frontmatterLabel",
|
|
2381
|
+
data: {
|
|
2382
|
+
hName: "span",
|
|
2383
|
+
hProperties: { className: [FRONTMATTER_LABEL_CLASS_NAME2] }
|
|
2384
|
+
},
|
|
2385
|
+
children: [{ type: "text", value: FRONTMATTER_LABEL_TEXT }]
|
|
2386
|
+
}
|
|
2387
|
+
]
|
|
2388
|
+
},
|
|
2389
|
+
createFrontmatterListNode(parsedValue, false)
|
|
2390
|
+
]
|
|
2143
2391
|
};
|
|
2144
2392
|
}
|
|
2145
|
-
function
|
|
2393
|
+
function isPlainRecord(value) {
|
|
2394
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2395
|
+
}
|
|
2396
|
+
function createFrontmatterListNode(record, nested) {
|
|
2397
|
+
const listClassNames = nested ? [FRONTMATTER_LIST_CLASS_NAME2, FRONTMATTER_LIST_NESTED_CLASS_NAME2] : [FRONTMATTER_LIST_CLASS_NAME2];
|
|
2398
|
+
const listChildren = [];
|
|
2399
|
+
Object.entries(record).forEach(([entryKey, entryValue]) => {
|
|
2400
|
+
listChildren.push({
|
|
2401
|
+
type: "frontmatterTerm",
|
|
2402
|
+
data: { hName: "dt" },
|
|
2403
|
+
children: [{ type: "text", value: entryKey }]
|
|
2404
|
+
});
|
|
2405
|
+
listChildren.push({
|
|
2406
|
+
type: "frontmatterDescription",
|
|
2407
|
+
data: { hName: "dd" },
|
|
2408
|
+
children: createFrontmatterValueNodes(entryValue)
|
|
2409
|
+
});
|
|
2410
|
+
});
|
|
2146
2411
|
return {
|
|
2147
|
-
type: "
|
|
2412
|
+
type: "frontmatterList",
|
|
2148
2413
|
data: {
|
|
2149
|
-
hName: "
|
|
2150
|
-
hProperties: {
|
|
2151
|
-
className: [IMAGE_FRAME_CLASS_NAME]
|
|
2152
|
-
}
|
|
2414
|
+
hName: "dl",
|
|
2415
|
+
hProperties: { className: listClassNames }
|
|
2153
2416
|
},
|
|
2154
|
-
children:
|
|
2417
|
+
children: listChildren
|
|
2155
2418
|
};
|
|
2156
2419
|
}
|
|
2157
|
-
function
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
return [];
|
|
2420
|
+
function createFrontmatterValueNodes(value) {
|
|
2421
|
+
if (isPlainRecord(value)) {
|
|
2422
|
+
return [createFrontmatterListNode(value, true)];
|
|
2161
2423
|
}
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
type: "imageCaption",
|
|
2165
|
-
data: {
|
|
2166
|
-
hName: "figcaption",
|
|
2167
|
-
hProperties: {
|
|
2168
|
-
className: [IMAGE_CAPTION_CLASS_NAME]
|
|
2169
|
-
}
|
|
2170
|
-
},
|
|
2171
|
-
children: [{ type: "text", value: imageTitle }]
|
|
2172
|
-
}
|
|
2173
|
-
];
|
|
2174
|
-
}
|
|
2175
|
-
function createImageFallbackNode(imageNode) {
|
|
2176
|
-
const fallbackText = imageNode.alt?.trim() || "\u56FE\u7247\u52A0\u8F7D\u5931\u8D25";
|
|
2177
|
-
const fallbackSource = imageNode.url ?? imageNode.identifier ?? "";
|
|
2178
|
-
return {
|
|
2179
|
-
type: "imageFallback",
|
|
2180
|
-
data: {
|
|
2181
|
-
hName: "span",
|
|
2182
|
-
hProperties: {
|
|
2183
|
-
className: [IMAGE_FALLBACK_CLASS_NAME]
|
|
2184
|
-
}
|
|
2185
|
-
},
|
|
2186
|
-
children: [
|
|
2187
|
-
{
|
|
2188
|
-
type: "imageFallbackIcon",
|
|
2189
|
-
data: {
|
|
2190
|
-
hName: "span",
|
|
2191
|
-
hProperties: {
|
|
2192
|
-
className: [IMAGE_FALLBACK_ICON_CLASS_NAME]
|
|
2193
|
-
}
|
|
2194
|
-
}
|
|
2195
|
-
},
|
|
2196
|
-
{
|
|
2197
|
-
type: "imageFallbackText",
|
|
2198
|
-
data: {
|
|
2199
|
-
hName: "span",
|
|
2200
|
-
hProperties: {
|
|
2201
|
-
className: [IMAGE_FALLBACK_TEXT_CLASS_NAME]
|
|
2202
|
-
}
|
|
2203
|
-
},
|
|
2204
|
-
children: [{ type: "text", value: fallbackText }]
|
|
2205
|
-
},
|
|
2424
|
+
if (Array.isArray(value)) {
|
|
2425
|
+
return [
|
|
2206
2426
|
{
|
|
2207
|
-
type: "
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2427
|
+
type: "text",
|
|
2428
|
+
value: value.map(formatFrontmatterScalar).join(FRONTMATTER_ARRAY_SEPARATOR)
|
|
2429
|
+
}
|
|
2430
|
+
];
|
|
2431
|
+
}
|
|
2432
|
+
return [{ type: "text", value: formatFrontmatterScalar(value) }];
|
|
2433
|
+
}
|
|
2434
|
+
function formatFrontmatterScalar(value) {
|
|
2435
|
+
if (value === null || value === void 0) {
|
|
2436
|
+
return "";
|
|
2437
|
+
}
|
|
2438
|
+
if (typeof value === "object") {
|
|
2439
|
+
return JSON.stringify(value);
|
|
2440
|
+
}
|
|
2441
|
+
return String(value);
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2444
|
+
// src/syntax/highlight-mark.ts
|
|
2445
|
+
import { SKIP as SKIP3, visit as visit5 } from "unist-util-visit";
|
|
2446
|
+
var HIGHLIGHT_MARKER_PATTERN = /==([^=\n]+?)==/g;
|
|
2447
|
+
function remarkHighlightMark() {
|
|
2448
|
+
return (tree) => {
|
|
2449
|
+
visit5(tree, "text", (node, index, parent) => {
|
|
2450
|
+
if (parent === void 0 || index === void 0 || !node.value.includes("==")) {
|
|
2451
|
+
return;
|
|
2215
2452
|
}
|
|
2216
|
-
|
|
2453
|
+
const segments = splitHighlightMarks(node.value);
|
|
2454
|
+
parent.children.splice(index, 1, ...segments);
|
|
2455
|
+
return [SKIP3, index + segments.length];
|
|
2456
|
+
});
|
|
2217
2457
|
};
|
|
2218
2458
|
}
|
|
2219
|
-
function
|
|
2220
|
-
|
|
2459
|
+
function splitHighlightMarks(textValue) {
|
|
2460
|
+
const segments = [];
|
|
2461
|
+
let cursor = 0;
|
|
2462
|
+
HIGHLIGHT_MARKER_PATTERN.lastIndex = 0;
|
|
2463
|
+
let match = HIGHLIGHT_MARKER_PATTERN.exec(textValue);
|
|
2464
|
+
while (match !== null) {
|
|
2465
|
+
const leadingText = textValue.slice(cursor, match.index);
|
|
2466
|
+
if (leadingText.length > 0) {
|
|
2467
|
+
segments.push({ type: "text", value: leadingText });
|
|
2468
|
+
}
|
|
2469
|
+
segments.push({
|
|
2470
|
+
type: "highlightMark",
|
|
2471
|
+
data: {
|
|
2472
|
+
hName: "mark"
|
|
2473
|
+
},
|
|
2474
|
+
children: [{ type: "text", value: match[1] }]
|
|
2475
|
+
});
|
|
2476
|
+
cursor = match.index + match[0].length;
|
|
2477
|
+
match = HIGHLIGHT_MARKER_PATTERN.exec(textValue);
|
|
2478
|
+
}
|
|
2479
|
+
const trailingText = textValue.slice(cursor);
|
|
2480
|
+
if (trailingText.length > 0) {
|
|
2481
|
+
segments.push({ type: "text", value: trailingText });
|
|
2482
|
+
}
|
|
2483
|
+
return segments;
|
|
2221
2484
|
}
|
|
2485
|
+
|
|
2486
|
+
// src/syntax/toc.ts
|
|
2487
|
+
import {
|
|
2488
|
+
FRAME_CLASS_NAME as FRAME_CLASS_NAME4,
|
|
2489
|
+
HEADING_MARK_CLASS_NAME as HEADING_MARK_CLASS_NAME2,
|
|
2490
|
+
SCRIBDOWN_MARKDOWN_CLASS_NAME,
|
|
2491
|
+
TOC_CLASS_NAME as TOC_CLASS_NAME3,
|
|
2492
|
+
TOC_ITEM_BRANCH_CLASS_NAME,
|
|
2493
|
+
TOC_ITEM_CLASS_PREFIX as TOC_ITEM_CLASS_PREFIX2,
|
|
2494
|
+
TOC_ITEM_COLLAPSED_CLASS_NAME,
|
|
2495
|
+
TOC_LINK_CLASS_NAME as TOC_LINK_CLASS_NAME2,
|
|
2496
|
+
TOC_LIST_CLASS_NAME as TOC_LIST_CLASS_NAME2,
|
|
2497
|
+
TOC_LIST_NESTED_CLASS_NAME as TOC_LIST_NESTED_CLASS_NAME2,
|
|
2498
|
+
TOC_NAV_CLASS_NAME as TOC_NAV_CLASS_NAME2,
|
|
2499
|
+
TOC_SUMMARY_CLASS_NAME as TOC_SUMMARY_CLASS_NAME2,
|
|
2500
|
+
TOC_TOGGLE_CLASS_NAME as TOC_TOGGLE_CLASS_NAME2
|
|
2501
|
+
} from "@scribdown/shared";
|
|
2502
|
+
import GithubSlugger from "github-slugger";
|
|
2503
|
+
import { visit as visit6 } from "unist-util-visit";
|
|
2504
|
+
var TOC_MARKER_PATTERN = /^\s*\[toc]\s*$/i;
|
|
2505
|
+
var TOC_TOGGLE_ARIA_LABEL = "\u5C55\u5F00\u6216\u6298\u53E0\u5B50\u76EE\u5F55";
|
|
2506
|
+
var TOC_TOGGLE_HYDRATED_DATA_KEY = "scribdownTocToggleHydrated";
|
|
2507
|
+
var TOC_LINK_HYDRATED_DATA_KEY = "scribdownTocLinkHydrated";
|
|
2508
|
+
var TOC_ARIA_LABEL = "\u76EE\u5F55";
|
|
2509
|
+
var TOC_SUMMARY_TEXT = "\u76EE\u5F55";
|
|
2510
|
+
var EMPTY_HEADING_SLUG_PREFIX = "section";
|
|
2222
2511
|
function remarkTableOfContents() {
|
|
2223
2512
|
return (tree) => {
|
|
2224
2513
|
const tocHeadings = collectTocHeadings(tree);
|
|
@@ -2226,17 +2515,14 @@ function remarkTableOfContents() {
|
|
|
2226
2515
|
};
|
|
2227
2516
|
}
|
|
2228
2517
|
function collectTocHeadings(tree) {
|
|
2229
|
-
const
|
|
2518
|
+
const headingSlugger = new GithubSlugger();
|
|
2230
2519
|
const tocHeadings = [];
|
|
2231
2520
|
const headingIndexCounts = Array.from({ length: 7 }, () => 0);
|
|
2232
2521
|
let rootHeadingDepth;
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
return;
|
|
2236
|
-
}
|
|
2237
|
-
const headingDepth = node.depth ?? 2;
|
|
2522
|
+
visit6(tree, "heading", (node) => {
|
|
2523
|
+
const headingDepth = node.depth;
|
|
2238
2524
|
const headingText = extractNodeText(node).trim();
|
|
2239
|
-
const headingId =
|
|
2525
|
+
const headingId = createHeadingId(headingSlugger, headingText, tocHeadings.length + 1);
|
|
2240
2526
|
const headingIndex = createHeadingIndex(
|
|
2241
2527
|
headingDepth,
|
|
2242
2528
|
headingIndexCounts,
|
|
@@ -2256,10 +2542,10 @@ function collectTocHeadings(tree) {
|
|
|
2256
2542
|
data: {
|
|
2257
2543
|
hName: "span",
|
|
2258
2544
|
hProperties: {
|
|
2259
|
-
className: [
|
|
2545
|
+
className: [HEADING_MARK_CLASS_NAME2]
|
|
2260
2546
|
}
|
|
2261
2547
|
},
|
|
2262
|
-
children: node.children
|
|
2548
|
+
children: node.children
|
|
2263
2549
|
}
|
|
2264
2550
|
];
|
|
2265
2551
|
tocHeadings.push({
|
|
@@ -2271,36 +2557,20 @@ function collectTocHeadings(tree) {
|
|
|
2271
2557
|
});
|
|
2272
2558
|
return tocHeadings;
|
|
2273
2559
|
}
|
|
2274
|
-
function
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
}
|
|
2279
|
-
const childNodes = node.children;
|
|
2280
|
-
for (let childIndex = 0; childIndex < childNodes.length; childIndex += 1) {
|
|
2281
|
-
visitMarkdownNode(childNodes[childIndex], visitor);
|
|
2282
|
-
}
|
|
2283
|
-
}
|
|
2284
|
-
function replaceTocMarkers(node, tocHeadings) {
|
|
2285
|
-
if (!node.children) {
|
|
2286
|
-
return;
|
|
2287
|
-
}
|
|
2288
|
-
const childNodes = node.children;
|
|
2289
|
-
for (let childIndex = 0; childIndex < childNodes.length; childIndex += 1) {
|
|
2290
|
-
const childNode = childNodes[childIndex];
|
|
2291
|
-
if (isTocMarkerParagraph(childNode)) {
|
|
2292
|
-
childNodes[childIndex] = createTocNode(tocHeadings, childNode);
|
|
2293
|
-
continue;
|
|
2560
|
+
function replaceTocMarkers(tree, tocHeadings) {
|
|
2561
|
+
visit6(tree, "paragraph", (node, index, parent) => {
|
|
2562
|
+
if (parent === void 0 || index === void 0 || !isTocMarkerParagraph(node)) {
|
|
2563
|
+
return;
|
|
2294
2564
|
}
|
|
2295
|
-
|
|
2296
|
-
}
|
|
2565
|
+
parent.children[index] = createTocNode(tocHeadings, node);
|
|
2566
|
+
});
|
|
2297
2567
|
}
|
|
2298
2568
|
function isTocMarkerParagraph(node) {
|
|
2299
|
-
if (node.
|
|
2569
|
+
if (node.children.length !== 1) {
|
|
2300
2570
|
return false;
|
|
2301
2571
|
}
|
|
2302
2572
|
const onlyChild = node.children[0];
|
|
2303
|
-
return onlyChild.type === "text" && TOC_MARKER_PATTERN.test(onlyChild.value
|
|
2573
|
+
return onlyChild.type === "text" && TOC_MARKER_PATTERN.test(onlyChild.value);
|
|
2304
2574
|
}
|
|
2305
2575
|
function createTocNode(tocHeadings, markerNode) {
|
|
2306
2576
|
const tocTree = createTocTree(tocHeadings);
|
|
@@ -2312,7 +2582,7 @@ function createTocNode(tocHeadings, markerNode) {
|
|
|
2312
2582
|
hName: "details",
|
|
2313
2583
|
hProperties: {
|
|
2314
2584
|
// 关键步骤:目录根 opt-in 手绘边框,再由 toc.css 叠加目录自身样式。
|
|
2315
|
-
className: [
|
|
2585
|
+
className: [TOC_CLASS_NAME3, FRAME_CLASS_NAME4]
|
|
2316
2586
|
}
|
|
2317
2587
|
},
|
|
2318
2588
|
children: [
|
|
@@ -2321,7 +2591,7 @@ function createTocNode(tocHeadings, markerNode) {
|
|
|
2321
2591
|
data: {
|
|
2322
2592
|
hName: "summary",
|
|
2323
2593
|
hProperties: {
|
|
2324
|
-
className: [
|
|
2594
|
+
className: [TOC_SUMMARY_CLASS_NAME2]
|
|
2325
2595
|
}
|
|
2326
2596
|
},
|
|
2327
2597
|
children: [
|
|
@@ -2337,7 +2607,7 @@ function createTocNode(tocHeadings, markerNode) {
|
|
|
2337
2607
|
hName: "nav",
|
|
2338
2608
|
hProperties: {
|
|
2339
2609
|
ariaLabel: TOC_ARIA_LABEL,
|
|
2340
|
-
className: [
|
|
2610
|
+
className: [TOC_NAV_CLASS_NAME2]
|
|
2341
2611
|
}
|
|
2342
2612
|
},
|
|
2343
2613
|
children: [createTocListNode(tocTree, false)]
|
|
@@ -2367,12 +2637,11 @@ function createTocTree(tocHeadings) {
|
|
|
2367
2637
|
return rootItems;
|
|
2368
2638
|
}
|
|
2369
2639
|
function createTocListNode(tocItems, isNested) {
|
|
2370
|
-
const tocListClassNames = isNested ? [
|
|
2640
|
+
const tocListClassNames = isNested ? [TOC_LIST_CLASS_NAME2, TOC_LIST_NESTED_CLASS_NAME2] : [TOC_LIST_CLASS_NAME2];
|
|
2371
2641
|
return {
|
|
2372
|
-
type: "
|
|
2373
|
-
ordered: true,
|
|
2374
|
-
spread: false,
|
|
2642
|
+
type: "tocList",
|
|
2375
2643
|
data: {
|
|
2644
|
+
hName: "ol",
|
|
2376
2645
|
hProperties: {
|
|
2377
2646
|
className: tocListClassNames
|
|
2378
2647
|
}
|
|
@@ -2385,9 +2654,9 @@ function createTocListItem(tocItem) {
|
|
|
2385
2654
|
const tocItemClassNames = createTocItemClassNames(tocItem.depth, hasChildren);
|
|
2386
2655
|
const itemChildren = hasChildren ? [createTocToggleNode(), createTocLinkNode(tocItem), createTocListNode(tocItem.children, true)] : [createTocLinkNode(tocItem)];
|
|
2387
2656
|
return {
|
|
2388
|
-
type: "
|
|
2389
|
-
spread: false,
|
|
2657
|
+
type: "tocItem",
|
|
2390
2658
|
data: {
|
|
2659
|
+
hName: "li",
|
|
2391
2660
|
hProperties: {
|
|
2392
2661
|
dataTocIndex: tocItem.index,
|
|
2393
2662
|
className: tocItemClassNames
|
|
@@ -2398,8 +2667,8 @@ function createTocListItem(tocItem) {
|
|
|
2398
2667
|
}
|
|
2399
2668
|
function createTocItemClassNames(depth, hasChildren) {
|
|
2400
2669
|
return [
|
|
2401
|
-
|
|
2402
|
-
`${
|
|
2670
|
+
TOC_ITEM_CLASS_PREFIX2,
|
|
2671
|
+
`${TOC_ITEM_CLASS_PREFIX2}--depth-${depth}`,
|
|
2403
2672
|
...hasChildren ? [TOC_ITEM_BRANCH_CLASS_NAME] : []
|
|
2404
2673
|
];
|
|
2405
2674
|
}
|
|
@@ -2410,7 +2679,7 @@ function createTocToggleNode() {
|
|
|
2410
2679
|
hName: "button",
|
|
2411
2680
|
hProperties: {
|
|
2412
2681
|
type: "button",
|
|
2413
|
-
className: [
|
|
2682
|
+
className: [TOC_TOGGLE_CLASS_NAME2],
|
|
2414
2683
|
ariaExpanded: "true",
|
|
2415
2684
|
ariaLabel: TOC_TOGGLE_ARIA_LABEL
|
|
2416
2685
|
}
|
|
@@ -2424,7 +2693,7 @@ function createTocLinkNode(tocItem) {
|
|
|
2424
2693
|
url: `#${tocItem.id}`,
|
|
2425
2694
|
data: {
|
|
2426
2695
|
hProperties: {
|
|
2427
|
-
className: [
|
|
2696
|
+
className: [TOC_LINK_CLASS_NAME2]
|
|
2428
2697
|
}
|
|
2429
2698
|
},
|
|
2430
2699
|
children: [
|
|
@@ -2435,6 +2704,26 @@ function createTocLinkNode(tocItem) {
|
|
|
2435
2704
|
]
|
|
2436
2705
|
};
|
|
2437
2706
|
}
|
|
2707
|
+
function createHeadingId(headingSlugger, headingText, fallbackIndex) {
|
|
2708
|
+
const baseSlug = headingSlugger.slug(headingText);
|
|
2709
|
+
if (baseSlug) {
|
|
2710
|
+
return baseSlug;
|
|
2711
|
+
}
|
|
2712
|
+
return headingSlugger.slug(`${EMPTY_HEADING_SLUG_PREFIX}-${fallbackIndex}`);
|
|
2713
|
+
}
|
|
2714
|
+
function createHeadingIndex(headingDepth, headingIndexCounts, rootHeadingDepth) {
|
|
2715
|
+
const startDepth = Math.min(rootHeadingDepth, headingDepth);
|
|
2716
|
+
headingIndexCounts[headingDepth] += 1;
|
|
2717
|
+
for (let depthIndex = headingDepth + 1; depthIndex < headingIndexCounts.length; depthIndex += 1) {
|
|
2718
|
+
headingIndexCounts[depthIndex] = 0;
|
|
2719
|
+
}
|
|
2720
|
+
for (let depthIndex = startDepth; depthIndex < headingDepth; depthIndex += 1) {
|
|
2721
|
+
if (headingIndexCounts[depthIndex] === 0) {
|
|
2722
|
+
headingIndexCounts[depthIndex] = 1;
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
return headingIndexCounts.slice(startDepth, headingDepth + 1).join(".");
|
|
2726
|
+
}
|
|
2438
2727
|
function collectTocHeadingsFromDom(rootElement) {
|
|
2439
2728
|
const markdownContainer = rootElement instanceof Element && rootElement.matches(`.${SCRIBDOWN_MARKDOWN_CLASS_NAME}`) ? rootElement : rootElement.querySelector(`.${SCRIBDOWN_MARKDOWN_CLASS_NAME}`);
|
|
2440
2729
|
if (!markdownContainer) {
|
|
@@ -2473,13 +2762,13 @@ function collectTocHeadingsFromDom(rootElement) {
|
|
|
2473
2762
|
function createTocNavElement(ownerDocument, tocTreeItems) {
|
|
2474
2763
|
const navElement = ownerDocument.createElement("nav");
|
|
2475
2764
|
navElement.setAttribute("aria-label", TOC_ARIA_LABEL);
|
|
2476
|
-
navElement.className =
|
|
2765
|
+
navElement.className = TOC_NAV_CLASS_NAME2;
|
|
2477
2766
|
navElement.appendChild(createTocListElement(ownerDocument, tocTreeItems, false));
|
|
2478
2767
|
return navElement;
|
|
2479
2768
|
}
|
|
2480
2769
|
function createTocListElement(ownerDocument, tocItems, isNested) {
|
|
2481
2770
|
const listElement = ownerDocument.createElement("ol");
|
|
2482
|
-
listElement.className = isNested ? `${
|
|
2771
|
+
listElement.className = isNested ? `${TOC_LIST_CLASS_NAME2} ${TOC_LIST_NESTED_CLASS_NAME2}` : TOC_LIST_CLASS_NAME2;
|
|
2483
2772
|
for (const tocItem of tocItems) {
|
|
2484
2773
|
listElement.appendChild(createTocListItemElement(ownerDocument, tocItem));
|
|
2485
2774
|
}
|
|
@@ -2493,13 +2782,13 @@ function createTocListItemElement(ownerDocument, tocItem) {
|
|
|
2493
2782
|
if (hasChildren) {
|
|
2494
2783
|
const toggleElement = ownerDocument.createElement("button");
|
|
2495
2784
|
toggleElement.type = "button";
|
|
2496
|
-
toggleElement.className =
|
|
2785
|
+
toggleElement.className = TOC_TOGGLE_CLASS_NAME2;
|
|
2497
2786
|
toggleElement.setAttribute("aria-expanded", "true");
|
|
2498
2787
|
toggleElement.setAttribute("aria-label", TOC_TOGGLE_ARIA_LABEL);
|
|
2499
2788
|
itemElement.appendChild(toggleElement);
|
|
2500
2789
|
}
|
|
2501
2790
|
const linkElement = ownerDocument.createElement("a");
|
|
2502
|
-
linkElement.className =
|
|
2791
|
+
linkElement.className = TOC_LINK_CLASS_NAME2;
|
|
2503
2792
|
linkElement.href = `#${tocItem.id}`;
|
|
2504
2793
|
linkElement.textContent = tocItem.text;
|
|
2505
2794
|
itemElement.appendChild(linkElement);
|
|
@@ -2510,7 +2799,7 @@ function createTocListItemElement(ownerDocument, tocItem) {
|
|
|
2510
2799
|
}
|
|
2511
2800
|
function hydrateToc(rootElement, scrollToHeading) {
|
|
2512
2801
|
const toggleElements = rootElement.querySelectorAll(
|
|
2513
|
-
`button.${
|
|
2802
|
+
`button.${TOC_TOGGLE_CLASS_NAME2}`
|
|
2514
2803
|
);
|
|
2515
2804
|
toggleElements.forEach((toggleElement) => {
|
|
2516
2805
|
if (toggleElement.dataset[TOC_TOGGLE_HYDRATED_DATA_KEY] === "true") {
|
|
@@ -2518,7 +2807,7 @@ function hydrateToc(rootElement, scrollToHeading) {
|
|
|
2518
2807
|
}
|
|
2519
2808
|
toggleElement.dataset[TOC_TOGGLE_HYDRATED_DATA_KEY] = "true";
|
|
2520
2809
|
toggleElement.addEventListener("click", () => {
|
|
2521
|
-
const itemElement = toggleElement.closest(`.${
|
|
2810
|
+
const itemElement = toggleElement.closest(`.${TOC_ITEM_CLASS_PREFIX2}`);
|
|
2522
2811
|
if (!itemElement) {
|
|
2523
2812
|
return;
|
|
2524
2813
|
}
|
|
@@ -2528,7 +2817,7 @@ function hydrateToc(rootElement, scrollToHeading) {
|
|
|
2528
2817
|
});
|
|
2529
2818
|
});
|
|
2530
2819
|
const linkElements = rootElement.querySelectorAll(
|
|
2531
|
-
`a.${
|
|
2820
|
+
`a.${TOC_LINK_CLASS_NAME2}`
|
|
2532
2821
|
);
|
|
2533
2822
|
linkElements.forEach((linkElement) => {
|
|
2534
2823
|
if (linkElement.dataset[TOC_LINK_HYDRATED_DATA_KEY] === "true") {
|
|
@@ -2556,192 +2845,37 @@ function hydrateToc(rootElement, scrollToHeading) {
|
|
|
2556
2845
|
});
|
|
2557
2846
|
});
|
|
2558
2847
|
}
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
headingIndexCounts[depthIndex] = 1;
|
|
2591
|
-
}
|
|
2592
|
-
}
|
|
2593
|
-
return headingIndexCounts.slice(startDepth, headingDepth + 1).join(".");
|
|
2594
|
-
}
|
|
2595
|
-
function normalizeSlugText(headingText) {
|
|
2596
|
-
return headingText.trim().toLowerCase().replace(/<[^>]+>/g, "").replace(/[^\p{L}\p{N}\s_-]+/gu, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
2597
|
-
}
|
|
2598
|
-
function rehypeFrameClass() {
|
|
2599
|
-
return (tree) => {
|
|
2600
|
-
tagContentDetails(tree);
|
|
2601
|
-
};
|
|
2602
|
-
}
|
|
2603
|
-
function tagContentDetails(node) {
|
|
2604
|
-
if (node.type === "element" && node.tagName === "details") {
|
|
2605
|
-
const classNames = normalizeClassNames(node.properties?.className);
|
|
2606
|
-
const isTocDetails = classNames.includes(TOC_CLASS_NAME);
|
|
2607
|
-
if (!isTocDetails) {
|
|
2608
|
-
addClassNames(node, [DETAILS_CLASS_NAME, FRAME_CLASS_NAME]);
|
|
2609
|
-
}
|
|
2610
|
-
}
|
|
2611
|
-
if (Array.isArray(node.children)) {
|
|
2612
|
-
for (const childNode of node.children) {
|
|
2613
|
-
tagContentDetails(childNode);
|
|
2614
|
-
}
|
|
2615
|
-
}
|
|
2616
|
-
}
|
|
2617
|
-
function normalizeClassNames(className) {
|
|
2618
|
-
if (Array.isArray(className)) {
|
|
2619
|
-
return className.filter((item) => typeof item === "string");
|
|
2620
|
-
}
|
|
2621
|
-
if (typeof className === "string") {
|
|
2622
|
-
return className.split(/\s+/u).filter(Boolean);
|
|
2623
|
-
}
|
|
2624
|
-
return [];
|
|
2625
|
-
}
|
|
2626
|
-
function addClassNames(node, classNamesToAdd) {
|
|
2627
|
-
const mergedClassNames = normalizeClassNames(node.properties?.className);
|
|
2628
|
-
for (const classNameToAdd of classNamesToAdd) {
|
|
2629
|
-
if (!mergedClassNames.includes(classNameToAdd)) {
|
|
2630
|
-
mergedClassNames.push(classNameToAdd);
|
|
2631
|
-
}
|
|
2632
|
-
}
|
|
2633
|
-
node.properties = { ...node.properties, className: mergedClassNames };
|
|
2634
|
-
}
|
|
2635
|
-
function rehypeVideoFigures() {
|
|
2636
|
-
return (tree) => {
|
|
2637
|
-
transformVideoFigures(tree);
|
|
2638
|
-
};
|
|
2639
|
-
}
|
|
2640
|
-
function transformVideoFigures(node) {
|
|
2641
|
-
if (!Array.isArray(node.children)) {
|
|
2642
|
-
return;
|
|
2643
|
-
}
|
|
2644
|
-
const childNodes = node.children;
|
|
2645
|
-
for (let childIndex = 0; childIndex < childNodes.length; childIndex += 1) {
|
|
2646
|
-
const childNode = childNodes[childIndex];
|
|
2647
|
-
if (isHastVideoElement(childNode)) {
|
|
2648
|
-
childNodes[childIndex] = createVideoFigureHast(childNode);
|
|
2649
|
-
continue;
|
|
2650
|
-
}
|
|
2651
|
-
const standaloneVideo = extractStandaloneParagraphVideo(childNode);
|
|
2652
|
-
if (standaloneVideo) {
|
|
2653
|
-
childNodes[childIndex] = createVideoFigureHast(standaloneVideo);
|
|
2654
|
-
continue;
|
|
2655
|
-
}
|
|
2656
|
-
transformVideoFigures(childNode);
|
|
2657
|
-
}
|
|
2658
|
-
}
|
|
2659
|
-
function isHastVideoElement(node) {
|
|
2660
|
-
return node.type === "element" && node.tagName === "video";
|
|
2661
|
-
}
|
|
2662
|
-
function extractStandaloneParagraphVideo(node) {
|
|
2663
|
-
if (node.type !== "element" || node.tagName !== "p" || !Array.isArray(node.children)) {
|
|
2664
|
-
return void 0;
|
|
2665
|
-
}
|
|
2666
|
-
const significantChildren = node.children.filter((childNode) => {
|
|
2667
|
-
if (childNode.type === "text" && typeof childNode.value === "string") {
|
|
2668
|
-
return childNode.value.trim().length > 0;
|
|
2669
|
-
}
|
|
2670
|
-
return true;
|
|
2671
|
-
});
|
|
2672
|
-
if (significantChildren.length === 1 && isHastVideoElement(significantChildren[0])) {
|
|
2673
|
-
return significantChildren[0];
|
|
2674
|
-
}
|
|
2675
|
-
return void 0;
|
|
2676
|
-
}
|
|
2677
|
-
function createVideoFigureHast(videoNode) {
|
|
2678
|
-
decorateHastVideoElement(videoNode);
|
|
2679
|
-
const sourceUrl = readHastVideoSourceUrl(videoNode);
|
|
2680
|
-
return {
|
|
2681
|
-
type: "element",
|
|
2682
|
-
tagName: "figure",
|
|
2683
|
-
properties: { className: [VIDEO_FIGURE_CLASS_NAME] },
|
|
2684
|
-
children: [
|
|
2685
|
-
{
|
|
2686
|
-
type: "element",
|
|
2687
|
-
tagName: "span",
|
|
2688
|
-
properties: { className: [VIDEO_FRAME_CLASS_NAME] },
|
|
2689
|
-
children: [videoNode, createVideoFallbackHast(sourceUrl)]
|
|
2690
|
-
}
|
|
2691
|
-
]
|
|
2692
|
-
};
|
|
2693
|
-
}
|
|
2694
|
-
function decorateHastVideoElement(videoNode) {
|
|
2695
|
-
const properties = videoNode.properties ?? {};
|
|
2696
|
-
const existingClassName = properties.className;
|
|
2697
|
-
const classList = Array.isArray(existingClassName) ? existingClassName.map(String) : typeof existingClassName === "string" ? [existingClassName] : [];
|
|
2698
|
-
if (!classList.includes(VIDEO_ELEMENT_CLASS_NAME)) {
|
|
2699
|
-
classList.push(VIDEO_ELEMENT_CLASS_NAME);
|
|
2700
|
-
}
|
|
2701
|
-
properties.className = classList;
|
|
2702
|
-
videoNode.properties = properties;
|
|
2703
|
-
}
|
|
2704
|
-
function readHastVideoSourceUrl(videoNode) {
|
|
2705
|
-
const properties = videoNode.properties ?? {};
|
|
2706
|
-
if (typeof properties.src === "string") {
|
|
2707
|
-
return properties.src;
|
|
2708
|
-
}
|
|
2709
|
-
if (Array.isArray(videoNode.children)) {
|
|
2710
|
-
for (const childNode of videoNode.children) {
|
|
2711
|
-
if (childNode.type === "element" && childNode.tagName === "source" && typeof childNode.properties?.src === "string") {
|
|
2712
|
-
return childNode.properties.src;
|
|
2713
|
-
}
|
|
2714
|
-
}
|
|
2715
|
-
}
|
|
2716
|
-
return "";
|
|
2717
|
-
}
|
|
2718
|
-
function createVideoFallbackHast(sourceUrl) {
|
|
2719
|
-
return {
|
|
2720
|
-
type: "element",
|
|
2721
|
-
tagName: "span",
|
|
2722
|
-
properties: { className: [VIDEO_FALLBACK_CLASS_NAME] },
|
|
2723
|
-
children: [
|
|
2724
|
-
{
|
|
2725
|
-
type: "element",
|
|
2726
|
-
tagName: "span",
|
|
2727
|
-
properties: { className: [VIDEO_FALLBACK_ICON_CLASS_NAME] },
|
|
2728
|
-
children: []
|
|
2729
|
-
},
|
|
2730
|
-
{
|
|
2731
|
-
type: "element",
|
|
2732
|
-
tagName: "span",
|
|
2733
|
-
properties: { className: [VIDEO_FALLBACK_TEXT_CLASS_NAME] },
|
|
2734
|
-
children: [{ type: "text", value: VIDEO_FALLBACK_DEFAULT_TEXT }]
|
|
2735
|
-
},
|
|
2736
|
-
{
|
|
2737
|
-
type: "element",
|
|
2738
|
-
tagName: "span",
|
|
2739
|
-
properties: { className: [VIDEO_FALLBACK_SOURCE_CLASS_NAME] },
|
|
2740
|
-
children: [{ type: "text", value: sourceUrl }]
|
|
2741
|
-
}
|
|
2742
|
-
]
|
|
2743
|
-
};
|
|
2744
|
-
}
|
|
2848
|
+
|
|
2849
|
+
// src/toolbar.ts
|
|
2850
|
+
import {
|
|
2851
|
+
CONTENT_WIDTH_STORAGE_KEY,
|
|
2852
|
+
PROJECT_HOMEPAGE_URL,
|
|
2853
|
+
SCRIBDOWN_CONTENT_AREA_CLASS_NAME,
|
|
2854
|
+
SCRIBDOWN_CONTENT_SCROLL_CLASS_NAME,
|
|
2855
|
+
SCRIBDOWN_MARKDOWN_CLASS_NAME as SCRIBDOWN_MARKDOWN_CLASS_NAME2,
|
|
2856
|
+
SCRIBDOWN_THIN_SCROLLBAR_CLASS_NAME,
|
|
2857
|
+
SCRIBDOWN_TOC_HOST_CLASS_NAME,
|
|
2858
|
+
SCRIBDOWN_TOOLBAR_BTN_CLASS_NAME,
|
|
2859
|
+
SCRIBDOWN_TOOLBAR_CLASS_NAME,
|
|
2860
|
+
SCRIBDOWN_TOOLBAR_CURRENT_CLASS_NAME,
|
|
2861
|
+
SCRIBDOWN_TOOLBAR_CURRENT_TEXT_CLASS_NAME,
|
|
2862
|
+
SCRIBDOWN_TOOLBAR_MENU_CLASS_NAME,
|
|
2863
|
+
SCRIBDOWN_TOOLBAR_MENU_GROUP_CHOICES_CLASS_NAME,
|
|
2864
|
+
SCRIBDOWN_TOOLBAR_MENU_GROUP_CLASS_NAME,
|
|
2865
|
+
SCRIBDOWN_TOOLBAR_MENU_ITEM_CLASS_NAME,
|
|
2866
|
+
SCRIBDOWN_TOOLBAR_MENU_SELECT_CHEVRON_CLASS_NAME,
|
|
2867
|
+
SCRIBDOWN_TOOLBAR_MENU_SELECT_LABEL_CLASS_NAME,
|
|
2868
|
+
SCRIBDOWN_TOOLBAR_MENU_SELECT_VALUE_CLASS_NAME,
|
|
2869
|
+
SCRIBDOWN_TOOLBAR_MENU_SUB_ITEM_CLASS_NAME,
|
|
2870
|
+
SCRIBDOWN_TOOLBAR_MENU_SUB_ITEM_LABEL_CLASS_NAME,
|
|
2871
|
+
SCRIBDOWN_TOOLBAR_MENU_SUB_ITEM_MARK_CLASS_NAME,
|
|
2872
|
+
SCRIBDOWN_TOOLBAR_TOC_PANEL_CLASS_NAME,
|
|
2873
|
+
SCRIBDOWN_TOOLBAR_TOC_PANEL_CLOSE_CLASS_NAME,
|
|
2874
|
+
SCRIBDOWN_TOOLBAR_TOC_PANEL_EMPTY_CLASS_NAME,
|
|
2875
|
+
SCRIBDOWN_TOOLBAR_TOC_PANEL_TITLE_CLASS_NAME,
|
|
2876
|
+
TOC_LINK_ACTIVE_CLASS_NAME,
|
|
2877
|
+
TOC_LINK_CLASS_NAME as TOC_LINK_CLASS_NAME3
|
|
2878
|
+
} from "@scribdown/shared";
|
|
2745
2879
|
var TOOLBAR_WIDTH_PRESETS = [
|
|
2746
2880
|
{ label: "680", value: "680px" },
|
|
2747
2881
|
{ label: "840", value: "840px" },
|
|
@@ -2778,10 +2912,11 @@ var TOOLBAR_MORE_ICON_SVG = '<svg width="16" height="16" viewBox="0 0 18 18" fil
|
|
|
2778
2912
|
var TOOLBAR_BACK_TOP_ICON_SVG = '<svg width="16" height="16" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M9 14V5M9 5L5 9M9 5l4 4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/><line x1="4" y1="3.5" x2="14" y2="3.5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>';
|
|
2779
2913
|
var TOOLBAR_WIDTH_ICON_SVG = '<svg width="16" height="16" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M3 6v6M15 6v6" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/><path d="M5.5 9H3l2.5-2.5M3 9l2.5 2.5M12.5 9H15l-2.5-2.5M15 9l-2.5 2.5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>';
|
|
2780
2914
|
var TOOLBAR_ABOUT_ICON_SVG = '<svg width="16" height="16" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><circle cx="9" cy="9" r="6.5" stroke="currentColor" stroke-width="1.6"/><path d="M9 8v4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/><circle cx="9" cy="5.6" r="0.9" fill="currentColor"/></svg>';
|
|
2781
|
-
var
|
|
2915
|
+
var pageToolbarTeardowns = /* @__PURE__ */ new WeakMap();
|
|
2782
2916
|
function mountPageToolbar(ownerDocument, container, scrollToHeading) {
|
|
2783
|
-
|
|
2784
|
-
|
|
2917
|
+
pageToolbarTeardowns.get(container)?.();
|
|
2918
|
+
pageToolbarTeardowns.delete(container);
|
|
2919
|
+
const teardownCallbacks = [];
|
|
2785
2920
|
container.querySelector(`:scope > .${SCRIBDOWN_TOOLBAR_CLASS_NAME}`)?.remove();
|
|
2786
2921
|
container.querySelector(`:scope > .${SCRIBDOWN_TOOLBAR_TOC_PANEL_CLASS_NAME}`)?.remove();
|
|
2787
2922
|
container.classList.add(SCRIBDOWN_TOC_HOST_CLASS_NAME);
|
|
@@ -2815,13 +2950,13 @@ function mountPageToolbar(ownerDocument, container, scrollToHeading) {
|
|
|
2815
2950
|
const tocPanel = ownerDocument.createElement("div");
|
|
2816
2951
|
tocPanel.className = `${SCRIBDOWN_TOOLBAR_TOC_PANEL_CLASS_NAME} ${SCRIBDOWN_THIN_SCROLLBAR_CLASS_NAME}`;
|
|
2817
2952
|
const tocTitle = ownerDocument.createElement("div");
|
|
2818
|
-
tocTitle.className =
|
|
2953
|
+
tocTitle.className = SCRIBDOWN_TOOLBAR_TOC_PANEL_TITLE_CLASS_NAME;
|
|
2819
2954
|
const tocTitleText = ownerDocument.createElement("span");
|
|
2820
2955
|
tocTitleText.textContent = "\u76EE\u5F55";
|
|
2821
2956
|
tocTitle.appendChild(tocTitleText);
|
|
2822
2957
|
const tocCloseBtn = ownerDocument.createElement("button");
|
|
2823
2958
|
tocCloseBtn.type = "button";
|
|
2824
|
-
tocCloseBtn.className =
|
|
2959
|
+
tocCloseBtn.className = SCRIBDOWN_TOOLBAR_TOC_PANEL_CLOSE_CLASS_NAME;
|
|
2825
2960
|
tocCloseBtn.setAttribute("aria-label", "\u5173\u95ED\u76EE\u5F55");
|
|
2826
2961
|
tocCloseBtn.innerHTML = '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>';
|
|
2827
2962
|
const setTocOpen = (open) => {
|
|
@@ -2836,7 +2971,7 @@ function mountPageToolbar(ownerDocument, container, scrollToHeading) {
|
|
|
2836
2971
|
const tocHeadings = collectTocHeadingsFromDom(container);
|
|
2837
2972
|
if (tocHeadings.length === 0) {
|
|
2838
2973
|
const emptyElement = ownerDocument.createElement("p");
|
|
2839
|
-
emptyElement.className =
|
|
2974
|
+
emptyElement.className = SCRIBDOWN_TOOLBAR_TOC_PANEL_EMPTY_CLASS_NAME;
|
|
2840
2975
|
emptyElement.textContent = "\u6682\u65E0\u6807\u9898";
|
|
2841
2976
|
tocPanel.appendChild(emptyElement);
|
|
2842
2977
|
} else {
|
|
@@ -2866,17 +3001,17 @@ function mountPageToolbar(ownerDocument, container, scrollToHeading) {
|
|
|
2866
3001
|
widthTrigger.setAttribute("aria-haspopup", "listbox");
|
|
2867
3002
|
widthTrigger.setAttribute("aria-expanded", "false");
|
|
2868
3003
|
const widthValueElement = ownerDocument.createElement("span");
|
|
2869
|
-
widthValueElement.className =
|
|
3004
|
+
widthValueElement.className = SCRIBDOWN_TOOLBAR_MENU_SELECT_VALUE_CLASS_NAME;
|
|
2870
3005
|
widthValueElement.textContent = TOOLBAR_WIDTH_PRESETS[widthIndex].label;
|
|
2871
|
-
widthTrigger.innerHTML = `${TOOLBAR_WIDTH_ICON_SVG}<span class="
|
|
3006
|
+
widthTrigger.innerHTML = `${TOOLBAR_WIDTH_ICON_SVG}<span class="${SCRIBDOWN_TOOLBAR_MENU_SELECT_LABEL_CLASS_NAME}">\u9875\u9762\u5BBD\u5EA6</span>`;
|
|
2872
3007
|
widthTrigger.appendChild(widthValueElement);
|
|
2873
3008
|
widthTrigger.insertAdjacentHTML(
|
|
2874
3009
|
"beforeend",
|
|
2875
|
-
|
|
3010
|
+
`<svg class="${SCRIBDOWN_TOOLBAR_MENU_SELECT_CHEVRON_CLASS_NAME}" width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true"><path d="M3 4.5l3 3 3-3" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>`
|
|
2876
3011
|
);
|
|
2877
3012
|
widthGroup.appendChild(widthTrigger);
|
|
2878
3013
|
const widthChoices = ownerDocument.createElement("div");
|
|
2879
|
-
widthChoices.className =
|
|
3014
|
+
widthChoices.className = SCRIBDOWN_TOOLBAR_MENU_GROUP_CHOICES_CLASS_NAME;
|
|
2880
3015
|
widthChoices.setAttribute("role", "listbox");
|
|
2881
3016
|
const widthSubItems = [];
|
|
2882
3017
|
TOOLBAR_WIDTH_PRESETS.forEach((preset, idx) => {
|
|
@@ -2885,7 +3020,7 @@ function mountPageToolbar(ownerDocument, container, scrollToHeading) {
|
|
|
2885
3020
|
subItem.className = SCRIBDOWN_TOOLBAR_MENU_SUB_ITEM_CLASS_NAME;
|
|
2886
3021
|
subItem.setAttribute("role", "option");
|
|
2887
3022
|
subItem.setAttribute("aria-selected", String(idx === widthIndex));
|
|
2888
|
-
subItem.innerHTML = `<span class="
|
|
3023
|
+
subItem.innerHTML = `<span class="${SCRIBDOWN_TOOLBAR_MENU_SUB_ITEM_MARK_CLASS_NAME}" aria-hidden="true"><svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M2.5 6.5l2.5 2.5 4.5-5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg></span><span class="${SCRIBDOWN_TOOLBAR_MENU_SUB_ITEM_LABEL_CLASS_NAME}">${preset.label}</span>`;
|
|
2889
3024
|
subItem.addEventListener("click", () => {
|
|
2890
3025
|
widthIndex = idx;
|
|
2891
3026
|
applyContentWidth(ownerDocument, preset.value);
|
|
@@ -2923,13 +3058,14 @@ function mountPageToolbar(ownerDocument, container, scrollToHeading) {
|
|
|
2923
3058
|
ownerWindow?.scrollTo({ top: 0, behavior: "smooth" });
|
|
2924
3059
|
closeMoreMenu();
|
|
2925
3060
|
});
|
|
2926
|
-
const aboutItem = ownerDocument.createElement("
|
|
2927
|
-
aboutItem.
|
|
3061
|
+
const aboutItem = ownerDocument.createElement("a");
|
|
3062
|
+
aboutItem.href = PROJECT_HOMEPAGE_URL;
|
|
3063
|
+
aboutItem.target = "_blank";
|
|
3064
|
+
aboutItem.rel = "noopener noreferrer";
|
|
2928
3065
|
aboutItem.className = SCRIBDOWN_TOOLBAR_MENU_ITEM_CLASS_NAME;
|
|
2929
3066
|
aboutItem.setAttribute("role", "menuitem");
|
|
2930
3067
|
aboutItem.innerHTML = `${TOOLBAR_ABOUT_ICON_SVG}<span>\u5173\u4E8E</span>`;
|
|
2931
3068
|
aboutItem.addEventListener("click", () => {
|
|
2932
|
-
ownerWindow?.open(PROJECT_HOMEPAGE_URL, "_blank", "noopener,noreferrer");
|
|
2933
3069
|
closeMoreMenu();
|
|
2934
3070
|
});
|
|
2935
3071
|
menu.appendChild(backTopItem);
|
|
@@ -2948,13 +3084,17 @@ function mountPageToolbar(ownerDocument, container, scrollToHeading) {
|
|
|
2948
3084
|
const opened = menu.classList.toggle("is-open");
|
|
2949
3085
|
moreBtn.setAttribute("aria-expanded", String(opened));
|
|
2950
3086
|
});
|
|
2951
|
-
|
|
3087
|
+
const handleDocumentClick = (e) => {
|
|
2952
3088
|
const target = e.target;
|
|
2953
3089
|
if (!menu.contains(target) && target !== moreBtn) {
|
|
2954
3090
|
closeMoreMenu();
|
|
2955
3091
|
}
|
|
3092
|
+
};
|
|
3093
|
+
ownerDocument.addEventListener("click", handleDocumentClick);
|
|
3094
|
+
teardownCallbacks.push(() => {
|
|
3095
|
+
ownerDocument.removeEventListener("click", handleDocumentClick);
|
|
2956
3096
|
});
|
|
2957
|
-
const markdownContainer = container.matches(`.${
|
|
3097
|
+
const markdownContainer = container.matches(`.${SCRIBDOWN_MARKDOWN_CLASS_NAME2}`) ? container : container.querySelector(`.${SCRIBDOWN_MARKDOWN_CLASS_NAME2}`);
|
|
2958
3098
|
const headingElements = markdownContainer ? Array.from(
|
|
2959
3099
|
markdownContainer.querySelectorAll(
|
|
2960
3100
|
"h1[id],h2[id],h3[id],h4[id],h5[id],h6[id]"
|
|
@@ -2967,12 +3107,12 @@ function mountPageToolbar(ownerDocument, container, scrollToHeading) {
|
|
|
2967
3107
|
currentHeading.setAttribute("role", "button");
|
|
2968
3108
|
currentHeading.tabIndex = 0;
|
|
2969
3109
|
const currentHeadingText = ownerDocument.createElement("span");
|
|
2970
|
-
currentHeadingText.className =
|
|
3110
|
+
currentHeadingText.className = SCRIBDOWN_TOOLBAR_CURRENT_TEXT_CLASS_NAME;
|
|
2971
3111
|
currentHeading.appendChild(currentHeadingText);
|
|
2972
3112
|
let lastHeadingText = "";
|
|
2973
3113
|
let activeHeadingElement = headingElements[0];
|
|
2974
3114
|
const tocLinkByHeading = /* @__PURE__ */ new Map();
|
|
2975
|
-
tocPanel.querySelectorAll(`a.${
|
|
3115
|
+
tocPanel.querySelectorAll(`a.${TOC_LINK_CLASS_NAME3}`).forEach((linkElement) => {
|
|
2976
3116
|
const rawTargetId = (linkElement.getAttribute("href") ?? "").slice(1);
|
|
2977
3117
|
if (!rawTargetId) {
|
|
2978
3118
|
return;
|
|
@@ -3050,7 +3190,7 @@ function mountPageToolbar(ownerDocument, container, scrollToHeading) {
|
|
|
3050
3190
|
});
|
|
3051
3191
|
ownerWindow?.addEventListener("resize", onScrollOrResize, { passive: true });
|
|
3052
3192
|
updateCurrentHeading();
|
|
3053
|
-
|
|
3193
|
+
teardownCallbacks.push(() => {
|
|
3054
3194
|
if (scrollRafId) {
|
|
3055
3195
|
ownerWindow?.cancelAnimationFrame(scrollRafId);
|
|
3056
3196
|
}
|
|
@@ -3064,6 +3204,40 @@ function mountPageToolbar(ownerDocument, container, scrollToHeading) {
|
|
|
3064
3204
|
toolbar.appendChild(menu);
|
|
3065
3205
|
container.appendChild(toolbar);
|
|
3066
3206
|
container.insertBefore(tocPanel, contentArea);
|
|
3207
|
+
pageToolbarTeardowns.set(container, () => {
|
|
3208
|
+
teardownCallbacks.forEach((teardownCallback) => {
|
|
3209
|
+
teardownCallback();
|
|
3210
|
+
});
|
|
3211
|
+
});
|
|
3212
|
+
}
|
|
3213
|
+
|
|
3214
|
+
// src/index.ts
|
|
3215
|
+
async function renderMarkdown(markdownText) {
|
|
3216
|
+
const processor = unified().use(remarkParse).use(remarkGfm).use(remarkFrontmatter).use(remarkFrontmatterMetadata).use(remarkHighlightMark).use(remarkDefinitionLists).use(remarkTableOfContents).use(remarkImageFigures).use(remarkSourceLine).use(remarkRehype, { allowDangerousHtml: true }).use(rehypeRaw).use(rehypeFrameClass).use(rehypeVideoFigures).use(rehypeSanitize, createScribdownSanitizeSchema()).use(rehypeStringify);
|
|
3217
|
+
const renderedHtml = String(await processor.process(markdownText));
|
|
3218
|
+
const sanitizedHtml = sanitizeHtmlWithDomPurify(renderedHtml);
|
|
3219
|
+
return highlightMarkdownCodeBlocks(sanitizedHtml);
|
|
3220
|
+
}
|
|
3221
|
+
function defaultScrollToHeading(targetElement) {
|
|
3222
|
+
targetElement.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
3223
|
+
}
|
|
3224
|
+
function hydrateMarkdown(rootElement, options = {}) {
|
|
3225
|
+
hydrateMarkdownImages(rootElement);
|
|
3226
|
+
hydrateMarkdownVideos(rootElement);
|
|
3227
|
+
hydrateMermaidBlocks(rootElement);
|
|
3228
|
+
hydrateCodeBlocks(rootElement);
|
|
3229
|
+
hydrateToc(rootElement, options.scrollToHeading ?? defaultScrollToHeading);
|
|
3230
|
+
}
|
|
3231
|
+
function mountMarkdownToolbar(container, options = {}) {
|
|
3232
|
+
const ownerDocument = container.ownerDocument;
|
|
3233
|
+
if (!ownerDocument) {
|
|
3234
|
+
return;
|
|
3235
|
+
}
|
|
3236
|
+
if (typeof ownerDocument.defaultView?.scrollTo !== "function") {
|
|
3237
|
+
return;
|
|
3238
|
+
}
|
|
3239
|
+
applyContentWidth(ownerDocument, loadContentWidth());
|
|
3240
|
+
mountPageToolbar(ownerDocument, container, options.scrollToHeading ?? defaultScrollToHeading);
|
|
3067
3241
|
}
|
|
3068
3242
|
export {
|
|
3069
3243
|
hydrateMarkdown,
|