@4399ywkf/editor 0.7.0 → 0.8.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.
@@ -0,0 +1,258 @@
1
+ // src/components/tiptap-node/code-block-node/code-highlighter.ts
2
+ var PLAINTEXT = "plaintext";
3
+ var DEFAULT_THEMES = { light: "github-light", dark: "github-dark" };
4
+ var MAX_HIGHLIGHT_LENGTH = 1e5;
5
+ var MAX_HIGHLIGHT_LINES = 2e3;
6
+ var CACHE_MAX_ENTRIES = 256;
7
+ var CACHE_MAX_SOURCE_LENGTH = 2e6;
8
+ var DEFAULT_ALIASES = {
9
+ sh: "bash",
10
+ shell: "bash",
11
+ zsh: "bash",
12
+ py: "python",
13
+ golang: "go",
14
+ tsx: "tsx",
15
+ jsx: "jsx",
16
+ js: "javascript",
17
+ mjs: "javascript",
18
+ cjs: "javascript",
19
+ ts: "typescript",
20
+ yml: "yaml",
21
+ md: "markdown",
22
+ "c++": "cpp",
23
+ "c#": "csharp",
24
+ cs: "csharp",
25
+ rb: "ruby",
26
+ rs: "rust",
27
+ kt: "kotlin",
28
+ ps1: "powershell",
29
+ dockerfile: "docker",
30
+ text: PLAINTEXT,
31
+ txt: PLAINTEXT,
32
+ plain: PLAINTEXT
33
+ };
34
+ var DISPLAY_NAMES = {
35
+ bash: "Bash",
36
+ python: "Python",
37
+ go: "Go",
38
+ javascript: "JavaScript",
39
+ typescript: "TypeScript",
40
+ jsx: "JSX",
41
+ tsx: "TSX",
42
+ json: "JSON",
43
+ yaml: "YAML",
44
+ toml: "TOML",
45
+ html: "HTML",
46
+ css: "CSS",
47
+ scss: "SCSS",
48
+ less: "Less",
49
+ markdown: "Markdown",
50
+ sql: "SQL",
51
+ java: "Java",
52
+ kotlin: "Kotlin",
53
+ swift: "Swift",
54
+ c: "C",
55
+ cpp: "C++",
56
+ csharp: "C#",
57
+ rust: "Rust",
58
+ ruby: "Ruby",
59
+ php: "PHP",
60
+ lua: "Lua",
61
+ dart: "Dart",
62
+ docker: "Dockerfile",
63
+ powershell: "PowerShell",
64
+ xml: "XML",
65
+ vue: "Vue",
66
+ svelte: "Svelte",
67
+ graphql: "GraphQL",
68
+ nginx: "Nginx",
69
+ ini: "INI",
70
+ diff: "Diff",
71
+ gdscript: "GDScript"
72
+ };
73
+ function getLanguageDisplayName(normalized) {
74
+ return DISPLAY_NAMES[normalized] ?? null;
75
+ }
76
+ function normalizeLanguageId(input) {
77
+ const raw = (input ?? "").trim().toLowerCase();
78
+ if (!raw) return PLAINTEXT;
79
+ return DEFAULT_ALIASES[raw] ?? raw;
80
+ }
81
+ function getCodeBlockTitle(attrs, normalizeLanguage = normalizeLanguageId) {
82
+ const label = attrs.label?.trim();
83
+ if (label) return label;
84
+ const raw = attrs.language?.trim();
85
+ if (!raw) return "纯文本";
86
+ const normalized = normalizeLanguage(raw);
87
+ if (normalized === PLAINTEXT) return "纯文本";
88
+ return getLanguageDisplayName(normalized) ?? raw;
89
+ }
90
+ function resolveShikiTheme(theme, isDark) {
91
+ if (!theme) return isDark ? DEFAULT_THEMES.dark : DEFAULT_THEMES.light;
92
+ if (typeof theme === "string") return theme;
93
+ return isDark ? theme.dark : theme.light;
94
+ }
95
+ var escapeHtml = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
96
+ function plaintextResult(source) {
97
+ return {
98
+ language: PLAINTEXT,
99
+ tokens: [],
100
+ html: `<pre class="shiki"><code>${escapeHtml(source)}</code></pre>`
101
+ };
102
+ }
103
+ function exceedsLimits(source) {
104
+ if (source.length > MAX_HIGHLIGHT_LENGTH) return true;
105
+ let lines = 1;
106
+ for (let i = 0; i < source.length; i += 1) {
107
+ if (source.charCodeAt(i) === 10) {
108
+ lines += 1;
109
+ if (lines > MAX_HIGHLIGHT_LINES) return true;
110
+ }
111
+ }
112
+ return false;
113
+ }
114
+ var defaultLoader = () => import('shiki');
115
+ var sharedModules = /* @__PURE__ */ new Map();
116
+ var sharedHighlighters = /* @__PURE__ */ new WeakMap();
117
+ var pendingLoads = /* @__PURE__ */ new WeakMap();
118
+ function loadModule(loader) {
119
+ let pending = sharedModules.get(loader);
120
+ if (!pending) {
121
+ pending = loader().catch((error) => {
122
+ sharedModules.delete(loader);
123
+ throw error;
124
+ });
125
+ sharedModules.set(loader, pending);
126
+ }
127
+ return pending;
128
+ }
129
+ function loadHighlighter(shiki) {
130
+ let pending = sharedHighlighters.get(shiki);
131
+ if (!pending) {
132
+ pending = shiki.createHighlighter({ themes: [], langs: [] }).then((h) => h).catch((error) => {
133
+ sharedHighlighters.delete(shiki);
134
+ throw error;
135
+ });
136
+ sharedHighlighters.set(shiki, pending);
137
+ }
138
+ return pending;
139
+ }
140
+ function loadOnce(highlighter, key, run) {
141
+ let loads = pendingLoads.get(highlighter);
142
+ if (!loads) {
143
+ loads = /* @__PURE__ */ new Map();
144
+ pendingLoads.set(highlighter, loads);
145
+ }
146
+ let pending = loads.get(key);
147
+ if (!pending) {
148
+ pending = run().catch((error) => {
149
+ loads?.delete(key);
150
+ throw error;
151
+ });
152
+ loads.set(key, pending);
153
+ }
154
+ return pending;
155
+ }
156
+ function flattenTokens(lines) {
157
+ const out = [];
158
+ for (const line of lines) {
159
+ for (const token of line) {
160
+ if (!token.content) continue;
161
+ const start = token.offset;
162
+ const end = start + token.content.length;
163
+ const fontStyle = typeof token.fontStyle === "number" && token.fontStyle > 0 ? token.fontStyle : void 0;
164
+ if (!token.color && fontStyle === void 0) continue;
165
+ out.push({ start, end, color: token.color, fontStyle });
166
+ }
167
+ }
168
+ return out;
169
+ }
170
+ var configSerial = 0;
171
+ function createCodeHighlighter(options = {}, deps = {}) {
172
+ const loader = deps.loadShiki ?? defaultLoader;
173
+ const aliases = { ...DEFAULT_ALIASES, ...options.languageAliases ?? {} };
174
+ configSerial += 1;
175
+ const configId = configSerial;
176
+ const cache = /* @__PURE__ */ new Map();
177
+ let cachedSourceLength = 0;
178
+ let customLanguagesLoaded = null;
179
+ const normalizeLanguage = (input) => {
180
+ const raw = (input ?? "").trim().toLowerCase();
181
+ if (!raw) return PLAINTEXT;
182
+ return aliases[raw] ?? raw;
183
+ };
184
+ const evict = () => {
185
+ while (cache.size > CACHE_MAX_ENTRIES || cachedSourceLength > CACHE_MAX_SOURCE_LENGTH) {
186
+ const oldest = cache.keys().next();
187
+ if (oldest.done) break;
188
+ const entry = cache.get(oldest.value);
189
+ cache.delete(oldest.value);
190
+ if (entry) cachedSourceLength -= entry.size;
191
+ }
192
+ };
193
+ const forget = (key) => {
194
+ const entry = cache.get(key);
195
+ if (!entry) return;
196
+ cache.delete(key);
197
+ cachedSourceLength -= entry.size;
198
+ };
199
+ const ensureCustomLanguages = (highlighter) => {
200
+ if (!options.languages) return Promise.resolve();
201
+ if (!customLanguagesLoaded) {
202
+ customLanguagesLoaded = highlighter.loadLanguage(options.languages).catch((error) => {
203
+ customLanguagesLoaded = null;
204
+ throw error;
205
+ });
206
+ }
207
+ return customLanguagesLoaded;
208
+ };
209
+ const isKnownLanguage = (shiki, highlighter, lang) => {
210
+ if (lang === PLAINTEXT) return true;
211
+ if (highlighter.getLoadedLanguages().includes(lang)) return true;
212
+ return Object.hasOwn(shiki.bundledLanguages, lang);
213
+ };
214
+ const run = async (source, lang, theme) => {
215
+ const shiki = await loadModule(loader);
216
+ const highlighter = await loadHighlighter(shiki);
217
+ await loadOnce(highlighter, `theme:${theme}`, () => highlighter.loadTheme(theme));
218
+ await ensureCustomLanguages(highlighter);
219
+ const resolved = isKnownLanguage(shiki, highlighter, lang) ? lang : PLAINTEXT;
220
+ if (resolved !== PLAINTEXT && !highlighter.getLoadedLanguages().includes(resolved)) {
221
+ await loadOnce(
222
+ highlighter,
223
+ `lang:${resolved}`,
224
+ () => highlighter.loadLanguage(resolved)
225
+ );
226
+ }
227
+ const html = highlighter.codeToHtml(source, { lang: resolved, theme });
228
+ if (resolved === PLAINTEXT) return { language: PLAINTEXT, tokens: [], html };
229
+ const { tokens } = highlighter.codeToTokens(source, { lang: resolved, theme });
230
+ return { language: resolved, tokens: flattenTokens(tokens), html };
231
+ };
232
+ const highlight = (source, language, theme) => {
233
+ const lang = normalizeLanguage(language);
234
+ const resolvedTheme = theme ?? resolveShikiTheme(options.shikiTheme, false);
235
+ if (lang === PLAINTEXT && !source) return Promise.resolve(plaintextResult(source));
236
+ if (exceedsLimits(source)) return Promise.resolve(plaintextResult(source));
237
+ const key = `${configId}\0${resolvedTheme}\0${lang}\0${source}`;
238
+ const hit = cache.get(key);
239
+ if (hit) {
240
+ cache.delete(key);
241
+ cache.set(key, hit);
242
+ return hit.promise;
243
+ }
244
+ const promise = run(source, lang, resolvedTheme).catch(() => {
245
+ forget(key);
246
+ return plaintextResult(source);
247
+ });
248
+ cache.set(key, { promise, size: source.length });
249
+ cachedSourceLength += source.length;
250
+ evict();
251
+ return promise;
252
+ };
253
+ return { normalizeLanguage, highlight };
254
+ }
255
+
256
+ export { createCodeHighlighter, getCodeBlockTitle, resolveShikiTheme };
257
+ //# sourceMappingURL=chunk-ZY5KLA7T.js.map
258
+ //# sourceMappingURL=chunk-ZY5KLA7T.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/tiptap-node/code-block-node/code-highlighter.ts"],"names":[],"mappings":"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","file":"chunk-ZY5KLA7T.js","sourcesContent":["/**\n * Shiki 高亮服务 —— 不依赖 Editor / React / DOM 的纯服务。\n *\n * 职责:语言别名解析、Shiki 动态加载(引擎 / 主题 / 语言全部懒加载并共享 Promise)、\n * 把 Shiki 的 token 行拍平成 UTF-16 区间给 ProseMirror Decoration、静态 HTML 输出、\n * 有上限的缓存,以及任何一步失败时的纯文本回退。\n *\n * 为什么用 `import(\"shiki\")` 而不是顶层 import:doc 入口之外(sheet / pdf / pptx)\n * 不能背上 Shiki;而且 bundle-full 的语言表本身只是一组 `() => import()`,\n * 真正的 grammar 只在 `loadLanguage` 时才下载。\n */\nimport type {\n CodeBlockOptions,\n CodeHighlighter,\n HighlightResult,\n HighlightToken,\n ShikiTheme,\n} from \"./code-types.ts\"\n\ntype ShikiModule = typeof import(\"shiki\")\ntype Highlighter = import(\"shiki\").HighlighterGeneric<string, string>\n\n/** Shiki 的特殊语言:不加载 grammar,直接按纯文本输出 */\nexport const PLAINTEXT = \"plaintext\"\n\nexport const DEFAULT_THEMES = { light: \"github-light\", dark: \"github-dark\" } as const\n\n/** 单块超过这个 UTF-16 长度 / 行数就不高亮(源码、复制、导出照常,只是不着色) */\nexport const MAX_HIGHLIGHT_LENGTH = 100_000\nexport const MAX_HIGHLIGHT_LINES = 2_000\n\n/** 缓存上限:条目数与保留的源码总长度,任一超限就淘汰最旧的 */\nconst CACHE_MAX_ENTRIES = 256\nconst CACHE_MAX_SOURCE_LENGTH = 2_000_000\n\n/**\n * 内置别名。Shiki 自己也带一份 `bundledLanguagesAlias`(py / sh / golang…),\n * 这里列的是**加载 Shiki 之前**就要能同步解析的那部分 —— 标签栏显示名和\n * 缓存键都在等待加载前就需要规范化结果。\n */\nconst DEFAULT_ALIASES: Record<string, string> = {\n sh: \"bash\",\n shell: \"bash\",\n zsh: \"bash\",\n py: \"python\",\n golang: \"go\",\n tsx: \"tsx\",\n jsx: \"jsx\",\n js: \"javascript\",\n mjs: \"javascript\",\n cjs: \"javascript\",\n ts: \"typescript\",\n yml: \"yaml\",\n md: \"markdown\",\n \"c++\": \"cpp\",\n \"c#\": \"csharp\",\n cs: \"csharp\",\n rb: \"ruby\",\n rs: \"rust\",\n kt: \"kotlin\",\n ps1: \"powershell\",\n dockerfile: \"docker\",\n text: PLAINTEXT,\n txt: PLAINTEXT,\n plain: PLAINTEXT,\n}\n\n/**\n * 常见语言的显示名(标签栏 / 标题用)。Shiki 的 `bundledLanguagesInfo` 有完整表,\n * 但那要先加载 Shiki;标题在加载前就要能显示,所以这里放一份同步的小表,\n * 没列到的语言直接显示规范化后的 id。\n */\nconst DISPLAY_NAMES: Record<string, string> = {\n bash: \"Bash\",\n python: \"Python\",\n go: \"Go\",\n javascript: \"JavaScript\",\n typescript: \"TypeScript\",\n jsx: \"JSX\",\n tsx: \"TSX\",\n json: \"JSON\",\n yaml: \"YAML\",\n toml: \"TOML\",\n html: \"HTML\",\n css: \"CSS\",\n scss: \"SCSS\",\n less: \"Less\",\n markdown: \"Markdown\",\n sql: \"SQL\",\n java: \"Java\",\n kotlin: \"Kotlin\",\n swift: \"Swift\",\n c: \"C\",\n cpp: \"C++\",\n csharp: \"C#\",\n rust: \"Rust\",\n ruby: \"Ruby\",\n php: \"PHP\",\n lua: \"Lua\",\n dart: \"Dart\",\n docker: \"Dockerfile\",\n powershell: \"PowerShell\",\n xml: \"XML\",\n vue: \"Vue\",\n svelte: \"Svelte\",\n graphql: \"GraphQL\",\n nginx: \"Nginx\",\n ini: \"INI\",\n diff: \"Diff\",\n gdscript: \"GDScript\",\n}\n\n/** 规范化语言 id → 显示名;没有专门显示名时返回 null,由调用方决定回退 */\nexport function getLanguageDisplayName(normalized: string): string | null {\n return DISPLAY_NAMES[normalized] ?? null\n}\n\n/** 只用内置别名的规范化(不需要服务实例的场合,如 docx 导出) */\nexport function normalizeLanguageId(input: string | null | undefined): string {\n const raw = (input ?? \"\").trim().toLowerCase()\n if (!raw) return PLAINTEXT\n return DEFAULT_ALIASES[raw] ?? raw\n}\n\n/** 标签栏 / 标题 / 导出标题的显示文字:label → 语言显示名 → 未知语言原文 → “纯文本” */\nexport function getCodeBlockTitle(\n attrs: { language: string | null; label: string | null },\n normalizeLanguage: (input: string | null | undefined) => string = normalizeLanguageId,\n): string {\n const label = attrs.label?.trim()\n if (label) return label\n const raw = attrs.language?.trim()\n if (!raw) return \"纯文本\"\n const normalized = normalizeLanguage(raw)\n if (normalized === PLAINTEXT) return \"纯文本\"\n return getLanguageDisplayName(normalized) ?? raw\n}\n\n/** 按 `.dark` 状态从配置里挑主题名 */\nexport function resolveShikiTheme(theme: ShikiTheme | undefined, isDark: boolean): string {\n if (!theme) return isDark ? DEFAULT_THEMES.dark : DEFAULT_THEMES.light\n if (typeof theme === \"string\") return theme\n return isDark ? theme.dark : theme.light\n}\n\nconst escapeHtml = (s: string): string =>\n s.replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\").replace(/\"/g, \"&quot;\")\n\n/** 任何失败路径都落到这里:没有 token(不着色),HTML 只做转义 */\nfunction plaintextResult(source: string): HighlightResult {\n return {\n language: PLAINTEXT,\n tokens: [],\n html: `<pre class=\"shiki\"><code>${escapeHtml(source)}</code></pre>`,\n }\n}\n\nfunction exceedsLimits(source: string): boolean {\n if (source.length > MAX_HIGHLIGHT_LENGTH) return true\n let lines = 1\n for (let i = 0; i < source.length; i += 1) {\n if (source.charCodeAt(i) === 10) {\n lines += 1\n if (lines > MAX_HIGHLIGHT_LINES) return true\n }\n }\n return false\n}\n\n/* --------------------------- 模块级共享状态 --------------------------- */\n\n/**\n * Shiki 模块与 highlighter 实例全模块共享:多个编辑器(编辑器 + ArticleViewer)\n * 同页时只加载一次引擎和主题。失败的 Promise 会被清掉,下次调用重新尝试。\n *\n * 共享状态按 loader 函数 / 模块对象作键:生产环境永远只有 `defaultLoader` 一把键,\n * 测试注入的 loader 各自独立,不会被先前成功加载的真模块顶掉。\n */\ntype ShikiLoader = () => Promise<ShikiModule>\nconst defaultLoader: ShikiLoader = () => import(\"shiki\")\nconst sharedModules = new Map<ShikiLoader, Promise<ShikiModule>>()\nconst sharedHighlighters = new WeakMap<ShikiModule, Promise<Highlighter>>()\n/** 每个 highlighter 已发起过的 loadLanguage / loadTheme,避免并发重复加载 */\nconst pendingLoads = new WeakMap<Highlighter, Map<string, Promise<void>>>()\n\nfunction loadModule(loader: ShikiLoader): Promise<ShikiModule> {\n let pending = sharedModules.get(loader)\n if (!pending) {\n pending = loader().catch((error) => {\n sharedModules.delete(loader)\n throw error\n })\n sharedModules.set(loader, pending)\n }\n return pending\n}\n\nfunction loadHighlighter(shiki: ShikiModule): Promise<Highlighter> {\n let pending = sharedHighlighters.get(shiki)\n if (!pending) {\n pending = shiki\n .createHighlighter({ themes: [], langs: [] })\n .then((h) => h as unknown as Highlighter)\n .catch((error) => {\n sharedHighlighters.delete(shiki)\n throw error\n })\n sharedHighlighters.set(shiki, pending)\n }\n return pending\n}\n\nfunction loadOnce(highlighter: Highlighter, key: string, run: () => Promise<void>): Promise<void> {\n let loads = pendingLoads.get(highlighter)\n if (!loads) {\n loads = new Map()\n pendingLoads.set(highlighter, loads)\n }\n let pending = loads.get(key)\n if (!pending) {\n pending = run().catch((error) => {\n loads?.delete(key)\n throw error\n })\n loads.set(key, pending)\n }\n return pending\n}\n\n/* --------------------------- token 拍平 --------------------------- */\n\n/**\n * Shiki 每个 token 自带相对整段源码的 `offset`(JS 字符串索引 = UTF-16 单位),\n * 与 ProseMirror 文本节点内的位置一一对应,直接拿来做 Decoration 区间。\n * 空 token 和没有任何样式的 token 跳过:不产生无意义的 span。\n */\nfunction flattenTokens(lines: import(\"shiki\").ThemedToken[][]): HighlightToken[] {\n const out: HighlightToken[] = []\n for (const line of lines) {\n for (const token of line) {\n if (!token.content) continue\n const start = token.offset\n const end = start + token.content.length\n const fontStyle =\n typeof token.fontStyle === \"number\" && token.fontStyle > 0 ? token.fontStyle : undefined\n if (!token.color && fontStyle === undefined) continue\n out.push({ start, end, color: token.color, fontStyle })\n }\n }\n return out\n}\n\n/* --------------------------- 服务 --------------------------- */\n\nexport interface CodeHighlighterDeps {\n /** 测试注入:替换 `import(\"shiki\")` */\n loadShiki?: () => Promise<ShikiModule>\n}\n\nlet configSerial = 0\n\nexport function createCodeHighlighter(\n options: CodeBlockOptions = {},\n deps: CodeHighlighterDeps = {},\n): CodeHighlighter {\n const loader = deps.loadShiki ?? defaultLoader\n const aliases: Record<string, string> = { ...DEFAULT_ALIASES, ...(options.languageAliases ?? {}) }\n // 每个服务实例一份缓存;configId 进缓存键,保证不同配置的实例即使合并也不串\n configSerial += 1\n const configId = configSerial\n const cache = new Map<string, { promise: Promise<HighlightResult>; size: number }>()\n let cachedSourceLength = 0\n /** 业务注册的额外语言只往共享 highlighter 里灌一次 */\n let customLanguagesLoaded: Promise<void> | null = null\n\n const normalizeLanguage = (input: string | null | undefined): string => {\n const raw = (input ?? \"\").trim().toLowerCase()\n if (!raw) return PLAINTEXT\n return aliases[raw] ?? raw\n }\n\n const evict = () => {\n while (cache.size > CACHE_MAX_ENTRIES || cachedSourceLength > CACHE_MAX_SOURCE_LENGTH) {\n const oldest = cache.keys().next()\n if (oldest.done) break\n const entry = cache.get(oldest.value)\n cache.delete(oldest.value)\n if (entry) cachedSourceLength -= entry.size\n }\n }\n\n const forget = (key: string) => {\n const entry = cache.get(key)\n if (!entry) return\n cache.delete(key)\n cachedSourceLength -= entry.size\n }\n\n const ensureCustomLanguages = (highlighter: Highlighter): Promise<void> => {\n if (!options.languages) return Promise.resolve()\n if (!customLanguagesLoaded) {\n customLanguagesLoaded = highlighter\n .loadLanguage(options.languages as never)\n .catch((error) => {\n customLanguagesLoaded = null\n throw error\n })\n }\n return customLanguagesLoaded\n }\n\n const isKnownLanguage = (shiki: ShikiModule, highlighter: Highlighter, lang: string): boolean => {\n if (lang === PLAINTEXT) return true\n if (highlighter.getLoadedLanguages().includes(lang)) return true\n return Object.hasOwn(shiki.bundledLanguages, lang)\n }\n\n const run = async (source: string, lang: string, theme: string): Promise<HighlightResult> => {\n const shiki = await loadModule(loader)\n const highlighter = await loadHighlighter(shiki)\n await loadOnce(highlighter, `theme:${theme}`, () => highlighter.loadTheme(theme as never))\n await ensureCustomLanguages(highlighter)\n\n // 未知语言:属性原样留给调用方,服务内部按纯文本输出(仍走 Shiki 以保证 HTML 结构一致)\n const resolved = isKnownLanguage(shiki, highlighter, lang) ? lang : PLAINTEXT\n if (resolved !== PLAINTEXT && !highlighter.getLoadedLanguages().includes(resolved)) {\n await loadOnce(highlighter, `lang:${resolved}`, () =>\n highlighter.loadLanguage(resolved as never),\n )\n }\n\n const html = highlighter.codeToHtml(source, { lang: resolved, theme })\n if (resolved === PLAINTEXT) return { language: PLAINTEXT, tokens: [], html }\n const { tokens } = highlighter.codeToTokens(source, { lang: resolved, theme })\n return { language: resolved, tokens: flattenTokens(tokens), html }\n }\n\n const highlight = (\n source: string,\n language: string | null | undefined,\n theme?: string,\n ): Promise<HighlightResult> => {\n const lang = normalizeLanguage(language)\n const resolvedTheme = theme ?? resolveShikiTheme(options.shikiTheme, false)\n // 超长内容不进 Shiki 也不进缓存:纯文本结果本身就是常量级开销\n if (lang === PLAINTEXT && !source) return Promise.resolve(plaintextResult(source))\n if (exceedsLimits(source)) return Promise.resolve(plaintextResult(source))\n\n const key = `${configId}\u0000${resolvedTheme}\u0000${lang}\u0000${source}`\n const hit = cache.get(key)\n if (hit) {\n // LRU:命中的挪到最新\n cache.delete(key)\n cache.set(key, hit)\n return hit.promise\n }\n\n const promise = run(source, lang, resolvedTheme).catch(() => {\n // 失败不缓存:网络恢复 / 主题修正后下一次调用重新尝试\n forget(key)\n return plaintextResult(source)\n })\n cache.set(key, { promise, size: source.length })\n cachedSourceLength += source.length\n evict()\n return promise\n }\n\n return { normalizeLanguage, highlight }\n}\n"]}
@@ -0,0 +1,65 @@
1
+ import { LanguageInput } from 'shiki';
2
+
3
+ /**
4
+ * 代码块 / 代码分组的公共类型。
5
+ *
6
+ * 这份文件**只有类型**,不带任何运行时导入 —— Shiki 的类型通过 `import type`
7
+ * 引用,编译后整段擦除。这样 schema 文件、Node 测试和 React NodeView 都能
8
+ * 从这里取类型,而不会把 Shiki 拉进不该加载它的入口(sheet / pdf / pptx)。
9
+ */
10
+
11
+ /**
12
+ * 传入编辑器的内容格式。
13
+ *
14
+ * 未声明时沿用旧行为:对象按 JSON、字符串按 HTML。`"json"` 只接受对象,
15
+ * 不会去解析 JSON 文本 —— 那是业务侧的事。
16
+ */
17
+ type ContentFormat = "json" | "html" | "markdown";
18
+ /** 单主题,或按 `.dark` 状态切换的明暗主题对。 */
19
+ type ShikiTheme = string | {
20
+ light: string;
21
+ dark: string;
22
+ };
23
+ /** `codeBlock` 扩展的可选配置。默认值即可用,无需注册语言。 */
24
+ interface CodeBlockOptions {
25
+ /** 默认 `{ light: "github-light", dark: "github-dark" }` */
26
+ shikiTheme?: ShikiTheme;
27
+ /** 自定义别名,优先于内置别名,如 `{ golang: "go" }` */
28
+ languageAliases?: Record<string, string>;
29
+ /** 额外语言注册项,使用 Shiki 的 LanguageInput(注册对象或异步加载器) */
30
+ languages?: LanguageInput;
31
+ }
32
+ /** `codeBlock` 节点属性。`language` 保留输入原值,规范化只在高亮时进行。 */
33
+ interface CodeBlockAttrs {
34
+ id?: string | null;
35
+ language: string | null;
36
+ /** 标签页文字 / 文件名,如 `curl`、`example.py`;为空时显示语言名 */
37
+ label: string | null;
38
+ }
39
+ /** `codeGroup` 节点属性。活动标签不在这里 —— 那是 NodeView 本地状态。 */
40
+ interface CodeGroupAttrs {
41
+ id?: string | null;
42
+ }
43
+ /** 一个 token 的 UTF-16 区间(相对代码块文本起点)与样式。 */
44
+ interface HighlightToken {
45
+ start: number;
46
+ end: number;
47
+ color?: string;
48
+ /** Shiki FontStyle 位掩码:1 斜体、2 粗体、4 下划线、8 删除线 */
49
+ fontStyle?: number;
50
+ }
51
+ /** 高亮服务的输出:Decoration 用 tokens,静态导出用 html。 */
52
+ interface HighlightResult {
53
+ /** 实际用于高亮的规范化语言;未知语言时为 `"plaintext"` */
54
+ language: string;
55
+ tokens: HighlightToken[];
56
+ html: string;
57
+ }
58
+ interface CodeHighlighter {
59
+ /** 别名解析 + 小写化;不认识的语言原样返回(调用方决定回退) */
60
+ normalizeLanguage(input: string | null | undefined): string;
61
+ /** 未指定 theme 时按配置的 light 主题 */
62
+ highlight(source: string, language: string | null | undefined, theme?: string): Promise<HighlightResult>;
63
+ }
64
+
65
+ export type { ContentFormat as C, HighlightResult as H, ShikiTheme as S, CodeBlockOptions as a, CodeHighlighter as b, CodeBlockAttrs as c, CodeGroupAttrs as d, HighlightToken as e };
@@ -21,7 +21,7 @@ import { Node } from '@tiptap/pm/model';
21
21
  /**
22
22
  * 节点类型 → 标签。不在表里的走 `<node type="..."/>` 兜底。
23
23
  *
24
- * 这张表镜像的是**本编辑器自己的 schema**(30 个节点类型)。它必须和
24
+ * 这张表镜像的是**本编辑器自己的 schema**(34 个节点类型)。它必须和
25
25
  * components/tiptap-node/ 下的节点定义住在同一个包里 —— 跨仓之后每加一个节点
26
26
  * 类型,序列化就静默降级成占位符,不报错、不丢数据、也不告诉你。
27
27
  */
@@ -8,6 +8,12 @@ var NODE_TAG = {
8
8
  taskItem: "task",
9
9
  blockquote: "quote",
10
10
  codeBlock: "code",
11
+ // 代码分组:块容器,子节点只能是 <code>;活动标签是 NodeView 本地状态,不进 XML
12
+ codeGroup: "code-group",
13
+ // 折叠容器:块容器,子块正常嵌套;展开状态是视图状态,不进 XML
14
+ callout: "callout",
15
+ details: "details",
16
+ apiParam: "param",
11
17
  horizontalRule: "hr",
12
18
  table: "table",
13
19
  tableRow: "tr",
@@ -51,7 +57,7 @@ var ATTR_UNALIAS = {
51
57
  };
52
58
  var NOISE_ATTRS = /* @__PURE__ */ new Set(["data-toc-id"]);
53
59
  var NUM_ATTRS = /* @__PURE__ */ new Set(["level", "indent", "colspan", "rowspan"]);
54
- var BOOL_ATTRS = /* @__PURE__ */ new Set(["checked"]);
60
+ var BOOL_ATTRS = /* @__PURE__ */ new Set(["checked", "required"]);
55
61
  var MARK_TAG = {
56
62
  bold: "b",
57
63
  italic: "i",