@hejiayue/x-markdown-test 0.0.1-beta.149 → 0.0.1-beta.151
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/types/vite-plugin.d.ts +0 -4
- package/dist/vite-plugin.cjs +19 -37
- package/dist/vite-plugin.cjs.map +1 -1
- package/dist/vite-plugin.js +21 -38
- package/dist/vite-plugin.js.map +1 -1
- package/dist/x-markdown.cjs7.js +1 -1
- package/dist/x-markdown.cjs7.js.map +1 -1
- package/dist/x-markdown.cjs9.js +1 -1
- package/dist/x-markdown.cjs9.js.map +1 -1
- package/dist/x-markdown.es7.js +6 -1
- package/dist/x-markdown.es7.js.map +1 -1
- package/dist/x-markdown.es9.js +6 -1
- package/dist/x-markdown.es9.js.map +1 -1
- package/package.json +1 -1
package/dist/vite-plugin.cjs
CHANGED
|
@@ -2,17 +2,10 @@
|
|
|
2
2
|
Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
|
|
3
3
|
const node_path = require("node:path");
|
|
4
4
|
const node_fs = require("node:fs");
|
|
5
|
-
const node_url = require("node:url");
|
|
6
5
|
const module$1 = require("module");
|
|
7
|
-
var _documentCurrentScript = typeof document !== "undefined" ? document.currentScript : null;
|
|
8
|
-
const __filename$1 = node_url.fileURLToPath(typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("vite-plugin.cjs", document.baseURI).href);
|
|
9
|
-
const __dirname$1 = node_path.dirname(__filename$1);
|
|
10
6
|
function createXMarkdownVitePlugin(options = {}) {
|
|
11
|
-
const {
|
|
12
|
-
|
|
13
|
-
virtualModulesDir = "./node_modules/x-markdown-vue/virtual-modules",
|
|
14
|
-
showConsoleHints = true
|
|
15
|
-
} = options;
|
|
7
|
+
const { optionalDeps = ["mermaid", "shiki", "shiki-stream"], showConsoleHints = true } = options;
|
|
8
|
+
const VIRTUAL_PREFIX = "\0virtual:x-markdown/";
|
|
16
9
|
const virtualModuleMap = /* @__PURE__ */ new Map();
|
|
17
10
|
return {
|
|
18
11
|
name: "x-markdown-vue:vite-plugin",
|
|
@@ -23,10 +16,17 @@ function createXMarkdownVitePlugin(options = {}) {
|
|
|
23
16
|
if (virtualModuleMap.has(id)) {
|
|
24
17
|
return virtualModuleMap.get(id);
|
|
25
18
|
}
|
|
19
|
+
if (id.startsWith(VIRTUAL_PREFIX)) {
|
|
20
|
+
return id;
|
|
21
|
+
}
|
|
26
22
|
return null;
|
|
27
23
|
},
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
load(id) {
|
|
25
|
+
if (id.startsWith(VIRTUAL_PREFIX)) {
|
|
26
|
+
return "export default null;";
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
configResolved() {
|
|
30
30
|
},
|
|
31
31
|
config(config) {
|
|
32
32
|
const projectRoot = process.cwd();
|
|
@@ -55,40 +55,22 @@ function createXMarkdownVitePlugin(options = {}) {
|
|
|
55
55
|
try {
|
|
56
56
|
if (!isDeclaredInProject(dep)) {
|
|
57
57
|
isInstalled = false;
|
|
58
|
-
console.log(`[x-markdown-plugin] ${dep} NOT declared in package.json`);
|
|
59
58
|
} else {
|
|
60
59
|
projectRequire.resolve(dep);
|
|
61
60
|
isInstalled = true;
|
|
62
|
-
console.log(`[x-markdown-plugin] ${dep} is installed and declared`);
|
|
63
61
|
}
|
|
64
62
|
} catch {
|
|
65
63
|
isInstalled = false;
|
|
66
64
|
}
|
|
67
65
|
if (!isInstalled) {
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
let virtualModulePath = null;
|
|
77
|
-
for (const path of virtualModulePaths) {
|
|
78
|
-
if (node_fs.existsSync(path)) {
|
|
79
|
-
virtualModulePath = path;
|
|
80
|
-
break;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
if (virtualModulePath) {
|
|
84
|
-
optionalAliases.push({
|
|
85
|
-
find: dep,
|
|
86
|
-
replacement: virtualModulePath
|
|
87
|
-
});
|
|
88
|
-
virtualModuleMap.set(dep, virtualModulePath);
|
|
89
|
-
if (config.mode === "development") {
|
|
90
|
-
console.log(`\x1B[33m[x-markdown-vue]\x1B[0m ${dep} 未安装,使用虚拟模块: ${virtualModulePath}`);
|
|
91
|
-
}
|
|
66
|
+
const virtualId = `${VIRTUAL_PREFIX}${dep}`;
|
|
67
|
+
optionalAliases.push({
|
|
68
|
+
find: dep,
|
|
69
|
+
replacement: virtualId
|
|
70
|
+
});
|
|
71
|
+
virtualModuleMap.set(dep, virtualId);
|
|
72
|
+
if (config.mode === "development") {
|
|
73
|
+
console.log(`\x1B[33m[x-markdown-vue]\x1B[0m ${dep} 未安装,使用虚拟模块`);
|
|
92
74
|
}
|
|
93
75
|
}
|
|
94
76
|
}
|
package/dist/vite-plugin.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite-plugin.cjs","sources":["../src/vite-plugin.ts"],"sourcesContent":["import { resolve
|
|
1
|
+
{"version":3,"file":"vite-plugin.cjs","sources":["../src/vite-plugin.ts"],"sourcesContent":["import { resolve } from 'node:path'\nimport { readFileSync } from 'node:fs'\nimport { createRequire } from 'module'\nimport type { Plugin, UserConfig } from 'vite'\n\n/**\n * x-markdown Vite 插件配置选项\n */\nexport interface XMarkdownVitePluginOptions {\n /**\n * 可选依赖列表(默认:['mermaid', 'shiki', 'shiki-stream'])\n */\n optionalDeps?: string[]\n /**\n * 是否显示控制台提示(默认:true)\n */\n showConsoleHints?: boolean\n}\n\n/**\n * x-markdown Vite 插件\n *\n * 自动处理可选依赖(shiki、shiki-stream、mermaid)的虚拟模块配置\n * 当这些依赖未安装时,自动映射到虚拟模块避免构建失败\n *\n * @param options - 插件配置选项\n * @returns Vite 插件\n */\nexport function createXMarkdownVitePlugin(options: XMarkdownVitePluginOptions = {}): Plugin {\n const { optionalDeps = ['mermaid', 'shiki', 'shiki-stream'], showConsoleHints = true } = options\n\n const VIRTUAL_PREFIX = '\\0virtual:x-markdown/'\n const virtualModuleMap = new Map<string, string>()\n\n return {\n name: 'x-markdown-vue:vite-plugin',\n enforce: 'pre', // 在其他插件之前执行\n\n // 拦截模块解析,包括动态导入\n resolveId(id) {\n // 检查是否是我们需要处理的可选依赖\n if (virtualModuleMap.has(id)) {\n return virtualModuleMap.get(id)\n }\n if (id.startsWith(VIRTUAL_PREFIX)) {\n return id\n }\n return null\n },\n\n load(id) {\n if (id.startsWith(VIRTUAL_PREFIX)) {\n return 'export default null;'\n }\n },\n\n configResolved() {\n // console.log('[x-markdown-plugin] Final aliases:', config.resolve.alias)\n },\n config(config: UserConfig) {\n // 获取项目根目录\n const projectRoot = process.cwd()\n\n // 注入全局变量,控制是否显示控制台提示\n if (!config.define) {\n config.define = {}\n }\n config.define.__X_MARKDOWN_CONSOLE_HINTS_ENABLED__ = JSON.stringify(showConsoleHints)\n\n // 动态生成可选依赖的 alias 配置\n const optionalAliases: Array<{ find: string; replacement: string }> = []\n\n // 创建 require 函数,用于解析依赖\n const projectRequire = createRequire(resolve(projectRoot, 'package.json'))\n let declaredDeps: Record<string, any> | null = null\n try {\n const pkgJsonPath = resolve(projectRoot, 'package.json')\n const pkgRaw = readFileSync(pkgJsonPath, 'utf-8')\n declaredDeps = JSON.parse(pkgRaw)\n } catch {\n declaredDeps = null\n }\n\n const isDeclaredInProject = (dep: string) => {\n if (!declaredDeps) return false\n const fields = ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies'] as const\n const declared = fields.some((field) => Boolean((declaredDeps as any)?.[field]?.[dep]))\n // console.log(`[x-markdown-plugin] Check ${dep} declared: ${declared}`)\n return declared\n }\n\n for (const dep of optionalDeps) {\n let isInstalled = false\n try {\n if (!isDeclaredInProject(dep)) {\n isInstalled = false\n // console.log(`[x-markdown-plugin] ${dep} NOT declared in package.json`)\n } else {\n projectRequire.resolve(dep)\n isInstalled = true\n // console.log(`[x-markdown-plugin] ${dep} is installed and declared`)\n }\n } catch {\n isInstalled = false\n }\n\n if (!isInstalled) {\n // 依赖未安装,使用虚拟模块\n const virtualId = `${VIRTUAL_PREFIX}${dep}`\n\n // 同时添加到 alias 和 resolveId 映射\n optionalAliases.push({\n find: dep,\n replacement: virtualId,\n })\n\n // 添加到 resolveId 映射表(用于动态导入)\n virtualModuleMap.set(dep, virtualId)\n\n // 在开发环境显示提示信息\n if (config.mode === 'development') {\n console.log(`\\x1b[33m[x-markdown-vue]\\x1b[0m ${dep} 未安装,使用虚拟模块`)\n }\n }\n }\n\n // 如果有需要添加的 alias,更新配置\n if (optionalAliases.length > 0) {\n // 保留原有的 alias 配置\n const existingAlias = config.resolve?.alias || []\n\n // 规范化现有 alias 为数组格式\n let normalizedExistingAlias: Array<any> = []\n if (Array.isArray(existingAlias)) {\n normalizedExistingAlias = existingAlias\n } else if (typeof existingAlias === 'object') {\n // 对象格式转换为数组格式\n normalizedExistingAlias = Object.entries(existingAlias).map(([find, replacement]) => ({\n find,\n replacement,\n }))\n }\n\n // 合并 alias 配置\n config.resolve = {\n ...config.resolve,\n alias: [...optionalAliases, ...normalizedExistingAlias],\n }\n }\n\n // 确保 optionalDeps 不被预构建\n const optimizeDeps = config.optimizeDeps || {}\n const excludeDeps = optimizeDeps.exclude || []\n const includeDeps = optimizeDeps.include || []\n\n // 移除未安装的依赖从 include\n const filteredInclude = includeDeps.filter((dep: string) => {\n try {\n if (!isDeclaredInProject(dep)) return false\n projectRequire.resolve(dep)\n return true\n } catch {\n return false\n }\n })\n\n // 添加未安装的依赖到 exclude\n const newExclude = [\n ...optionalDeps.filter((dep) => {\n try {\n if (!isDeclaredInProject(dep)) return true\n projectRequire.resolve(dep)\n return false\n } catch {\n return true\n }\n }),\n ...excludeDeps,\n ]\n\n config.optimizeDeps = {\n ...optimizeDeps,\n include: filteredInclude,\n exclude: [...new Set(newExclude)],\n }\n },\n }\n}\n\n// 默认导出,方便使用\nexport default createXMarkdownVitePlugin\n\n/**\n * 为了兼容性,也提供一个具名导出\n */\nexport { createXMarkdownVitePlugin as xMarkdownVitePlugin }\n"],"names":["createRequire","resolve","readFileSync"],"mappings":"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"}
|
package/dist/vite-plugin.js
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { readFileSync
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
4
3
|
import { createRequire } from "module";
|
|
5
|
-
const __filename$1 = fileURLToPath(import.meta.url);
|
|
6
|
-
const __dirname$1 = dirname(__filename$1);
|
|
7
4
|
function createXMarkdownVitePlugin(options = {}) {
|
|
8
|
-
const {
|
|
9
|
-
|
|
10
|
-
virtualModulesDir = "./node_modules/x-markdown-vue/virtual-modules",
|
|
11
|
-
showConsoleHints = true
|
|
12
|
-
} = options;
|
|
5
|
+
const { optionalDeps = ["mermaid", "shiki", "shiki-stream"], showConsoleHints = true } = options;
|
|
6
|
+
const VIRTUAL_PREFIX = "\0virtual:x-markdown/";
|
|
13
7
|
const virtualModuleMap = /* @__PURE__ */ new Map();
|
|
14
8
|
return {
|
|
15
9
|
name: "x-markdown-vue:vite-plugin",
|
|
@@ -20,10 +14,17 @@ function createXMarkdownVitePlugin(options = {}) {
|
|
|
20
14
|
if (virtualModuleMap.has(id)) {
|
|
21
15
|
return virtualModuleMap.get(id);
|
|
22
16
|
}
|
|
17
|
+
if (id.startsWith(VIRTUAL_PREFIX)) {
|
|
18
|
+
return id;
|
|
19
|
+
}
|
|
23
20
|
return null;
|
|
24
21
|
},
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
load(id) {
|
|
23
|
+
if (id.startsWith(VIRTUAL_PREFIX)) {
|
|
24
|
+
return "export default null;";
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
configResolved() {
|
|
27
28
|
},
|
|
28
29
|
config(config) {
|
|
29
30
|
const projectRoot = process.cwd();
|
|
@@ -52,40 +53,22 @@ function createXMarkdownVitePlugin(options = {}) {
|
|
|
52
53
|
try {
|
|
53
54
|
if (!isDeclaredInProject(dep)) {
|
|
54
55
|
isInstalled = false;
|
|
55
|
-
console.log(`[x-markdown-plugin] ${dep} NOT declared in package.json`);
|
|
56
56
|
} else {
|
|
57
57
|
projectRequire.resolve(dep);
|
|
58
58
|
isInstalled = true;
|
|
59
|
-
console.log(`[x-markdown-plugin] ${dep} is installed and declared`);
|
|
60
59
|
}
|
|
61
60
|
} catch {
|
|
62
61
|
isInstalled = false;
|
|
63
62
|
}
|
|
64
63
|
if (!isInstalled) {
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
let virtualModulePath = null;
|
|
74
|
-
for (const path of virtualModulePaths) {
|
|
75
|
-
if (existsSync(path)) {
|
|
76
|
-
virtualModulePath = path;
|
|
77
|
-
break;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
if (virtualModulePath) {
|
|
81
|
-
optionalAliases.push({
|
|
82
|
-
find: dep,
|
|
83
|
-
replacement: virtualModulePath
|
|
84
|
-
});
|
|
85
|
-
virtualModuleMap.set(dep, virtualModulePath);
|
|
86
|
-
if (config.mode === "development") {
|
|
87
|
-
console.log(`\x1B[33m[x-markdown-vue]\x1B[0m ${dep} 未安装,使用虚拟模块: ${virtualModulePath}`);
|
|
88
|
-
}
|
|
64
|
+
const virtualId = `${VIRTUAL_PREFIX}${dep}`;
|
|
65
|
+
optionalAliases.push({
|
|
66
|
+
find: dep,
|
|
67
|
+
replacement: virtualId
|
|
68
|
+
});
|
|
69
|
+
virtualModuleMap.set(dep, virtualId);
|
|
70
|
+
if (config.mode === "development") {
|
|
71
|
+
console.log(`\x1B[33m[x-markdown-vue]\x1B[0m ${dep} 未安装,使用虚拟模块`);
|
|
89
72
|
}
|
|
90
73
|
}
|
|
91
74
|
}
|
package/dist/vite-plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite-plugin.js","sources":["../src/vite-plugin.ts"],"sourcesContent":["import { resolve
|
|
1
|
+
{"version":3,"file":"vite-plugin.js","sources":["../src/vite-plugin.ts"],"sourcesContent":["import { resolve } from 'node:path'\nimport { readFileSync } from 'node:fs'\nimport { createRequire } from 'module'\nimport type { Plugin, UserConfig } from 'vite'\n\n/**\n * x-markdown Vite 插件配置选项\n */\nexport interface XMarkdownVitePluginOptions {\n /**\n * 可选依赖列表(默认:['mermaid', 'shiki', 'shiki-stream'])\n */\n optionalDeps?: string[]\n /**\n * 是否显示控制台提示(默认:true)\n */\n showConsoleHints?: boolean\n}\n\n/**\n * x-markdown Vite 插件\n *\n * 自动处理可选依赖(shiki、shiki-stream、mermaid)的虚拟模块配置\n * 当这些依赖未安装时,自动映射到虚拟模块避免构建失败\n *\n * @param options - 插件配置选项\n * @returns Vite 插件\n */\nexport function createXMarkdownVitePlugin(options: XMarkdownVitePluginOptions = {}): Plugin {\n const { optionalDeps = ['mermaid', 'shiki', 'shiki-stream'], showConsoleHints = true } = options\n\n const VIRTUAL_PREFIX = '\\0virtual:x-markdown/'\n const virtualModuleMap = new Map<string, string>()\n\n return {\n name: 'x-markdown-vue:vite-plugin',\n enforce: 'pre', // 在其他插件之前执行\n\n // 拦截模块解析,包括动态导入\n resolveId(id) {\n // 检查是否是我们需要处理的可选依赖\n if (virtualModuleMap.has(id)) {\n return virtualModuleMap.get(id)\n }\n if (id.startsWith(VIRTUAL_PREFIX)) {\n return id\n }\n return null\n },\n\n load(id) {\n if (id.startsWith(VIRTUAL_PREFIX)) {\n return 'export default null;'\n }\n },\n\n configResolved() {\n // console.log('[x-markdown-plugin] Final aliases:', config.resolve.alias)\n },\n config(config: UserConfig) {\n // 获取项目根目录\n const projectRoot = process.cwd()\n\n // 注入全局变量,控制是否显示控制台提示\n if (!config.define) {\n config.define = {}\n }\n config.define.__X_MARKDOWN_CONSOLE_HINTS_ENABLED__ = JSON.stringify(showConsoleHints)\n\n // 动态生成可选依赖的 alias 配置\n const optionalAliases: Array<{ find: string; replacement: string }> = []\n\n // 创建 require 函数,用于解析依赖\n const projectRequire = createRequire(resolve(projectRoot, 'package.json'))\n let declaredDeps: Record<string, any> | null = null\n try {\n const pkgJsonPath = resolve(projectRoot, 'package.json')\n const pkgRaw = readFileSync(pkgJsonPath, 'utf-8')\n declaredDeps = JSON.parse(pkgRaw)\n } catch {\n declaredDeps = null\n }\n\n const isDeclaredInProject = (dep: string) => {\n if (!declaredDeps) return false\n const fields = ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies'] as const\n const declared = fields.some((field) => Boolean((declaredDeps as any)?.[field]?.[dep]))\n // console.log(`[x-markdown-plugin] Check ${dep} declared: ${declared}`)\n return declared\n }\n\n for (const dep of optionalDeps) {\n let isInstalled = false\n try {\n if (!isDeclaredInProject(dep)) {\n isInstalled = false\n // console.log(`[x-markdown-plugin] ${dep} NOT declared in package.json`)\n } else {\n projectRequire.resolve(dep)\n isInstalled = true\n // console.log(`[x-markdown-plugin] ${dep} is installed and declared`)\n }\n } catch {\n isInstalled = false\n }\n\n if (!isInstalled) {\n // 依赖未安装,使用虚拟模块\n const virtualId = `${VIRTUAL_PREFIX}${dep}`\n\n // 同时添加到 alias 和 resolveId 映射\n optionalAliases.push({\n find: dep,\n replacement: virtualId,\n })\n\n // 添加到 resolveId 映射表(用于动态导入)\n virtualModuleMap.set(dep, virtualId)\n\n // 在开发环境显示提示信息\n if (config.mode === 'development') {\n console.log(`\\x1b[33m[x-markdown-vue]\\x1b[0m ${dep} 未安装,使用虚拟模块`)\n }\n }\n }\n\n // 如果有需要添加的 alias,更新配置\n if (optionalAliases.length > 0) {\n // 保留原有的 alias 配置\n const existingAlias = config.resolve?.alias || []\n\n // 规范化现有 alias 为数组格式\n let normalizedExistingAlias: Array<any> = []\n if (Array.isArray(existingAlias)) {\n normalizedExistingAlias = existingAlias\n } else if (typeof existingAlias === 'object') {\n // 对象格式转换为数组格式\n normalizedExistingAlias = Object.entries(existingAlias).map(([find, replacement]) => ({\n find,\n replacement,\n }))\n }\n\n // 合并 alias 配置\n config.resolve = {\n ...config.resolve,\n alias: [...optionalAliases, ...normalizedExistingAlias],\n }\n }\n\n // 确保 optionalDeps 不被预构建\n const optimizeDeps = config.optimizeDeps || {}\n const excludeDeps = optimizeDeps.exclude || []\n const includeDeps = optimizeDeps.include || []\n\n // 移除未安装的依赖从 include\n const filteredInclude = includeDeps.filter((dep: string) => {\n try {\n if (!isDeclaredInProject(dep)) return false\n projectRequire.resolve(dep)\n return true\n } catch {\n return false\n }\n })\n\n // 添加未安装的依赖到 exclude\n const newExclude = [\n ...optionalDeps.filter((dep) => {\n try {\n if (!isDeclaredInProject(dep)) return true\n projectRequire.resolve(dep)\n return false\n } catch {\n return true\n }\n }),\n ...excludeDeps,\n ]\n\n config.optimizeDeps = {\n ...optimizeDeps,\n include: filteredInclude,\n exclude: [...new Set(newExclude)],\n }\n },\n }\n}\n\n// 默认导出,方便使用\nexport default createXMarkdownVitePlugin\n\n/**\n * 为了兼容性,也提供一个具名导出\n */\nexport { createXMarkdownVitePlugin as xMarkdownVitePlugin }\n"],"names":[],"mappings":"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"}
|
package/dist/x-markdown.cjs7.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.create,Object.defineProperty,Object.getOwnPropertyDescriptor,Object.getOwnPropertyNames,Object.getPrototypeOf,Object.prototype.hasOwnProperty,Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("vue"),t=()=>"boolean"!=typeof __X_MARKDOWN_CONSOLE_HINTS_ENABLED__||__X_MARKDOWN_CONSOLE_HINTS_ENABLED__;let l=null,n=null,a=!1,o=!1;const
|
|
1
|
+
"use strict";Object.create,Object.defineProperty,Object.getOwnPropertyDescriptor,Object.getOwnPropertyNames,Object.getPrototypeOf,Object.prototype.hasOwnProperty,Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("vue"),t=()=>"boolean"!=typeof __X_MARKDOWN_CONSOLE_HINTS_ENABLED__||__X_MARKDOWN_CONSOLE_HINTS_ENABLED__;let l=null,n=null,a=!1,o=!1;const r=new Map,c=()=>{o||t()&&(o=!0,console.log("%c[x-markdown]%c AI 流式可选: %cpnpm add shiki-stream%c (需先装 shiki)","font-weight: bold; color: #0066cc;","color: #666;","color: #00aa00; font-family: monospace;","color: #999;"))},i=e=>{if(!e.length)return[[]];if(Array.isArray(e[0]))return e;const t=[[]];let l=t[0];const n=()=>{l=[],t.push(l)};return e.forEach(e=>{const t=e.content??"";if("\n"===t)return void n();if(!t.includes("\n"))return void l.push(e);const a=t.split("\n");a.forEach((t,o)=>{t&&l.push({...e,content:t}),o<a.length-1&&n()})}),0===t.length?[[]]:t};exports.useHighlight=function(o,s){const u=e.ref(),p=e.ref(!1),h=e.ref(null);let m=null,y="",g=null,v="",d="";const f=e.computed(()=>(e.isRef(s.theme)?s.theme.value:s.theme)||"slack-dark"),S=e.computed(()=>e.toValue(s.language)||"text"),w=e.computed(()=>u.value?.lines||[[]]),k=e.computed(()=>u.value?.preStyle),R=async(e,t=!1)=>{if(m){t&&(m.clear(),y="");const n=!t&&e.startsWith(y);let a=e;if(n?a=e.slice(y.length):t||m.clear(),y=e,!a){const e=[...m.tokensStable,...m.tokensUnstable];return void(u.value={colorReplacements:s.colorReplacements,lines:e.length?i(e):[[]],preStyle:u.value?.preStyle})}try{await m.enqueue(a);const e=[...m.tokensStable,...m.tokensUnstable];u.value={colorReplacements:s.colorReplacements,lines:i(e),preStyle:u.value?.preStyle}}catch(l){h.value=l}}else if(g)try{const t=v||"plaintext",l=f.value,n=g.codeToTokens(e,{lang:t,theme:l}),a=n.tokens||n;u.value={colorReplacements:s.colorReplacements,lines:i(a),preStyle:u.value?.preStyle}}catch(l){u.value={colorReplacements:s.colorReplacements,lines:[[{content:e}]],preStyle:u.value?.preStyle}}},b=async()=>{p.value=!0,h.value=null;let e=S.value;const i=f.value,w=`highlighter-${i}`;try{const p=await(async()=>(l||(l=(async()=>{try{const e=await import("shiki");return e&&null===e.default?null:e&&(e.createHighlighter||e.getHighlighter)?e:null}catch{return null}})()),l))();if(!p)return u.value={colorReplacements:s.colorReplacements,lines:[[{content:o.value}]],preStyle:void 0},a||t()&&(a=!0,console.log("%c[x-markdown]%c 需安装 shiki: %cpnpm add shiki%c","font-weight: bold; color: #0066cc;","color: #666;","color: #00aa00; font-family: monospace;","color: #999;")),void c();r.has(w)?g=r.get(w):(g=await p.createHighlighter({themes:[i],langs:[]}),r.set(w,g)),d=e;try{await g.loadLanguage(e),v=e}catch{e="plaintext",v="plaintext"}const h=await(async()=>(n||(n=(async()=>{try{const e=await import("shiki-stream");return e&&null===e.default?null:e&&e.ShikiStreamTokenizer?e:null}catch{return null}})()),n))();h?m=new h.ShikiStreamTokenizer({highlighter:g,lang:e,theme:i}):c(),y="";const f=g.getTheme(i),S=((e,t)=>{if(e||t)return{backgroundColor:e,color:t}})(f?.bg,f?.fg);o.value?(await R(o.value,!0),u.value&&(u.value.preStyle=S)):u.value={colorReplacements:s.colorReplacements,lines:[[]],preStyle:S}}catch(k){u.value={colorReplacements:s.colorReplacements,lines:[[{content:o.value}]],preStyle:void 0}}finally{p.value=!1}};return e.watch(()=>[S.value,f.value],async([e])=>{const t=e;if(g&&"plaintext"===v&&t!==d&&"plaintext"!==t)try{return await g.loadLanguage(t),void b()}catch{return void(d=t)}b()},{immediate:!0}),e.watch(o,async e=>{const t=S.value;if(g&&"plaintext"===v&&t!==d&&"plaintext"!==t)try{return await g.loadLanguage(t),void(await b())}catch{d=t}m||g?R(e):u.value={colorReplacements:s.colorReplacements,lines:[[{content:e}]],preStyle:u.value?.preStyle}}),e.onUnmounted(()=>{m?.clear(),m=null,y=""}),{streaming:u,lines:w,preStyle:k,isLoading:p,error:h}};
|
|
2
2
|
//# sourceMappingURL=x-markdown.cjs7.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"x-markdown.cjs7.js","sources":["../src/hooks/useHighlight.ts"],"sourcesContent":["import {\r\n ref,\r\n watch,\r\n onUnmounted,\r\n computed,\r\n isRef,\r\n toValue,\r\n type Ref,\r\n type MaybeRefOrGetter,\r\n type CSSProperties,\r\n} from 'vue'\r\n\r\n// 获取是否启用控制台提示的辅助函数\r\nconst consoleHintsEnabled = () => {\r\n if (typeof __X_MARKDOWN_CONSOLE_HINTS_ENABLED__ === 'boolean') {\r\n return __X_MARKDOWN_CONSOLE_HINTS_ENABLED__\r\n }\r\n return true // 默认启用\r\n}\r\n\r\nexport interface HighlightToken {\r\n content?: string\r\n color?: string\r\n fontStyle?: 'italic' | null\r\n fontWeight?: 'normal' | 'bold' | null\r\n htmlStyle?: Record<string, string>\r\n}\r\n\r\ninterface StreamingHighlightResult {\r\n colorReplacements?: Record<string, string>\r\n lines: HighlightToken[][]\r\n preStyle?: CSSProperties\r\n}\r\n\r\ninterface UseHighlightOptions {\r\n language: MaybeRefOrGetter<string>\r\n theme?: string | Ref<string>\r\n colorReplacements?: Record<string, string>\r\n}\r\n\r\nlet shikiModulePromise: Promise<any | null> | null = null\r\nlet shikiStreamModulePromise: Promise<any | null> | null = null\r\nlet hasShownShikiHint = false\r\nlet hasShownShikiStreamHint = false\r\n\r\n// 全局 highlighter 缓存,避免重复创建实例\r\nconst highlighterCache = new Map<string, any>()\r\nconst getHighlighterCacheKey = (theme: string) => `highlighter-${theme}`\r\n\r\nconst showShikiHint = () => {\r\n if (hasShownShikiHint) return\r\n if (!consoleHintsEnabled()) return\r\n\r\n hasShownShikiHint = true\r\n\r\n console.log(\r\n '%c[x-markdown]%c 需安装 shiki: %cpnpm add shiki%c',\r\n 'font-weight: bold; color: #0066cc;',\r\n 'color: #666;',\r\n 'color: #00aa00; font-family: monospace;',\r\n 'color: #999;',\r\n )\r\n}\r\n\r\nconst showShikiStreamHint = () => {\r\n if (hasShownShikiStreamHint) return\r\n if (!consoleHintsEnabled()) return\r\n\r\n hasShownShikiStreamHint = true\r\n\r\n console.log(\r\n '%c[x-markdown]%c AI 流式可选: %cpnpm add shiki-stream%c (需先装 shiki)',\r\n 'font-weight: bold; color: #0066cc;',\r\n 'color: #666;',\r\n 'color: #00aa00; font-family: monospace;',\r\n 'color: #999;',\r\n )\r\n}\r\n\r\nconst loadShiki = async () => {\r\n if (!shikiModulePromise) {\r\n shikiModulePromise = (async () => {\r\n try {\r\n const mod = await import('shiki')\r\n // 检查是否是虚拟模块(虚拟模块返回 { default: null })\r\n if (mod && (mod as any).default === null) {\r\n return null\r\n }\r\n return mod\r\n } catch {\r\n // 静默失败,返回 null\r\n return null\r\n }\r\n })()\r\n }\r\n return shikiModulePromise\r\n}\r\n\r\nconst loadShikiStream = async () => {\r\n if (!shikiStreamModulePromise) {\r\n shikiStreamModulePromise = (async () => {\r\n try {\r\n const mod = await import('shiki-stream')\r\n // 检查是否是虚拟模块(虚拟模块返回 { default: null })\r\n if (mod && (mod as any).default === null) {\r\n return null\r\n }\r\n return mod\r\n } catch {\r\n // 静默失败,返回 null\r\n return null\r\n }\r\n })()\r\n }\r\n return shikiStreamModulePromise\r\n}\r\n\r\nconst tokensToLineTokens = (tokens: HighlightToken[] | HighlightToken[][]): HighlightToken[][] => {\r\n if (!tokens.length) return [[]]\r\n\r\n // 检查是否已经是二维数组(shiki 3.x codeToTokens 的返回格式)\r\n if (Array.isArray(tokens[0])) {\r\n return tokens as HighlightToken[][]\r\n }\r\n\r\n // 处理一维数组(shiki-stream 的格式)\r\n const lines: HighlightToken[][] = [[]]\r\n let currentLine = lines[0]\r\n\r\n const startNewLine = () => {\r\n currentLine = []\r\n lines.push(currentLine)\r\n }\r\n\r\n ;(tokens as HighlightToken[]).forEach((token) => {\r\n const content = token.content ?? ''\r\n\r\n if (content === '\\n') {\r\n startNewLine()\r\n return\r\n }\r\n\r\n if (!content.includes('\\n')) {\r\n currentLine.push(token)\r\n return\r\n }\r\n\r\n const segments = content.split('\\n')\r\n segments.forEach((segment: string, index: number) => {\r\n if (segment) {\r\n currentLine.push({\r\n ...token,\r\n content: segment,\r\n })\r\n }\r\n\r\n if (index < segments.length - 1) {\r\n startNewLine()\r\n }\r\n })\r\n })\r\n\r\n return lines.length === 0 ? [[]] : lines\r\n}\r\n\r\nconst createPreStyle = (bg?: string, fg?: string): CSSProperties | undefined => {\r\n if (!bg && !fg) return undefined\r\n return {\r\n backgroundColor: bg,\r\n color: fg,\r\n }\r\n}\r\n\r\nexport function useHighlight(text: Ref<string>, options: UseHighlightOptions) {\r\n const streaming = ref<StreamingHighlightResult>()\r\n const isLoading = ref(false)\r\n const error = ref<Error | null>(null)\r\n\r\n let tokenizer: any | null = null\r\n let previousText = ''\r\n let highlighter: any | null = null\r\n let currentUsedLang = ''\r\n let lastRequestedLang = ''\r\n\r\n const effectiveTheme = computed(() => {\r\n const theme = isRef(options.theme) ? options.theme.value : options.theme\r\n return theme || 'slack-dark'\r\n })\r\n\r\n const effectiveLanguage = computed(() => {\r\n return toValue(options.language) || 'text'\r\n })\r\n\r\n const lines = computed(() => streaming.value?.lines || [[]])\r\n const preStyle = computed(() => streaming.value?.preStyle)\r\n\r\n const updateTokens = async (nextText: string, forceReset = false) => {\r\n // 当有 tokenizer 时使用流式处理\r\n if (tokenizer) {\r\n if (forceReset) {\r\n tokenizer.clear()\r\n previousText = ''\r\n }\r\n\r\n const canAppend = !forceReset && nextText.startsWith(previousText)\r\n let chunk = nextText\r\n\r\n if (canAppend) {\r\n chunk = nextText.slice(previousText.length)\r\n } else if (!forceReset) {\r\n tokenizer.clear()\r\n }\r\n\r\n previousText = nextText\r\n\r\n if (!chunk) {\r\n const mergedTokens = [...tokenizer.tokensStable, ...tokenizer.tokensUnstable]\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: mergedTokens.length ? tokensToLineTokens(mergedTokens) : [[]],\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n return\r\n }\r\n\r\n try {\r\n await tokenizer.enqueue(chunk)\r\n\r\n const mergedTokens = [...tokenizer.tokensStable, ...tokenizer.tokensUnstable]\r\n\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: tokensToLineTokens(mergedTokens),\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n } catch (err) {\r\n // 静默失败,降级为纯文本\r\n error.value = err as Error\r\n }\r\n } else if (highlighter) {\r\n // 当没有 tokenizer 但有 highlighter 时,使用非流式方式高亮\r\n // 这发生在 shiki 可用但 shiki-stream 不可用时\r\n try {\r\n const currentLang = currentUsedLang || 'plaintext'\r\n const currentTheme = effectiveTheme.value\r\n const tokens = highlighter.codeToTokens(nextText, {\r\n lang: currentLang,\r\n theme: currentTheme,\r\n })\r\n\r\n // shiki 3.x 的 codeToTokens 返回一个对象,包含 tokens、fg、bg 等属性\r\n // 需要从返回对象中提取 tokens 数组\r\n const tokensArray = (tokens as any).tokens || tokens\r\n\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: tokensToLineTokens(tokensArray),\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n } catch (err) {\r\n // 静默降级为纯文本\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[{ content: nextText }]],\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n }\r\n }\r\n }\r\n\r\n const initHighlighter = async () => {\r\n isLoading.value = true\r\n error.value = null\r\n\r\n let currentLang = effectiveLanguage.value\r\n const currentTheme = effectiveTheme.value\r\n const cacheKey = getHighlighterCacheKey(currentTheme)\r\n\r\n try {\r\n const mod = await loadShiki()\r\n if (!mod) {\r\n // shiki 完全不可用,降级为纯文本\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[{ content: text.value }]],\r\n preStyle: undefined,\r\n }\r\n showShikiHint()\r\n showShikiStreamHint()\r\n return\r\n }\r\n\r\n // 检查缓存,如果已有相同主题的 highlighter,直接复用\r\n // 这避免了 Shiki 单例警告并提高了性能\r\n console.log('%c[x-markdown] Shiki loaded successfully', 'background: #0b0; color: #fff; padding: 2px 4px;')\r\n if (!highlighterCache.has(cacheKey)) {\r\n highlighter = await mod.createHighlighter({\r\n themes: [currentTheme],\r\n langs: [], // 将动态加载语言\r\n })\r\n highlighterCache.set(cacheKey, highlighter)\r\n } else {\r\n highlighter = highlighterCache.get(cacheKey)\r\n }\r\n\r\n lastRequestedLang = currentLang\r\n\r\n // 尝试加载请求的语言,失败则降级为纯文本\r\n try {\r\n await highlighter.loadLanguage(currentLang as any)\r\n currentUsedLang = currentLang\r\n } catch {\r\n currentLang = 'plaintext'\r\n currentUsedLang = 'plaintext'\r\n }\r\n\r\n // 动态加载 shiki-stream\r\n const shikiStreamMod = await loadShikiStream()\r\n if (shikiStreamMod) {\r\n tokenizer = new shikiStreamMod.ShikiStreamTokenizer({\r\n highlighter: highlighter,\r\n lang: currentLang,\r\n theme: currentTheme,\r\n })\r\n } else {\r\n // shiki 可用但 shiki-stream 不可用\r\n showShikiStreamHint()\r\n }\r\n\r\n previousText = ''\r\n\r\n const themeInfo = highlighter.getTheme(currentTheme)\r\n const preStyleValue = createPreStyle(themeInfo?.bg, themeInfo?.fg)\r\n\r\n if (text.value) {\r\n await updateTokens(text.value, true)\r\n if (streaming.value) {\r\n streaming.value.preStyle = preStyleValue\r\n }\r\n } else {\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[]],\r\n preStyle: preStyleValue,\r\n }\r\n }\r\n } catch (err) {\r\n // 静默降级\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[{ content: text.value }]],\r\n preStyle: undefined,\r\n }\r\n } finally {\r\n isLoading.value = false\r\n }\r\n }\r\n\r\n watch(\r\n () => [effectiveLanguage.value, effectiveTheme.value],\r\n async ([newLang]) => {\r\n const requestedLang = newLang as string\r\n\r\n if (\r\n highlighter &&\r\n currentUsedLang === 'plaintext' &&\r\n requestedLang !== lastRequestedLang &&\r\n requestedLang !== 'plaintext'\r\n ) {\r\n try {\r\n await highlighter.loadLanguage(requestedLang as any)\r\n initHighlighter()\r\n return\r\n } catch {\r\n lastRequestedLang = requestedLang\r\n return\r\n }\r\n }\r\n\r\n initHighlighter()\r\n },\r\n { immediate: true },\r\n )\r\n\r\n watch(text, async (newText) => {\r\n const requestedLang = effectiveLanguage.value\r\n if (\r\n highlighter &&\r\n currentUsedLang === 'plaintext' &&\r\n requestedLang !== lastRequestedLang &&\r\n requestedLang !== 'plaintext'\r\n ) {\r\n try {\r\n await highlighter.loadLanguage(requestedLang as any)\r\n await initHighlighter()\r\n return\r\n } catch {\r\n lastRequestedLang = requestedLang\r\n }\r\n }\r\n\r\n if (tokenizer || highlighter) {\r\n // 当有 tokenizer 或 highlighter 时都调用 updateTokens\r\n // updateTokens 内部会处理两种情况\r\n updateTokens(newText)\r\n } else {\r\n // 两者都没有时降级为纯文本\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[{ content: newText }]],\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n }\r\n })\r\n\r\n onUnmounted(() => {\r\n tokenizer?.clear()\r\n tokenizer = null\r\n previousText = ''\r\n })\r\n\r\n return {\r\n streaming,\r\n lines,\r\n preStyle,\r\n isLoading,\r\n error,\r\n }\r\n}\r\n"],"names":["consoleHintsEnabled","__X_MARKDOWN_CONSOLE_HINTS_ENABLED__","shikiModulePromise","shikiStreamModulePromise","hasShownShikiHint","hasShownShikiStreamHint","highlighterCache","Map","showShikiStreamHint","console","log","tokensToLineTokens","tokens","length","Array","isArray","lines","currentLine","startNewLine","push","forEach","token","content","includes","segments","split","segment","index","text","options","streaming","ref","isLoading","error","tokenizer","previousText","highlighter","currentUsedLang","lastRequestedLang","effectiveTheme","computed","isRef","theme","value","effectiveLanguage","toValue","language","preStyle","updateTokens","async","nextText","forceReset","clear","canAppend","startsWith","chunk","slice","mergedTokens","tokensStable","tokensUnstable","colorReplacements","enqueue","err","currentLang","currentTheme","codeToTokens","lang","tokensArray","initHighlighter","cacheKey","mod","import","default","loadShiki","has","get","createHighlighter","themes","langs","set","loadLanguage","shikiStreamMod","loadShikiStream","ShikiStreamTokenizer","themeInfo","getTheme","preStyleValue","bg","fg","backgroundColor","color","createPreStyle","watch","newLang","requestedLang","immediate","newText","onUnmounted"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"x-markdown.cjs7.js","sources":["../src/hooks/useHighlight.ts"],"sourcesContent":["import {\r\n ref,\r\n watch,\r\n onUnmounted,\r\n computed,\r\n isRef,\r\n toValue,\r\n type Ref,\r\n type MaybeRefOrGetter,\r\n type CSSProperties,\r\n} from 'vue'\r\n\r\n// 获取是否启用控制台提示的辅助函数\r\nconst consoleHintsEnabled = () => {\r\n if (typeof __X_MARKDOWN_CONSOLE_HINTS_ENABLED__ === 'boolean') {\r\n return __X_MARKDOWN_CONSOLE_HINTS_ENABLED__\r\n }\r\n return true // 默认启用\r\n}\r\n\r\nexport interface HighlightToken {\r\n content?: string\r\n color?: string\r\n fontStyle?: 'italic' | null\r\n fontWeight?: 'normal' | 'bold' | null\r\n htmlStyle?: Record<string, string>\r\n}\r\n\r\ninterface StreamingHighlightResult {\r\n colorReplacements?: Record<string, string>\r\n lines: HighlightToken[][]\r\n preStyle?: CSSProperties\r\n}\r\n\r\ninterface UseHighlightOptions {\r\n language: MaybeRefOrGetter<string>\r\n theme?: string | Ref<string>\r\n colorReplacements?: Record<string, string>\r\n}\r\n\r\nlet shikiModulePromise: Promise<any | null> | null = null\r\nlet shikiStreamModulePromise: Promise<any | null> | null = null\r\nlet hasShownShikiHint = false\r\nlet hasShownShikiStreamHint = false\r\n\r\n// 全局 highlighter 缓存,避免重复创建实例\r\nconst highlighterCache = new Map<string, any>()\r\nconst getHighlighterCacheKey = (theme: string) => `highlighter-${theme}`\r\n\r\nconst showShikiHint = () => {\r\n if (hasShownShikiHint) return\r\n if (!consoleHintsEnabled()) return\r\n\r\n hasShownShikiHint = true\r\n\r\n console.log(\r\n '%c[x-markdown]%c 需安装 shiki: %cpnpm add shiki%c',\r\n 'font-weight: bold; color: #0066cc;',\r\n 'color: #666;',\r\n 'color: #00aa00; font-family: monospace;',\r\n 'color: #999;',\r\n )\r\n}\r\n\r\nconst showShikiStreamHint = () => {\r\n if (hasShownShikiStreamHint) return\r\n if (!consoleHintsEnabled()) return\r\n\r\n hasShownShikiStreamHint = true\r\n\r\n console.log(\r\n '%c[x-markdown]%c AI 流式可选: %cpnpm add shiki-stream%c (需先装 shiki)',\r\n 'font-weight: bold; color: #0066cc;',\r\n 'color: #666;',\r\n 'color: #00aa00; font-family: monospace;',\r\n 'color: #999;',\r\n )\r\n}\r\n\r\nconst loadShiki = async () => {\r\n if (!shikiModulePromise) {\r\n shikiModulePromise = (async () => {\r\n try {\r\n const mod = await import('shiki')\r\n // 检查是否是虚拟模块(虚拟模块返回 { default: null })\r\n if (mod && (mod as any).default === null) {\r\n return null\r\n }\r\n // 检查必要的方法是否存在,确保模块可用\r\n if (!mod || !(mod.createHighlighter || (mod as any).getHighlighter)) {\r\n return null\r\n }\r\n return mod\r\n } catch {\r\n // 静默失败,返回 null\r\n return null\r\n }\r\n })()\r\n }\r\n return shikiModulePromise\r\n}\r\n\r\nconst loadShikiStream = async () => {\r\n if (!shikiStreamModulePromise) {\r\n shikiStreamModulePromise = (async () => {\r\n try {\r\n const mod = await import('shiki-stream')\r\n // 检查是否是虚拟模块(虚拟模块返回 { default: null })\r\n if (mod && (mod as any).default === null) {\r\n return null\r\n }\r\n // 检查必要的方法是否存在\r\n if (!mod || !(mod as any).ShikiStreamTokenizer) {\r\n return null\r\n }\r\n return mod\r\n } catch {\r\n // 静默失败,返回 null\r\n return null\r\n }\r\n })()\r\n }\r\n return shikiStreamModulePromise\r\n}\r\n\r\nconst tokensToLineTokens = (tokens: HighlightToken[] | HighlightToken[][]): HighlightToken[][] => {\r\n if (!tokens.length) return [[]]\r\n\r\n // 检查是否已经是二维数组(shiki 3.x codeToTokens 的返回格式)\r\n if (Array.isArray(tokens[0])) {\r\n return tokens as HighlightToken[][]\r\n }\r\n\r\n // 处理一维数组(shiki-stream 的格式)\r\n const lines: HighlightToken[][] = [[]]\r\n let currentLine = lines[0]\r\n\r\n const startNewLine = () => {\r\n currentLine = []\r\n lines.push(currentLine)\r\n }\r\n\r\n ;(tokens as HighlightToken[]).forEach((token) => {\r\n const content = token.content ?? ''\r\n\r\n if (content === '\\n') {\r\n startNewLine()\r\n return\r\n }\r\n\r\n if (!content.includes('\\n')) {\r\n currentLine.push(token)\r\n return\r\n }\r\n\r\n const segments = content.split('\\n')\r\n segments.forEach((segment: string, index: number) => {\r\n if (segment) {\r\n currentLine.push({\r\n ...token,\r\n content: segment,\r\n })\r\n }\r\n\r\n if (index < segments.length - 1) {\r\n startNewLine()\r\n }\r\n })\r\n })\r\n\r\n return lines.length === 0 ? [[]] : lines\r\n}\r\n\r\nconst createPreStyle = (bg?: string, fg?: string): CSSProperties | undefined => {\r\n if (!bg && !fg) return undefined\r\n return {\r\n backgroundColor: bg,\r\n color: fg,\r\n }\r\n}\r\n\r\nexport function useHighlight(text: Ref<string>, options: UseHighlightOptions) {\r\n const streaming = ref<StreamingHighlightResult>()\r\n const isLoading = ref(false)\r\n const error = ref<Error | null>(null)\r\n\r\n let tokenizer: any | null = null\r\n let previousText = ''\r\n let highlighter: any | null = null\r\n let currentUsedLang = ''\r\n let lastRequestedLang = ''\r\n\r\n const effectiveTheme = computed(() => {\r\n const theme = isRef(options.theme) ? options.theme.value : options.theme\r\n return theme || 'slack-dark'\r\n })\r\n\r\n const effectiveLanguage = computed(() => {\r\n return toValue(options.language) || 'text'\r\n })\r\n\r\n const lines = computed(() => streaming.value?.lines || [[]])\r\n const preStyle = computed(() => streaming.value?.preStyle)\r\n\r\n const updateTokens = async (nextText: string, forceReset = false) => {\r\n // 当有 tokenizer 时使用流式处理\r\n if (tokenizer) {\r\n if (forceReset) {\r\n tokenizer.clear()\r\n previousText = ''\r\n }\r\n\r\n const canAppend = !forceReset && nextText.startsWith(previousText)\r\n let chunk = nextText\r\n\r\n if (canAppend) {\r\n chunk = nextText.slice(previousText.length)\r\n } else if (!forceReset) {\r\n tokenizer.clear()\r\n }\r\n\r\n previousText = nextText\r\n\r\n if (!chunk) {\r\n const mergedTokens = [...tokenizer.tokensStable, ...tokenizer.tokensUnstable]\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: mergedTokens.length ? tokensToLineTokens(mergedTokens) : [[]],\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n return\r\n }\r\n\r\n try {\r\n await tokenizer.enqueue(chunk)\r\n\r\n const mergedTokens = [...tokenizer.tokensStable, ...tokenizer.tokensUnstable]\r\n\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: tokensToLineTokens(mergedTokens),\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n } catch (err) {\r\n // 静默失败,降级为纯文本\r\n error.value = err as Error\r\n }\r\n } else if (highlighter) {\r\n // 当没有 tokenizer 但有 highlighter 时,使用非流式方式高亮\r\n // 这发生在 shiki 可用但 shiki-stream 不可用时\r\n try {\r\n const currentLang = currentUsedLang || 'plaintext'\r\n const currentTheme = effectiveTheme.value\r\n const tokens = highlighter.codeToTokens(nextText, {\r\n lang: currentLang,\r\n theme: currentTheme,\r\n })\r\n\r\n // shiki 3.x 的 codeToTokens 返回一个对象,包含 tokens、fg、bg 等属性\r\n // 需要从返回对象中提取 tokens 数组\r\n const tokensArray = (tokens as any).tokens || tokens\r\n\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: tokensToLineTokens(tokensArray),\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n } catch (err) {\r\n // 静默降级为纯文本\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[{ content: nextText }]],\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n }\r\n }\r\n }\r\n\r\n const initHighlighter = async () => {\r\n isLoading.value = true\r\n error.value = null\r\n\r\n let currentLang = effectiveLanguage.value\r\n const currentTheme = effectiveTheme.value\r\n const cacheKey = getHighlighterCacheKey(currentTheme)\r\n\r\n try {\r\n const mod = await loadShiki()\r\n if (!mod) {\r\n // shiki 完全不可用,降级为纯文本\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[{ content: text.value }]],\r\n preStyle: undefined,\r\n }\r\n showShikiHint()\r\n showShikiStreamHint()\r\n return\r\n }\r\n\r\n // 检查缓存,如果已有相同主题的 highlighter,直接复用\r\n // 这避免了 Shiki 单例警告并提高了性能\r\n if (!highlighterCache.has(cacheKey)) {\r\n highlighter = await mod.createHighlighter({\r\n themes: [currentTheme],\r\n langs: [], // 将动态加载语言\r\n })\r\n highlighterCache.set(cacheKey, highlighter)\r\n } else {\r\n highlighter = highlighterCache.get(cacheKey)\r\n }\r\n\r\n lastRequestedLang = currentLang\r\n\r\n // 尝试加载请求的语言,失败则降级为纯文本\r\n try {\r\n await highlighter.loadLanguage(currentLang as any)\r\n currentUsedLang = currentLang\r\n } catch {\r\n currentLang = 'plaintext'\r\n currentUsedLang = 'plaintext'\r\n }\r\n\r\n // 动态加载 shiki-stream\r\n const shikiStreamMod = await loadShikiStream()\r\n if (shikiStreamMod) {\r\n tokenizer = new shikiStreamMod.ShikiStreamTokenizer({\r\n highlighter: highlighter,\r\n lang: currentLang,\r\n theme: currentTheme,\r\n })\r\n } else {\r\n // shiki 可用但 shiki-stream 不可用\r\n showShikiStreamHint()\r\n }\r\n\r\n previousText = ''\r\n\r\n const themeInfo = highlighter.getTheme(currentTheme)\r\n const preStyleValue = createPreStyle(themeInfo?.bg, themeInfo?.fg)\r\n\r\n if (text.value) {\r\n await updateTokens(text.value, true)\r\n if (streaming.value) {\r\n streaming.value.preStyle = preStyleValue\r\n }\r\n } else {\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[]],\r\n preStyle: preStyleValue,\r\n }\r\n }\r\n } catch (err) {\r\n // 静默降级\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[{ content: text.value }]],\r\n preStyle: undefined,\r\n }\r\n } finally {\r\n isLoading.value = false\r\n }\r\n }\r\n\r\n watch(\r\n () => [effectiveLanguage.value, effectiveTheme.value],\r\n async ([newLang]) => {\r\n const requestedLang = newLang as string\r\n\r\n if (\r\n highlighter &&\r\n currentUsedLang === 'plaintext' &&\r\n requestedLang !== lastRequestedLang &&\r\n requestedLang !== 'plaintext'\r\n ) {\r\n try {\r\n await highlighter.loadLanguage(requestedLang as any)\r\n initHighlighter()\r\n return\r\n } catch {\r\n lastRequestedLang = requestedLang\r\n return\r\n }\r\n }\r\n\r\n initHighlighter()\r\n },\r\n { immediate: true },\r\n )\r\n\r\n watch(text, async (newText) => {\r\n const requestedLang = effectiveLanguage.value\r\n if (\r\n highlighter &&\r\n currentUsedLang === 'plaintext' &&\r\n requestedLang !== lastRequestedLang &&\r\n requestedLang !== 'plaintext'\r\n ) {\r\n try {\r\n await highlighter.loadLanguage(requestedLang as any)\r\n await initHighlighter()\r\n return\r\n } catch {\r\n lastRequestedLang = requestedLang\r\n }\r\n }\r\n\r\n if (tokenizer || highlighter) {\r\n // 当有 tokenizer 或 highlighter 时都调用 updateTokens\r\n // updateTokens 内部会处理两种情况\r\n updateTokens(newText)\r\n } else {\r\n // 两者都没有时降级为纯文本\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[{ content: newText }]],\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n }\r\n })\r\n\r\n onUnmounted(() => {\r\n tokenizer?.clear()\r\n tokenizer = null\r\n previousText = ''\r\n })\r\n\r\n return {\r\n streaming,\r\n lines,\r\n preStyle,\r\n isLoading,\r\n error,\r\n }\r\n}\r\n"],"names":["consoleHintsEnabled","__X_MARKDOWN_CONSOLE_HINTS_ENABLED__","shikiModulePromise","shikiStreamModulePromise","hasShownShikiHint","hasShownShikiStreamHint","highlighterCache","Map","showShikiStreamHint","console","log","tokensToLineTokens","tokens","length","Array","isArray","lines","currentLine","startNewLine","push","forEach","token","content","includes","segments","split","segment","index","text","options","streaming","ref","isLoading","error","tokenizer","previousText","highlighter","currentUsedLang","lastRequestedLang","effectiveTheme","computed","isRef","theme","value","effectiveLanguage","toValue","language","preStyle","updateTokens","async","nextText","forceReset","clear","canAppend","startsWith","chunk","slice","mergedTokens","tokensStable","tokensUnstable","colorReplacements","enqueue","err","currentLang","currentTheme","codeToTokens","lang","tokensArray","initHighlighter","cacheKey","mod","import","default","createHighlighter","getHighlighter","loadShiki","has","get","themes","langs","set","loadLanguage","shikiStreamMod","ShikiStreamTokenizer","loadShikiStream","themeInfo","getTheme","preStyleValue","bg","fg","backgroundColor","color","createPreStyle","watch","newLang","requestedLang","immediate","newText","onUnmounted"],"mappings":"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"}
|
package/dist/x-markdown.cjs9.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.create,Object.defineProperty,Object.getOwnPropertyDescriptor,Object.getOwnPropertyNames,Object.getPrototypeOf,Object.prototype.hasOwnProperty,Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("lodash-es"),t=require("vue");let n=null,o=!1,a=null,r=null;const l=()=>{o||("boolean"!=typeof __X_MARKDOWN_CONSOLE_HINTS_ENABLED__||__X_MARKDOWN_CONSOLE_HINTS_ENABLED__)&&(o=!0,console.log("%c[x-markdown]%c 图表可选: %cpnpm add mermaid%c","font-weight: bold; color: #9333ea;","color: #666;","color: #9333ea; font-family: monospace;","color: #999;"))},u=[];let
|
|
1
|
+
"use strict";Object.create,Object.defineProperty,Object.getOwnPropertyDescriptor,Object.getOwnPropertyNames,Object.getPrototypeOf,Object.prototype.hasOwnProperty,Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("lodash-es"),t=require("vue");let n=null,o=!1,a=null,r=null;const l=()=>{o||("boolean"!=typeof __X_MARKDOWN_CONSOLE_HINTS_ENABLED__||__X_MARKDOWN_CONSOLE_HINTS_ENABLED__)&&(o=!0,console.log("%c[x-markdown]%c 图表可选: %cpnpm add mermaid%c","font-weight: bold; color: #9333ea;","color: #666;","color: #9333ea; font-family: monospace;","color: #999;"))},u=[];let i=!1;exports.checkMermaidAvailable=async function(){return null!==a?a:r||(r=(async()=>{try{const e=await import("mermaid"),t=e?.default,n=t&&("function"==typeof t.render||"function"==typeof t.initialize||"function"==typeof t.run);return a=n,n||l(),a}catch{return a=!1,l(),!1}})(),r)},exports.downloadSvgAsPng=function(e){if(e)try{const t=`data:image/svg+xml;charset=utf-8,${encodeURIComponent(e)}`,n=new Image;n.onload=()=>{try{const o=document.createElement("canvas"),a=o.getContext("2d",{willReadFrequently:!1});if(!a)return;const r=2;o.width=n.width*r,o.height=n.height*r,a.imageSmoothingEnabled=!0,a.imageSmoothingQuality="high",a.fillStyle="#ffffff",a.fillRect(0,0,o.width,o.height),a.drawImage(n,0,0,o.width,o.height);const l=(new Date).toISOString().slice(0,19).replace(/:/g,"-");try{o.toBlob(e=>{if(!e)return;const t=URL.createObjectURL(e),n=document.createElement("a");n.href=t,n.download=`mermaid-diagram-${l}.png`,document.body.appendChild(n),n.click(),document.body.removeChild(n),URL.revokeObjectURL(t)},"image/png",.95)}catch(e){console.error("Failed to convert canvas to blob:",e);try{const e=o.toDataURL("image/png",.95),t=document.createElement("a");t.href=e,t.download=`mermaid-diagram-${l}.png`,document.body.appendChild(t),t.click(),document.body.removeChild(t)}catch(t){console.error("Failed to convert canvas to data URL:",t)}}}catch(o){console.error("Canvas operation failed:",o)}},n.onerror=e=>{console.error("Failed to load image:",e)},n.src=t}catch(t){console.error("Failed to download SVG:",t)}},exports.useMermaid=function(o,a={}){const r=t.computed(()=>"object"==typeof a&&"value"in a?a.value:a),c=t.computed(()=>({suppressErrorRendering:!0,startOnLoad:!1,securityLevel:"loose",theme:r.value.theme||"default",...r.value.config||{}})),s=t.ref(""),d=t.ref(null),v=t.ref(!1);let m=!1;const f=e.throttle(()=>{const e="string"==typeof o?o:o.value;if(!e?.trim())return s.value="",d.value=null,void(v.value=!1);var t;v.value=!0,t=async()=>{if(!m)try{const t=await async function(){return"undefined"==typeof window?null:(n||(n=(async()=>{try{const e=await import("mermaid");if(e&&null===e.default)return l(),null;const t=e?.default;return t&&"function"==typeof t.initialize?t:(l(),null)}catch{return l(),null}})()),n)}();if(!t)return s.value=e,d.value=null,void(v.value=!1);if(t.initialize(c.value),!(await t.parse(e.trim())))return s.value="",d.value=new Error("Mermaid parse error: Invalid syntax"),void(v.value=!1);const o=`${r.value.id||"mermaid"}-${Math.random().toString(36).substring(2,11)}`,a=(()=>{const e=r.value.container;return e?"object"==typeof e&&"value"in e?e.value:e:null})();if(!a)return void(v.value=!1);const{svg:u}=await t.render(o,e,a);s.value=u,d.value=null,v.value=!1}catch(t){s.value="",d.value=t,v.value=!1}},u.push(t),async function(){if(!i){for(i=!0;u.length>0;){const t=u.shift();if(t)try{await t()}catch(e){console.error("Mermaid render queue error:",e)}}i=!1}}()},100,{leading:!1,trailing:!0});return t.watch([()=>"string"==typeof o?o:o.value,()=>c.value],()=>{f()},{immediate:!0}),t.onUnmounted(()=>{m=!0}),{data:s,error:d,isLoading:v}},exports.useMermaidZoom=function(n){const{container:o}=n,a=t.ref(1),r=t.ref(0),l=t.ref(0),u=t.ref(!1);let i=null;const c=()=>o.value?.querySelector(".syntax-mermaid__content svg"),s=e=>{e.style.transformOrigin="center center",e.style.transform=`translate(${r.value}px, ${l.value}px) scale(${a.value})`},d=()=>{a.value=1,r.value=0,l.value=0,u.value=!1},v=()=>{i?.(),i=null,d()};return t.watch(()=>o.value,()=>{v(),d()}),t.onUnmounted(v),{zoomIn:()=>{const e=c();e&&(a.value=Math.min(a.value+.2,10),s(e))},zoomOut:()=>{const e=c();e&&(a.value=Math.max(a.value-.2,.1),s(e))},reset:()=>{const e=c();e&&(d(),s(e))},fullscreen:()=>{o.value&&(document.fullscreenElement?document.exitFullscreen():o.value.requestFullscreen?.())},destroy:v,initialize:()=>{if(!o.value)return;d(),i=(t=>{let n=0,o=0,i=!1;const d=(e,t)=>{u.value=!0,n=e-r.value,o=t-l.value,document.body.style.userSelect="none"},v=(e,t)=>{if(u.value&&i){r.value=e-n,l.value=t-o;const a=c();a&&s(a)}},m=()=>{u.value=!1,i=!1,document.body.style.userSelect=""},f=e=>{0===e.button&&(e.target===t||t.contains(e.target))&&(e.preventDefault(),i=!0,d(e.clientX,e.clientY))},h=e=>{i&&v(e.clientX,e.clientY)},g=e.throttle(e=>{const n=c();if(!n)return;const o=t.getBoundingClientRect(),u=n.getBoundingClientRect(),i=e.clientX-o.left,d=e.clientY-o.top,v=u.left-o.left+u.width/2,m=u.top-o.top+u.height/2,f=(i-v-r.value)/a.value,h=(d-m-l.value)/a.value,g=e.deltaY>0?-.05:.05,p=Math.min(Math.max(a.value+g,.1),10);p!==a.value&&(a.value=p,r.value=i-v-f*a.value,l.value=d-m-h*a.value,s(n))},20,{leading:!0,trailing:!0}),p=e=>{(e.target===t||t.contains(e.target))&&(e.preventDefault(),g(e))},y=e=>{(e.target===t||t.contains(e.target))&&1===e.touches.length&&(e.preventDefault(),i=!0,d(e.touches[0].clientX,e.touches[0].clientY))},w=e=>{i&&(e.preventDefault(),v(e.touches[0].clientX,e.touches[0].clientY))};return t.addEventListener("mousedown",f),document.addEventListener("mousemove",h),document.addEventListener("mouseup",m),t.addEventListener("wheel",p,{passive:!1}),t.addEventListener("touchstart",y,{passive:!1}),t.addEventListener("touchmove",w,{passive:!1}),document.addEventListener("touchend",m),()=>{t.removeEventListener("mousedown",f),document.removeEventListener("mousemove",h),document.removeEventListener("mouseup",m),t.removeEventListener("wheel",p),t.removeEventListener("touchstart",y),t.removeEventListener("touchmove",w),document.removeEventListener("touchend",m),document.body.style.userSelect=""}})(o.value);const t=c();t&&s(t)}}};
|
|
2
2
|
//# sourceMappingURL=x-markdown.cjs9.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"x-markdown.cjs9.js","sources":["../src/hooks/useMermaid.ts"],"sourcesContent":["import type { Ref } from 'vue'\r\nimport { throttle } from 'lodash-es'\r\nimport { computed, ref, watch, onUnmounted } from 'vue'\r\nimport type { MermaidZoomControls, UseMermaidZoomOptions, UseMermaidResult } from '../components/Mermaid/types'\r\n\r\n// 获取是否启用控制台提示的辅助函数\r\nconst consoleHintsEnabled = () => {\r\n if (typeof __X_MARKDOWN_CONSOLE_HINTS_ENABLED__ === 'boolean') {\r\n return __X_MARKDOWN_CONSOLE_HINTS_ENABLED__\r\n }\r\n return true // 默认启用\r\n}\r\n\r\nexport function downloadSvgAsPng(svg: string): void {\r\n if (!svg) return\r\n\r\n try {\r\n const svgDataUrl = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`\r\n const img = new Image()\r\n\r\n img.onload = () => {\r\n try {\r\n const canvas = document.createElement('canvas')\r\n const ctx = canvas.getContext('2d', { willReadFrequently: false })\r\n if (!ctx) return\r\n\r\n const scale = 2\r\n canvas.width = img.width * scale\r\n canvas.height = img.height * scale\r\n ctx.imageSmoothingEnabled = true\r\n ctx.imageSmoothingQuality = 'high'\r\n\r\n ctx.fillStyle = '#ffffff'\r\n ctx.fillRect(0, 0, canvas.width, canvas.height)\r\n ctx.drawImage(img, 0, 0, canvas.width, canvas.height)\r\n\r\n const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, '-')\r\n\r\n try {\r\n canvas.toBlob(\r\n (blob) => {\r\n if (!blob) return\r\n const url = URL.createObjectURL(blob)\r\n const link = document.createElement('a')\r\n link.href = url\r\n link.download = `mermaid-diagram-${timestamp}.png`\r\n document.body.appendChild(link)\r\n link.click()\r\n document.body.removeChild(link)\r\n URL.revokeObjectURL(url)\r\n },\r\n 'image/png',\r\n 0.95,\r\n )\r\n } catch (toBlobError) {\r\n console.error('Failed to convert canvas to blob:', toBlobError)\r\n try {\r\n const dataUrl = canvas.toDataURL('image/png', 0.95)\r\n const link = document.createElement('a')\r\n link.href = dataUrl\r\n link.download = `mermaid-diagram-${timestamp}.png`\r\n document.body.appendChild(link)\r\n link.click()\r\n document.body.removeChild(link)\r\n } catch (dataUrlError) {\r\n console.error('Failed to convert canvas to data URL:', dataUrlError)\r\n }\r\n }\r\n } catch (canvasError) {\r\n console.error('Canvas operation failed:', canvasError)\r\n }\r\n }\r\n\r\n img.onerror = (error) => {\r\n console.error('Failed to load image:', error)\r\n }\r\n\r\n img.src = svgDataUrl\r\n } catch (error) {\r\n console.error('Failed to download SVG:', error)\r\n }\r\n}\r\n\r\ninterface UseMermaidOptions {\r\n id?: string\r\n theme?: 'default' | 'dark' | 'forest' | 'neutral' | string\r\n config?: any\r\n container?: HTMLElement | Ref<HTMLElement | null> | null\r\n}\r\n\r\ntype UseMermaidOptionsInput = UseMermaidOptions | Ref<UseMermaidOptions>\r\n\r\nlet mermaidPromise: Promise<any> | null = null\r\nlet hasShownMermaidHint = false\r\nlet mermaidAvailableCache: boolean | null = null\r\nlet mermaidCheckPromise: Promise<boolean | null> | null = null\r\n\r\n/**\r\n * 显示 mermaid 安装提示\r\n */\r\nconst showMermaidHint = () => {\r\n if (hasShownMermaidHint) return\r\n if (!consoleHintsEnabled()) return\r\n\r\n hasShownMermaidHint = true\r\n\r\n console.log(\r\n '%c[x-markdown]%c 图表可选: %cpnpm add mermaid%c',\r\n 'font-weight: bold; color: #9333ea;',\r\n 'color: #666;',\r\n 'color: #9333ea; font-family: monospace;',\r\n 'color: #999;'\r\n )\r\n}\r\n\r\n/**\r\n * 同步检查缓存状态(不触发检测)\r\n * @returns 缓存状态,null 表示未检测\r\n */\r\nexport function getMermaidAvailableCache(): boolean | null {\r\n return mermaidAvailableCache\r\n}\r\n\r\n/**\r\n * 检测 mermaid 是否可用(全局缓存,只检测一次)\r\n */\r\nexport async function checkMermaidAvailable(): Promise<boolean> {\r\n // 如果已经有缓存结果,直接返回\r\n if (mermaidAvailableCache !== null) {\r\n return mermaidAvailableCache\r\n }\r\n\r\n // 如果正在检测,返回检测 Promise\r\n if (mermaidCheckPromise) {\r\n return mermaidCheckPromise as Promise<boolean>\r\n }\r\n\r\n // 开始检测\r\n mermaidCheckPromise = (async () => {\r\n try {\r\n const mod = await import('mermaid')\r\n // 检查模块是否有实际的 mermaid 功能\r\n // 虚拟模块返回 { default: null },所以需要检查 default 是否存在且有效\r\n const mermaidInstance = (mod as any)?.default\r\n // 检查是否有 mermaid 的关键方法或属性\r\n const hasMermaidAPI = mermaidInstance && (\r\n typeof mermaidInstance.render === 'function' ||\r\n typeof mermaidInstance.initialize === 'function' ||\r\n typeof mermaidInstance.run === 'function'\r\n )\r\n mermaidAvailableCache = hasMermaidAPI\r\n\r\n // 当 mermaid 不可用时,显示提示\r\n if (!hasMermaidAPI) {\r\n showMermaidHint()\r\n }\r\n\r\n return mermaidAvailableCache\r\n } catch {\r\n // 静默失败,mermaid 不可用\r\n mermaidAvailableCache = false\r\n showMermaidHint()\r\n return false\r\n }\r\n })()\r\n\r\n return mermaidCheckPromise as Promise<boolean>\r\n}\r\n\r\nasync function loadMermaid() {\r\n if (typeof window === 'undefined') return null\r\n if (!mermaidPromise) {\r\n mermaidPromise = (async () => {\r\n try {\r\n const mod = await import('mermaid')\r\n // 检查是否是虚拟模块(虚拟模块返回 { default: null })\r\n if (mod && (mod as any).default === null) {\r\n showMermaidHint()\r\n return null\r\n }\r\n return (mod as any)?.default\r\n } catch {\r\n // 静默失败,显示友好提示\r\n showMermaidHint()\r\n return null\r\n }\r\n })()\r\n }\r\n return mermaidPromise\r\n}\r\n\r\ntype RenderTask = () => Promise<void>\r\nconst renderQueue: RenderTask[] = []\r\nlet isProcessingQueue = false\r\n\r\nasync function processRenderQueue() {\r\n if (isProcessingQueue) return\r\n isProcessingQueue = true\r\n\r\n while (renderQueue.length > 0) {\r\n const task = renderQueue.shift()\r\n if (task) {\r\n try {\r\n await task()\r\n } catch (err) {\r\n console.error('Mermaid render queue error:', err)\r\n }\r\n }\r\n }\r\n\r\n isProcessingQueue = false\r\n}\r\n\r\nfunction addToRenderQueue(task: RenderTask) {\r\n renderQueue.push(task)\r\n processRenderQueue()\r\n}\r\n\r\nexport function useMermaid(content: string | Ref<string>, options: UseMermaidOptionsInput = {}): UseMermaidResult {\r\n const optionsRef = computed(() => (typeof options === 'object' && 'value' in options ? options.value : options))\r\n const mermaidConfig = computed(() => ({\r\n suppressErrorRendering: true,\r\n startOnLoad: false,\r\n securityLevel: 'loose',\r\n theme: optionsRef.value.theme || 'default',\r\n ...(optionsRef.value.config || {}),\r\n }))\r\n const data = ref('')\r\n const error = ref<unknown>(null)\r\n const isLoading = ref(false)\r\n\r\n let isUnmounted = false\r\n\r\n const getRenderContainer = () => {\r\n const containerOption = optionsRef.value.container\r\n if (containerOption) {\r\n return typeof containerOption === 'object' && 'value' in containerOption ? containerOption.value : containerOption\r\n }\r\n return null\r\n }\r\n\r\n const throttledRender = throttle(\r\n () => {\r\n const contentValue = typeof content === 'string' ? content : content.value\r\n if (!contentValue?.trim()) {\r\n data.value = ''\r\n error.value = null\r\n isLoading.value = false\r\n return\r\n }\r\n\r\n isLoading.value = true\r\n\r\n addToRenderQueue(async () => {\r\n if (isUnmounted) return\r\n\r\n try {\r\n const mermaidInstance = await loadMermaid()\r\n if (!mermaidInstance) {\r\n data.value = contentValue\r\n error.value = null\r\n isLoading.value = false\r\n return\r\n }\r\n\r\n mermaidInstance.initialize(mermaidConfig.value)\r\n\r\n const isValid = await mermaidInstance.parse(contentValue.trim())\r\n if (!isValid) {\r\n data.value = ''\r\n error.value = new Error('Mermaid parse error: Invalid syntax')\r\n isLoading.value = false\r\n return\r\n }\r\n\r\n const renderId = `${optionsRef.value.id || 'mermaid'}-${Math.random().toString(36).substring(2, 11)}`\r\n const container = getRenderContainer()\r\n if (!container) {\r\n isLoading.value = false\r\n return\r\n }\r\n\r\n const { svg } = await mermaidInstance.render(renderId, contentValue, container)\r\n data.value = svg\r\n error.value = null\r\n isLoading.value = false\r\n } catch (err) {\r\n // Mermaid render error\r\n data.value = ''\r\n error.value = err\r\n isLoading.value = false\r\n }\r\n })\r\n },\r\n 100,\r\n { leading: false, trailing: true },\r\n )\r\n\r\n watch(\r\n [() => (typeof content === 'string' ? content : content.value), () => mermaidConfig.value],\r\n () => {\r\n throttledRender()\r\n },\r\n { immediate: true },\r\n )\r\n\r\n onUnmounted(() => {\r\n isUnmounted = true\r\n })\r\n\r\n return {\r\n data,\r\n error,\r\n isLoading,\r\n }\r\n}\r\n\r\nexport function useMermaidZoom(options: UseMermaidZoomOptions): MermaidZoomControls {\r\n const { container } = options\r\n\r\n const scale = ref(1)\r\n const posX = ref(0)\r\n const posY = ref(0)\r\n const isDragging = ref(false)\r\n\r\n let removeEvents: (() => void) | null = null\r\n\r\n const getSvg = () => container.value?.querySelector('.syntax-mermaid__content svg') as HTMLElement\r\n\r\n const updateTransform = (svg: HTMLElement) => {\r\n svg.style.transformOrigin = 'center center'\r\n svg.style.transform = `translate(${posX.value}px, ${posY.value}px) scale(${scale.value})`\r\n }\r\n\r\n const resetState = () => {\r\n scale.value = 1\r\n posX.value = 0\r\n posY.value = 0\r\n isDragging.value = false\r\n }\r\n\r\n const addInteractionEvents = (containerEl: HTMLElement) => {\r\n let startX = 0\r\n let startY = 0\r\n let isInteractingWithMermaid = false\r\n\r\n const onStart = (clientX: number, clientY: number) => {\r\n isDragging.value = true\r\n startX = clientX - posX.value\r\n startY = clientY - posY.value\r\n document.body.style.userSelect = 'none'\r\n }\r\n\r\n const onMove = (clientX: number, clientY: number) => {\r\n if (isDragging.value && isInteractingWithMermaid) {\r\n posX.value = clientX - startX\r\n posY.value = clientY - startY\r\n const svg = getSvg()\r\n if (svg) {\r\n updateTransform(svg)\r\n }\r\n }\r\n }\r\n\r\n const onEnd = () => {\r\n isDragging.value = false\r\n isInteractingWithMermaid = false\r\n document.body.style.userSelect = ''\r\n }\r\n\r\n const onMouseDown = (e: MouseEvent) => {\r\n if (e.button !== 0) return\r\n if (e.target === containerEl || containerEl.contains(e.target as Node)) {\r\n e.preventDefault()\r\n isInteractingWithMermaid = true\r\n onStart(e.clientX, e.clientY)\r\n }\r\n }\r\n\r\n const onMouseMove = (e: MouseEvent) => {\r\n if (isInteractingWithMermaid) {\r\n onMove(e.clientX, e.clientY)\r\n }\r\n }\r\n\r\n const handleWheelZoom = (e: WheelEvent) => {\r\n const svg = getSvg()\r\n if (!svg) return\r\n\r\n const containerRect = containerEl.getBoundingClientRect()\r\n const svgRect = svg.getBoundingClientRect()\r\n\r\n const mouseX = e.clientX - containerRect.left\r\n const mouseY = e.clientY - containerRect.top\r\n\r\n const svgCenterX = svgRect.left - containerRect.left + svgRect.width / 2\r\n const svgCenterY = svgRect.top - containerRect.top + svgRect.height / 2\r\n\r\n const offsetX = (mouseX - svgCenterX - posX.value) / scale.value\r\n const offsetY = (mouseY - svgCenterY - posY.value) / scale.value\r\n\r\n const delta = e.deltaY > 0 ? -0.05 : 0.05\r\n const newScale = Math.min(Math.max(scale.value + delta, 0.1), 10)\r\n\r\n if (newScale === scale.value) return\r\n\r\n scale.value = newScale\r\n\r\n posX.value = mouseX - svgCenterX - offsetX * scale.value\r\n posY.value = mouseY - svgCenterY - offsetY * scale.value\r\n\r\n updateTransform(svg)\r\n }\r\n\r\n const throttledWheelZoom = throttle(handleWheelZoom, 20, { leading: true, trailing: true })\r\n\r\n const onWheel = (e: WheelEvent) => {\r\n if (e.target === containerEl || containerEl.contains(e.target as Node)) {\r\n e.preventDefault()\r\n throttledWheelZoom(e)\r\n }\r\n }\r\n\r\n const onTouchStart = (e: TouchEvent) => {\r\n if (e.target === containerEl || containerEl.contains(e.target as Node)) {\r\n if (e.touches.length === 1) {\r\n e.preventDefault()\r\n isInteractingWithMermaid = true\r\n onStart(e.touches[0].clientX, e.touches[0].clientY)\r\n }\r\n }\r\n }\r\n\r\n const onTouchMove = (e: TouchEvent) => {\r\n if (isInteractingWithMermaid) {\r\n e.preventDefault()\r\n onMove(e.touches[0].clientX, e.touches[0].clientY)\r\n }\r\n }\r\n\r\n containerEl.addEventListener('mousedown', onMouseDown)\r\n document.addEventListener('mousemove', onMouseMove)\r\n document.addEventListener('mouseup', onEnd)\r\n containerEl.addEventListener('wheel', onWheel, { passive: false })\r\n containerEl.addEventListener('touchstart', onTouchStart, { passive: false })\r\n containerEl.addEventListener('touchmove', onTouchMove, { passive: false })\r\n document.addEventListener('touchend', onEnd)\r\n\r\n return () => {\r\n containerEl.removeEventListener('mousedown', onMouseDown)\r\n document.removeEventListener('mousemove', onMouseMove)\r\n document.removeEventListener('mouseup', onEnd)\r\n containerEl.removeEventListener('wheel', onWheel)\r\n containerEl.removeEventListener('touchstart', onTouchStart)\r\n containerEl.removeEventListener('touchmove', onTouchMove)\r\n document.removeEventListener('touchend', onEnd)\r\n document.body.style.userSelect = ''\r\n }\r\n }\r\n\r\n const zoomIn = () => {\r\n const svg = getSvg()\r\n if (svg) {\r\n scale.value = Math.min(scale.value + 0.2, 10)\r\n updateTransform(svg)\r\n }\r\n }\r\n\r\n const zoomOut = () => {\r\n const svg = getSvg()\r\n if (svg) {\r\n scale.value = Math.max(scale.value - 0.2, 0.1)\r\n updateTransform(svg)\r\n }\r\n }\r\n\r\n const reset = () => {\r\n const svg = getSvg()\r\n if (svg) {\r\n resetState()\r\n updateTransform(svg)\r\n }\r\n }\r\n\r\n const fullscreen = () => {\r\n if (!container.value) return\r\n\r\n if (document.fullscreenElement) {\r\n document.exitFullscreen()\r\n } else {\r\n container.value.requestFullscreen?.()\r\n }\r\n }\r\n\r\n const initialize = () => {\r\n if (!container.value) return\r\n\r\n resetState()\r\n\r\n removeEvents = addInteractionEvents(container.value)\r\n\r\n const svg = getSvg()\r\n if (svg) {\r\n updateTransform(svg)\r\n }\r\n }\r\n\r\n const destroy = () => {\r\n removeEvents?.()\r\n removeEvents = null\r\n resetState()\r\n }\r\n\r\n watch(\r\n () => container.value,\r\n () => {\r\n destroy()\r\n resetState()\r\n },\r\n )\r\n\r\n onUnmounted(destroy)\r\n\r\n return {\r\n zoomIn,\r\n zoomOut,\r\n reset,\r\n fullscreen,\r\n destroy,\r\n initialize,\r\n }\r\n}\r\n"],"names":["mermaidPromise","hasShownMermaidHint","mermaidAvailableCache","mermaidCheckPromise","showMermaidHint","__X_MARKDOWN_CONSOLE_HINTS_ENABLED__","console","log","renderQueue","isProcessingQueue","async","mod","import","mermaidInstance","default","hasMermaidAPI","render","initialize","run","svg","svgDataUrl","encodeURIComponent","img","Image","onload","canvas","document","createElement","ctx","getContext","willReadFrequently","scale","width","height","imageSmoothingEnabled","imageSmoothingQuality","fillStyle","fillRect","drawImage","timestamp","Date","toISOString","slice","replace","toBlob","blob","url","URL","createObjectURL","link","href","download","body","appendChild","click","removeChild","revokeObjectURL","toBlobError","error","dataUrl","toDataURL","dataUrlError","canvasError","onerror","src","content","options","optionsRef","computed","value","mermaidConfig","suppressErrorRendering","startOnLoad","securityLevel","theme","config","data","ref","isLoading","isUnmounted","throttledRender","throttle","contentValue","trim","task","window","loadMermaid","parse","Error","renderId","id","Math","random","toString","substring","container","containerOption","getRenderContainer","err","push","length","shift","processRenderQueue","leading","trailing","watch","immediate","onUnmounted","posX","posY","isDragging","removeEvents","getSvg","querySelector","updateTransform","style","transformOrigin","transform","resetState","destroy","zoomIn","min","zoomOut","max","reset","fullscreen","fullscreenElement","exitFullscreen","requestFullscreen","containerEl","startX","startY","isInteractingWithMermaid","onStart","clientX","clientY","userSelect","onMove","onEnd","onMouseDown","e","button","target","contains","preventDefault","onMouseMove","throttledWheelZoom","containerRect","getBoundingClientRect","svgRect","mouseX","left","mouseY","top","svgCenterX","svgCenterY","offsetX","offsetY","delta","deltaY","newScale","onWheel","onTouchStart","touches","onTouchMove","addEventListener","passive","removeEventListener","addInteractionEvents"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"x-markdown.cjs9.js","sources":["../src/hooks/useMermaid.ts"],"sourcesContent":["import type { Ref } from 'vue'\r\nimport { throttle } from 'lodash-es'\r\nimport { computed, ref, watch, onUnmounted } from 'vue'\r\nimport type { MermaidZoomControls, UseMermaidZoomOptions, UseMermaidResult } from '../components/Mermaid/types'\r\n\r\n// 获取是否启用控制台提示的辅助函数\r\nconst consoleHintsEnabled = () => {\r\n if (typeof __X_MARKDOWN_CONSOLE_HINTS_ENABLED__ === 'boolean') {\r\n return __X_MARKDOWN_CONSOLE_HINTS_ENABLED__\r\n }\r\n return true // 默认启用\r\n}\r\n\r\nexport function downloadSvgAsPng(svg: string): void {\r\n if (!svg) return\r\n\r\n try {\r\n const svgDataUrl = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`\r\n const img = new Image()\r\n\r\n img.onload = () => {\r\n try {\r\n const canvas = document.createElement('canvas')\r\n const ctx = canvas.getContext('2d', { willReadFrequently: false })\r\n if (!ctx) return\r\n\r\n const scale = 2\r\n canvas.width = img.width * scale\r\n canvas.height = img.height * scale\r\n ctx.imageSmoothingEnabled = true\r\n ctx.imageSmoothingQuality = 'high'\r\n\r\n ctx.fillStyle = '#ffffff'\r\n ctx.fillRect(0, 0, canvas.width, canvas.height)\r\n ctx.drawImage(img, 0, 0, canvas.width, canvas.height)\r\n\r\n const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, '-')\r\n\r\n try {\r\n canvas.toBlob(\r\n (blob) => {\r\n if (!blob) return\r\n const url = URL.createObjectURL(blob)\r\n const link = document.createElement('a')\r\n link.href = url\r\n link.download = `mermaid-diagram-${timestamp}.png`\r\n document.body.appendChild(link)\r\n link.click()\r\n document.body.removeChild(link)\r\n URL.revokeObjectURL(url)\r\n },\r\n 'image/png',\r\n 0.95,\r\n )\r\n } catch (toBlobError) {\r\n console.error('Failed to convert canvas to blob:', toBlobError)\r\n try {\r\n const dataUrl = canvas.toDataURL('image/png', 0.95)\r\n const link = document.createElement('a')\r\n link.href = dataUrl\r\n link.download = `mermaid-diagram-${timestamp}.png`\r\n document.body.appendChild(link)\r\n link.click()\r\n document.body.removeChild(link)\r\n } catch (dataUrlError) {\r\n console.error('Failed to convert canvas to data URL:', dataUrlError)\r\n }\r\n }\r\n } catch (canvasError) {\r\n console.error('Canvas operation failed:', canvasError)\r\n }\r\n }\r\n\r\n img.onerror = (error) => {\r\n console.error('Failed to load image:', error)\r\n }\r\n\r\n img.src = svgDataUrl\r\n } catch (error) {\r\n console.error('Failed to download SVG:', error)\r\n }\r\n}\r\n\r\ninterface UseMermaidOptions {\r\n id?: string\r\n theme?: 'default' | 'dark' | 'forest' | 'neutral' | string\r\n config?: any\r\n container?: HTMLElement | Ref<HTMLElement | null> | null\r\n}\r\n\r\ntype UseMermaidOptionsInput = UseMermaidOptions | Ref<UseMermaidOptions>\r\n\r\nlet mermaidPromise: Promise<any> | null = null\r\nlet hasShownMermaidHint = false\r\nlet mermaidAvailableCache: boolean | null = null\r\nlet mermaidCheckPromise: Promise<boolean | null> | null = null\r\n\r\n/**\r\n * 显示 mermaid 安装提示\r\n */\r\nconst showMermaidHint = () => {\r\n if (hasShownMermaidHint) return\r\n if (!consoleHintsEnabled()) return\r\n\r\n hasShownMermaidHint = true\r\n\r\n console.log(\r\n '%c[x-markdown]%c 图表可选: %cpnpm add mermaid%c',\r\n 'font-weight: bold; color: #9333ea;',\r\n 'color: #666;',\r\n 'color: #9333ea; font-family: monospace;',\r\n 'color: #999;'\r\n )\r\n}\r\n\r\n/**\r\n * 同步检查缓存状态(不触发检测)\r\n * @returns 缓存状态,null 表示未检测\r\n */\r\nexport function getMermaidAvailableCache(): boolean | null {\r\n return mermaidAvailableCache\r\n}\r\n\r\n/**\r\n * 检测 mermaid 是否可用(全局缓存,只检测一次)\r\n */\r\nexport async function checkMermaidAvailable(): Promise<boolean> {\r\n // 如果已经有缓存结果,直接返回\r\n if (mermaidAvailableCache !== null) {\r\n return mermaidAvailableCache\r\n }\r\n\r\n // 如果正在检测,返回检测 Promise\r\n if (mermaidCheckPromise) {\r\n return mermaidCheckPromise as Promise<boolean>\r\n }\r\n\r\n // 开始检测\r\n mermaidCheckPromise = (async () => {\r\n try {\r\n const mod = await import('mermaid')\r\n // 检查模块是否有实际的 mermaid 功能\r\n // 虚拟模块返回 { default: null },所以需要检查 default 是否存在且有效\r\n const mermaidInstance = (mod as any)?.default\r\n // 检查是否有 mermaid 的关键方法或属性\r\n const hasMermaidAPI = mermaidInstance && (\r\n typeof mermaidInstance.render === 'function' ||\r\n typeof mermaidInstance.initialize === 'function' ||\r\n typeof mermaidInstance.run === 'function'\r\n )\r\n mermaidAvailableCache = hasMermaidAPI\r\n\r\n // 当 mermaid 不可用时,显示提示\r\n if (!hasMermaidAPI) {\r\n showMermaidHint()\r\n }\r\n\r\n return mermaidAvailableCache\r\n } catch {\r\n // 静默失败,mermaid 不可用\r\n mermaidAvailableCache = false\r\n showMermaidHint()\r\n return false\r\n }\r\n })()\r\n\r\n return mermaidCheckPromise as Promise<boolean>\r\n}\r\n\r\nasync function loadMermaid() {\r\n if (typeof window === 'undefined') return null\r\n if (!mermaidPromise) {\r\n mermaidPromise = (async () => {\r\n try {\r\n const mod = await import('mermaid')\r\n // 检查是否是虚拟模块(虚拟模块返回 { default: null })\r\n if (mod && (mod as any).default === null) {\r\n showMermaidHint()\r\n return null\r\n }\r\n \r\n const mermaidInstance = (mod as any)?.default\r\n // 检查是否有 mermaid 的关键方法,确保模块可用\r\n if (!mermaidInstance || typeof mermaidInstance.initialize !== 'function') {\r\n showMermaidHint()\r\n return null\r\n }\r\n \r\n return mermaidInstance\r\n } catch {\r\n // 静默失败,显示友好提示\r\n showMermaidHint()\r\n return null\r\n }\r\n })()\r\n }\r\n return mermaidPromise\r\n}\r\n\r\ntype RenderTask = () => Promise<void>\r\nconst renderQueue: RenderTask[] = []\r\nlet isProcessingQueue = false\r\n\r\nasync function processRenderQueue() {\r\n if (isProcessingQueue) return\r\n isProcessingQueue = true\r\n\r\n while (renderQueue.length > 0) {\r\n const task = renderQueue.shift()\r\n if (task) {\r\n try {\r\n await task()\r\n } catch (err) {\r\n console.error('Mermaid render queue error:', err)\r\n }\r\n }\r\n }\r\n\r\n isProcessingQueue = false\r\n}\r\n\r\nfunction addToRenderQueue(task: RenderTask) {\r\n renderQueue.push(task)\r\n processRenderQueue()\r\n}\r\n\r\nexport function useMermaid(content: string | Ref<string>, options: UseMermaidOptionsInput = {}): UseMermaidResult {\r\n const optionsRef = computed(() => (typeof options === 'object' && 'value' in options ? options.value : options))\r\n const mermaidConfig = computed(() => ({\r\n suppressErrorRendering: true,\r\n startOnLoad: false,\r\n securityLevel: 'loose',\r\n theme: optionsRef.value.theme || 'default',\r\n ...(optionsRef.value.config || {}),\r\n }))\r\n const data = ref('')\r\n const error = ref<unknown>(null)\r\n const isLoading = ref(false)\r\n\r\n let isUnmounted = false\r\n\r\n const getRenderContainer = () => {\r\n const containerOption = optionsRef.value.container\r\n if (containerOption) {\r\n return typeof containerOption === 'object' && 'value' in containerOption ? containerOption.value : containerOption\r\n }\r\n return null\r\n }\r\n\r\n const throttledRender = throttle(\r\n () => {\r\n const contentValue = typeof content === 'string' ? content : content.value\r\n if (!contentValue?.trim()) {\r\n data.value = ''\r\n error.value = null\r\n isLoading.value = false\r\n return\r\n }\r\n\r\n isLoading.value = true\r\n\r\n addToRenderQueue(async () => {\r\n if (isUnmounted) return\r\n\r\n try {\r\n const mermaidInstance = await loadMermaid()\r\n if (!mermaidInstance) {\r\n data.value = contentValue\r\n error.value = null\r\n isLoading.value = false\r\n return\r\n }\r\n\r\n mermaidInstance.initialize(mermaidConfig.value)\r\n\r\n const isValid = await mermaidInstance.parse(contentValue.trim())\r\n if (!isValid) {\r\n data.value = ''\r\n error.value = new Error('Mermaid parse error: Invalid syntax')\r\n isLoading.value = false\r\n return\r\n }\r\n\r\n const renderId = `${optionsRef.value.id || 'mermaid'}-${Math.random().toString(36).substring(2, 11)}`\r\n const container = getRenderContainer()\r\n if (!container) {\r\n isLoading.value = false\r\n return\r\n }\r\n\r\n const { svg } = await mermaidInstance.render(renderId, contentValue, container)\r\n data.value = svg\r\n error.value = null\r\n isLoading.value = false\r\n } catch (err) {\r\n // Mermaid render error\r\n data.value = ''\r\n error.value = err\r\n isLoading.value = false\r\n }\r\n })\r\n },\r\n 100,\r\n { leading: false, trailing: true },\r\n )\r\n\r\n watch(\r\n [() => (typeof content === 'string' ? content : content.value), () => mermaidConfig.value],\r\n () => {\r\n throttledRender()\r\n },\r\n { immediate: true },\r\n )\r\n\r\n onUnmounted(() => {\r\n isUnmounted = true\r\n })\r\n\r\n return {\r\n data,\r\n error,\r\n isLoading,\r\n }\r\n}\r\n\r\nexport function useMermaidZoom(options: UseMermaidZoomOptions): MermaidZoomControls {\r\n const { container } = options\r\n\r\n const scale = ref(1)\r\n const posX = ref(0)\r\n const posY = ref(0)\r\n const isDragging = ref(false)\r\n\r\n let removeEvents: (() => void) | null = null\r\n\r\n const getSvg = () => container.value?.querySelector('.syntax-mermaid__content svg') as HTMLElement\r\n\r\n const updateTransform = (svg: HTMLElement) => {\r\n svg.style.transformOrigin = 'center center'\r\n svg.style.transform = `translate(${posX.value}px, ${posY.value}px) scale(${scale.value})`\r\n }\r\n\r\n const resetState = () => {\r\n scale.value = 1\r\n posX.value = 0\r\n posY.value = 0\r\n isDragging.value = false\r\n }\r\n\r\n const addInteractionEvents = (containerEl: HTMLElement) => {\r\n let startX = 0\r\n let startY = 0\r\n let isInteractingWithMermaid = false\r\n\r\n const onStart = (clientX: number, clientY: number) => {\r\n isDragging.value = true\r\n startX = clientX - posX.value\r\n startY = clientY - posY.value\r\n document.body.style.userSelect = 'none'\r\n }\r\n\r\n const onMove = (clientX: number, clientY: number) => {\r\n if (isDragging.value && isInteractingWithMermaid) {\r\n posX.value = clientX - startX\r\n posY.value = clientY - startY\r\n const svg = getSvg()\r\n if (svg) {\r\n updateTransform(svg)\r\n }\r\n }\r\n }\r\n\r\n const onEnd = () => {\r\n isDragging.value = false\r\n isInteractingWithMermaid = false\r\n document.body.style.userSelect = ''\r\n }\r\n\r\n const onMouseDown = (e: MouseEvent) => {\r\n if (e.button !== 0) return\r\n if (e.target === containerEl || containerEl.contains(e.target as Node)) {\r\n e.preventDefault()\r\n isInteractingWithMermaid = true\r\n onStart(e.clientX, e.clientY)\r\n }\r\n }\r\n\r\n const onMouseMove = (e: MouseEvent) => {\r\n if (isInteractingWithMermaid) {\r\n onMove(e.clientX, e.clientY)\r\n }\r\n }\r\n\r\n const handleWheelZoom = (e: WheelEvent) => {\r\n const svg = getSvg()\r\n if (!svg) return\r\n\r\n const containerRect = containerEl.getBoundingClientRect()\r\n const svgRect = svg.getBoundingClientRect()\r\n\r\n const mouseX = e.clientX - containerRect.left\r\n const mouseY = e.clientY - containerRect.top\r\n\r\n const svgCenterX = svgRect.left - containerRect.left + svgRect.width / 2\r\n const svgCenterY = svgRect.top - containerRect.top + svgRect.height / 2\r\n\r\n const offsetX = (mouseX - svgCenterX - posX.value) / scale.value\r\n const offsetY = (mouseY - svgCenterY - posY.value) / scale.value\r\n\r\n const delta = e.deltaY > 0 ? -0.05 : 0.05\r\n const newScale = Math.min(Math.max(scale.value + delta, 0.1), 10)\r\n\r\n if (newScale === scale.value) return\r\n\r\n scale.value = newScale\r\n\r\n posX.value = mouseX - svgCenterX - offsetX * scale.value\r\n posY.value = mouseY - svgCenterY - offsetY * scale.value\r\n\r\n updateTransform(svg)\r\n }\r\n\r\n const throttledWheelZoom = throttle(handleWheelZoom, 20, { leading: true, trailing: true })\r\n\r\n const onWheel = (e: WheelEvent) => {\r\n if (e.target === containerEl || containerEl.contains(e.target as Node)) {\r\n e.preventDefault()\r\n throttledWheelZoom(e)\r\n }\r\n }\r\n\r\n const onTouchStart = (e: TouchEvent) => {\r\n if (e.target === containerEl || containerEl.contains(e.target as Node)) {\r\n if (e.touches.length === 1) {\r\n e.preventDefault()\r\n isInteractingWithMermaid = true\r\n onStart(e.touches[0].clientX, e.touches[0].clientY)\r\n }\r\n }\r\n }\r\n\r\n const onTouchMove = (e: TouchEvent) => {\r\n if (isInteractingWithMermaid) {\r\n e.preventDefault()\r\n onMove(e.touches[0].clientX, e.touches[0].clientY)\r\n }\r\n }\r\n\r\n containerEl.addEventListener('mousedown', onMouseDown)\r\n document.addEventListener('mousemove', onMouseMove)\r\n document.addEventListener('mouseup', onEnd)\r\n containerEl.addEventListener('wheel', onWheel, { passive: false })\r\n containerEl.addEventListener('touchstart', onTouchStart, { passive: false })\r\n containerEl.addEventListener('touchmove', onTouchMove, { passive: false })\r\n document.addEventListener('touchend', onEnd)\r\n\r\n return () => {\r\n containerEl.removeEventListener('mousedown', onMouseDown)\r\n document.removeEventListener('mousemove', onMouseMove)\r\n document.removeEventListener('mouseup', onEnd)\r\n containerEl.removeEventListener('wheel', onWheel)\r\n containerEl.removeEventListener('touchstart', onTouchStart)\r\n containerEl.removeEventListener('touchmove', onTouchMove)\r\n document.removeEventListener('touchend', onEnd)\r\n document.body.style.userSelect = ''\r\n }\r\n }\r\n\r\n const zoomIn = () => {\r\n const svg = getSvg()\r\n if (svg) {\r\n scale.value = Math.min(scale.value + 0.2, 10)\r\n updateTransform(svg)\r\n }\r\n }\r\n\r\n const zoomOut = () => {\r\n const svg = getSvg()\r\n if (svg) {\r\n scale.value = Math.max(scale.value - 0.2, 0.1)\r\n updateTransform(svg)\r\n }\r\n }\r\n\r\n const reset = () => {\r\n const svg = getSvg()\r\n if (svg) {\r\n resetState()\r\n updateTransform(svg)\r\n }\r\n }\r\n\r\n const fullscreen = () => {\r\n if (!container.value) return\r\n\r\n if (document.fullscreenElement) {\r\n document.exitFullscreen()\r\n } else {\r\n container.value.requestFullscreen?.()\r\n }\r\n }\r\n\r\n const initialize = () => {\r\n if (!container.value) return\r\n\r\n resetState()\r\n\r\n removeEvents = addInteractionEvents(container.value)\r\n\r\n const svg = getSvg()\r\n if (svg) {\r\n updateTransform(svg)\r\n }\r\n }\r\n\r\n const destroy = () => {\r\n removeEvents?.()\r\n removeEvents = null\r\n resetState()\r\n }\r\n\r\n watch(\r\n () => container.value,\r\n () => {\r\n destroy()\r\n resetState()\r\n },\r\n )\r\n\r\n onUnmounted(destroy)\r\n\r\n return {\r\n zoomIn,\r\n zoomOut,\r\n reset,\r\n fullscreen,\r\n destroy,\r\n initialize,\r\n }\r\n}\r\n"],"names":["mermaidPromise","hasShownMermaidHint","mermaidAvailableCache","mermaidCheckPromise","showMermaidHint","__X_MARKDOWN_CONSOLE_HINTS_ENABLED__","console","log","renderQueue","isProcessingQueue","async","mod","import","mermaidInstance","default","hasMermaidAPI","render","initialize","run","svg","svgDataUrl","encodeURIComponent","img","Image","onload","canvas","document","createElement","ctx","getContext","willReadFrequently","scale","width","height","imageSmoothingEnabled","imageSmoothingQuality","fillStyle","fillRect","drawImage","timestamp","Date","toISOString","slice","replace","toBlob","blob","url","URL","createObjectURL","link","href","download","body","appendChild","click","removeChild","revokeObjectURL","toBlobError","error","dataUrl","toDataURL","dataUrlError","canvasError","onerror","src","content","options","optionsRef","computed","value","mermaidConfig","suppressErrorRendering","startOnLoad","securityLevel","theme","config","data","ref","isLoading","isUnmounted","throttledRender","throttle","contentValue","trim","task","window","loadMermaid","parse","Error","renderId","id","Math","random","toString","substring","container","containerOption","getRenderContainer","err","push","length","shift","processRenderQueue","leading","trailing","watch","immediate","onUnmounted","posX","posY","isDragging","removeEvents","getSvg","querySelector","updateTransform","style","transformOrigin","transform","resetState","destroy","zoomIn","min","zoomOut","max","reset","fullscreen","fullscreenElement","exitFullscreen","requestFullscreen","containerEl","startX","startY","isInteractingWithMermaid","onStart","clientX","clientY","userSelect","onMove","onEnd","onMouseDown","e","button","target","contains","preventDefault","onMouseMove","throttledWheelZoom","containerRect","getBoundingClientRect","svgRect","mouseX","left","mouseY","top","svgCenterX","svgCenterY","offsetX","offsetY","delta","deltaY","newScale","onWheel","onTouchStart","touches","onTouchMove","addEventListener","passive","removeEventListener","addInteractionEvents"],"mappings":"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"}
|
package/dist/x-markdown.es7.js
CHANGED
|
@@ -43,6 +43,9 @@ const loadShiki = async () => {
|
|
|
43
43
|
if (mod && mod.default === null) {
|
|
44
44
|
return null;
|
|
45
45
|
}
|
|
46
|
+
if (!mod || !(mod.createHighlighter || mod.getHighlighter)) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
46
49
|
return mod;
|
|
47
50
|
} catch {
|
|
48
51
|
return null;
|
|
@@ -59,6 +62,9 @@ const loadShikiStream = async () => {
|
|
|
59
62
|
if (mod && mod.default === null) {
|
|
60
63
|
return null;
|
|
61
64
|
}
|
|
65
|
+
if (!mod || !mod.ShikiStreamTokenizer) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
62
68
|
return mod;
|
|
63
69
|
} catch {
|
|
64
70
|
return null;
|
|
@@ -203,7 +209,6 @@ function useHighlight(text, options) {
|
|
|
203
209
|
showShikiStreamHint();
|
|
204
210
|
return;
|
|
205
211
|
}
|
|
206
|
-
console.log("%c[x-markdown] Shiki loaded successfully", "background: #0b0; color: #fff; padding: 2px 4px;");
|
|
207
212
|
if (!highlighterCache.has(cacheKey)) {
|
|
208
213
|
highlighter = await mod.createHighlighter({
|
|
209
214
|
themes: [currentTheme],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"x-markdown.es7.js","sources":["../src/hooks/useHighlight.ts"],"sourcesContent":["import {\r\n ref,\r\n watch,\r\n onUnmounted,\r\n computed,\r\n isRef,\r\n toValue,\r\n type Ref,\r\n type MaybeRefOrGetter,\r\n type CSSProperties,\r\n} from 'vue'\r\n\r\n// 获取是否启用控制台提示的辅助函数\r\nconst consoleHintsEnabled = () => {\r\n if (typeof __X_MARKDOWN_CONSOLE_HINTS_ENABLED__ === 'boolean') {\r\n return __X_MARKDOWN_CONSOLE_HINTS_ENABLED__\r\n }\r\n return true // 默认启用\r\n}\r\n\r\nexport interface HighlightToken {\r\n content?: string\r\n color?: string\r\n fontStyle?: 'italic' | null\r\n fontWeight?: 'normal' | 'bold' | null\r\n htmlStyle?: Record<string, string>\r\n}\r\n\r\ninterface StreamingHighlightResult {\r\n colorReplacements?: Record<string, string>\r\n lines: HighlightToken[][]\r\n preStyle?: CSSProperties\r\n}\r\n\r\ninterface UseHighlightOptions {\r\n language: MaybeRefOrGetter<string>\r\n theme?: string | Ref<string>\r\n colorReplacements?: Record<string, string>\r\n}\r\n\r\nlet shikiModulePromise: Promise<any | null> | null = null\r\nlet shikiStreamModulePromise: Promise<any | null> | null = null\r\nlet hasShownShikiHint = false\r\nlet hasShownShikiStreamHint = false\r\n\r\n// 全局 highlighter 缓存,避免重复创建实例\r\nconst highlighterCache = new Map<string, any>()\r\nconst getHighlighterCacheKey = (theme: string) => `highlighter-${theme}`\r\n\r\nconst showShikiHint = () => {\r\n if (hasShownShikiHint) return\r\n if (!consoleHintsEnabled()) return\r\n\r\n hasShownShikiHint = true\r\n\r\n console.log(\r\n '%c[x-markdown]%c 需安装 shiki: %cpnpm add shiki%c',\r\n 'font-weight: bold; color: #0066cc;',\r\n 'color: #666;',\r\n 'color: #00aa00; font-family: monospace;',\r\n 'color: #999;',\r\n )\r\n}\r\n\r\nconst showShikiStreamHint = () => {\r\n if (hasShownShikiStreamHint) return\r\n if (!consoleHintsEnabled()) return\r\n\r\n hasShownShikiStreamHint = true\r\n\r\n console.log(\r\n '%c[x-markdown]%c AI 流式可选: %cpnpm add shiki-stream%c (需先装 shiki)',\r\n 'font-weight: bold; color: #0066cc;',\r\n 'color: #666;',\r\n 'color: #00aa00; font-family: monospace;',\r\n 'color: #999;',\r\n )\r\n}\r\n\r\nconst loadShiki = async () => {\r\n if (!shikiModulePromise) {\r\n shikiModulePromise = (async () => {\r\n try {\r\n const mod = await import('shiki')\r\n // 检查是否是虚拟模块(虚拟模块返回 { default: null })\r\n if (mod && (mod as any).default === null) {\r\n return null\r\n }\r\n return mod\r\n } catch {\r\n // 静默失败,返回 null\r\n return null\r\n }\r\n })()\r\n }\r\n return shikiModulePromise\r\n}\r\n\r\nconst loadShikiStream = async () => {\r\n if (!shikiStreamModulePromise) {\r\n shikiStreamModulePromise = (async () => {\r\n try {\r\n const mod = await import('shiki-stream')\r\n // 检查是否是虚拟模块(虚拟模块返回 { default: null })\r\n if (mod && (mod as any).default === null) {\r\n return null\r\n }\r\n return mod\r\n } catch {\r\n // 静默失败,返回 null\r\n return null\r\n }\r\n })()\r\n }\r\n return shikiStreamModulePromise\r\n}\r\n\r\nconst tokensToLineTokens = (tokens: HighlightToken[] | HighlightToken[][]): HighlightToken[][] => {\r\n if (!tokens.length) return [[]]\r\n\r\n // 检查是否已经是二维数组(shiki 3.x codeToTokens 的返回格式)\r\n if (Array.isArray(tokens[0])) {\r\n return tokens as HighlightToken[][]\r\n }\r\n\r\n // 处理一维数组(shiki-stream 的格式)\r\n const lines: HighlightToken[][] = [[]]\r\n let currentLine = lines[0]\r\n\r\n const startNewLine = () => {\r\n currentLine = []\r\n lines.push(currentLine)\r\n }\r\n\r\n ;(tokens as HighlightToken[]).forEach((token) => {\r\n const content = token.content ?? ''\r\n\r\n if (content === '\\n') {\r\n startNewLine()\r\n return\r\n }\r\n\r\n if (!content.includes('\\n')) {\r\n currentLine.push(token)\r\n return\r\n }\r\n\r\n const segments = content.split('\\n')\r\n segments.forEach((segment: string, index: number) => {\r\n if (segment) {\r\n currentLine.push({\r\n ...token,\r\n content: segment,\r\n })\r\n }\r\n\r\n if (index < segments.length - 1) {\r\n startNewLine()\r\n }\r\n })\r\n })\r\n\r\n return lines.length === 0 ? [[]] : lines\r\n}\r\n\r\nconst createPreStyle = (bg?: string, fg?: string): CSSProperties | undefined => {\r\n if (!bg && !fg) return undefined\r\n return {\r\n backgroundColor: bg,\r\n color: fg,\r\n }\r\n}\r\n\r\nexport function useHighlight(text: Ref<string>, options: UseHighlightOptions) {\r\n const streaming = ref<StreamingHighlightResult>()\r\n const isLoading = ref(false)\r\n const error = ref<Error | null>(null)\r\n\r\n let tokenizer: any | null = null\r\n let previousText = ''\r\n let highlighter: any | null = null\r\n let currentUsedLang = ''\r\n let lastRequestedLang = ''\r\n\r\n const effectiveTheme = computed(() => {\r\n const theme = isRef(options.theme) ? options.theme.value : options.theme\r\n return theme || 'slack-dark'\r\n })\r\n\r\n const effectiveLanguage = computed(() => {\r\n return toValue(options.language) || 'text'\r\n })\r\n\r\n const lines = computed(() => streaming.value?.lines || [[]])\r\n const preStyle = computed(() => streaming.value?.preStyle)\r\n\r\n const updateTokens = async (nextText: string, forceReset = false) => {\r\n // 当有 tokenizer 时使用流式处理\r\n if (tokenizer) {\r\n if (forceReset) {\r\n tokenizer.clear()\r\n previousText = ''\r\n }\r\n\r\n const canAppend = !forceReset && nextText.startsWith(previousText)\r\n let chunk = nextText\r\n\r\n if (canAppend) {\r\n chunk = nextText.slice(previousText.length)\r\n } else if (!forceReset) {\r\n tokenizer.clear()\r\n }\r\n\r\n previousText = nextText\r\n\r\n if (!chunk) {\r\n const mergedTokens = [...tokenizer.tokensStable, ...tokenizer.tokensUnstable]\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: mergedTokens.length ? tokensToLineTokens(mergedTokens) : [[]],\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n return\r\n }\r\n\r\n try {\r\n await tokenizer.enqueue(chunk)\r\n\r\n const mergedTokens = [...tokenizer.tokensStable, ...tokenizer.tokensUnstable]\r\n\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: tokensToLineTokens(mergedTokens),\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n } catch (err) {\r\n // 静默失败,降级为纯文本\r\n error.value = err as Error\r\n }\r\n } else if (highlighter) {\r\n // 当没有 tokenizer 但有 highlighter 时,使用非流式方式高亮\r\n // 这发生在 shiki 可用但 shiki-stream 不可用时\r\n try {\r\n const currentLang = currentUsedLang || 'plaintext'\r\n const currentTheme = effectiveTheme.value\r\n const tokens = highlighter.codeToTokens(nextText, {\r\n lang: currentLang,\r\n theme: currentTheme,\r\n })\r\n\r\n // shiki 3.x 的 codeToTokens 返回一个对象,包含 tokens、fg、bg 等属性\r\n // 需要从返回对象中提取 tokens 数组\r\n const tokensArray = (tokens as any).tokens || tokens\r\n\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: tokensToLineTokens(tokensArray),\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n } catch (err) {\r\n // 静默降级为纯文本\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[{ content: nextText }]],\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n }\r\n }\r\n }\r\n\r\n const initHighlighter = async () => {\r\n isLoading.value = true\r\n error.value = null\r\n\r\n let currentLang = effectiveLanguage.value\r\n const currentTheme = effectiveTheme.value\r\n const cacheKey = getHighlighterCacheKey(currentTheme)\r\n\r\n try {\r\n const mod = await loadShiki()\r\n if (!mod) {\r\n // shiki 完全不可用,降级为纯文本\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[{ content: text.value }]],\r\n preStyle: undefined,\r\n }\r\n showShikiHint()\r\n showShikiStreamHint()\r\n return\r\n }\r\n\r\n // 检查缓存,如果已有相同主题的 highlighter,直接复用\r\n // 这避免了 Shiki 单例警告并提高了性能\r\n console.log('%c[x-markdown] Shiki loaded successfully', 'background: #0b0; color: #fff; padding: 2px 4px;')\r\n if (!highlighterCache.has(cacheKey)) {\r\n highlighter = await mod.createHighlighter({\r\n themes: [currentTheme],\r\n langs: [], // 将动态加载语言\r\n })\r\n highlighterCache.set(cacheKey, highlighter)\r\n } else {\r\n highlighter = highlighterCache.get(cacheKey)\r\n }\r\n\r\n lastRequestedLang = currentLang\r\n\r\n // 尝试加载请求的语言,失败则降级为纯文本\r\n try {\r\n await highlighter.loadLanguage(currentLang as any)\r\n currentUsedLang = currentLang\r\n } catch {\r\n currentLang = 'plaintext'\r\n currentUsedLang = 'plaintext'\r\n }\r\n\r\n // 动态加载 shiki-stream\r\n const shikiStreamMod = await loadShikiStream()\r\n if (shikiStreamMod) {\r\n tokenizer = new shikiStreamMod.ShikiStreamTokenizer({\r\n highlighter: highlighter,\r\n lang: currentLang,\r\n theme: currentTheme,\r\n })\r\n } else {\r\n // shiki 可用但 shiki-stream 不可用\r\n showShikiStreamHint()\r\n }\r\n\r\n previousText = ''\r\n\r\n const themeInfo = highlighter.getTheme(currentTheme)\r\n const preStyleValue = createPreStyle(themeInfo?.bg, themeInfo?.fg)\r\n\r\n if (text.value) {\r\n await updateTokens(text.value, true)\r\n if (streaming.value) {\r\n streaming.value.preStyle = preStyleValue\r\n }\r\n } else {\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[]],\r\n preStyle: preStyleValue,\r\n }\r\n }\r\n } catch (err) {\r\n // 静默降级\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[{ content: text.value }]],\r\n preStyle: undefined,\r\n }\r\n } finally {\r\n isLoading.value = false\r\n }\r\n }\r\n\r\n watch(\r\n () => [effectiveLanguage.value, effectiveTheme.value],\r\n async ([newLang]) => {\r\n const requestedLang = newLang as string\r\n\r\n if (\r\n highlighter &&\r\n currentUsedLang === 'plaintext' &&\r\n requestedLang !== lastRequestedLang &&\r\n requestedLang !== 'plaintext'\r\n ) {\r\n try {\r\n await highlighter.loadLanguage(requestedLang as any)\r\n initHighlighter()\r\n return\r\n } catch {\r\n lastRequestedLang = requestedLang\r\n return\r\n }\r\n }\r\n\r\n initHighlighter()\r\n },\r\n { immediate: true },\r\n )\r\n\r\n watch(text, async (newText) => {\r\n const requestedLang = effectiveLanguage.value\r\n if (\r\n highlighter &&\r\n currentUsedLang === 'plaintext' &&\r\n requestedLang !== lastRequestedLang &&\r\n requestedLang !== 'plaintext'\r\n ) {\r\n try {\r\n await highlighter.loadLanguage(requestedLang as any)\r\n await initHighlighter()\r\n return\r\n } catch {\r\n lastRequestedLang = requestedLang\r\n }\r\n }\r\n\r\n if (tokenizer || highlighter) {\r\n // 当有 tokenizer 或 highlighter 时都调用 updateTokens\r\n // updateTokens 内部会处理两种情况\r\n updateTokens(newText)\r\n } else {\r\n // 两者都没有时降级为纯文本\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[{ content: newText }]],\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n }\r\n })\r\n\r\n onUnmounted(() => {\r\n tokenizer?.clear()\r\n tokenizer = null\r\n previousText = ''\r\n })\r\n\r\n return {\r\n streaming,\r\n lines,\r\n preStyle,\r\n isLoading,\r\n error,\r\n }\r\n}\r\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"x-markdown.es7.js","sources":["../src/hooks/useHighlight.ts"],"sourcesContent":["import {\r\n ref,\r\n watch,\r\n onUnmounted,\r\n computed,\r\n isRef,\r\n toValue,\r\n type Ref,\r\n type MaybeRefOrGetter,\r\n type CSSProperties,\r\n} from 'vue'\r\n\r\n// 获取是否启用控制台提示的辅助函数\r\nconst consoleHintsEnabled = () => {\r\n if (typeof __X_MARKDOWN_CONSOLE_HINTS_ENABLED__ === 'boolean') {\r\n return __X_MARKDOWN_CONSOLE_HINTS_ENABLED__\r\n }\r\n return true // 默认启用\r\n}\r\n\r\nexport interface HighlightToken {\r\n content?: string\r\n color?: string\r\n fontStyle?: 'italic' | null\r\n fontWeight?: 'normal' | 'bold' | null\r\n htmlStyle?: Record<string, string>\r\n}\r\n\r\ninterface StreamingHighlightResult {\r\n colorReplacements?: Record<string, string>\r\n lines: HighlightToken[][]\r\n preStyle?: CSSProperties\r\n}\r\n\r\ninterface UseHighlightOptions {\r\n language: MaybeRefOrGetter<string>\r\n theme?: string | Ref<string>\r\n colorReplacements?: Record<string, string>\r\n}\r\n\r\nlet shikiModulePromise: Promise<any | null> | null = null\r\nlet shikiStreamModulePromise: Promise<any | null> | null = null\r\nlet hasShownShikiHint = false\r\nlet hasShownShikiStreamHint = false\r\n\r\n// 全局 highlighter 缓存,避免重复创建实例\r\nconst highlighterCache = new Map<string, any>()\r\nconst getHighlighterCacheKey = (theme: string) => `highlighter-${theme}`\r\n\r\nconst showShikiHint = () => {\r\n if (hasShownShikiHint) return\r\n if (!consoleHintsEnabled()) return\r\n\r\n hasShownShikiHint = true\r\n\r\n console.log(\r\n '%c[x-markdown]%c 需安装 shiki: %cpnpm add shiki%c',\r\n 'font-weight: bold; color: #0066cc;',\r\n 'color: #666;',\r\n 'color: #00aa00; font-family: monospace;',\r\n 'color: #999;',\r\n )\r\n}\r\n\r\nconst showShikiStreamHint = () => {\r\n if (hasShownShikiStreamHint) return\r\n if (!consoleHintsEnabled()) return\r\n\r\n hasShownShikiStreamHint = true\r\n\r\n console.log(\r\n '%c[x-markdown]%c AI 流式可选: %cpnpm add shiki-stream%c (需先装 shiki)',\r\n 'font-weight: bold; color: #0066cc;',\r\n 'color: #666;',\r\n 'color: #00aa00; font-family: monospace;',\r\n 'color: #999;',\r\n )\r\n}\r\n\r\nconst loadShiki = async () => {\r\n if (!shikiModulePromise) {\r\n shikiModulePromise = (async () => {\r\n try {\r\n const mod = await import('shiki')\r\n // 检查是否是虚拟模块(虚拟模块返回 { default: null })\r\n if (mod && (mod as any).default === null) {\r\n return null\r\n }\r\n // 检查必要的方法是否存在,确保模块可用\r\n if (!mod || !(mod.createHighlighter || (mod as any).getHighlighter)) {\r\n return null\r\n }\r\n return mod\r\n } catch {\r\n // 静默失败,返回 null\r\n return null\r\n }\r\n })()\r\n }\r\n return shikiModulePromise\r\n}\r\n\r\nconst loadShikiStream = async () => {\r\n if (!shikiStreamModulePromise) {\r\n shikiStreamModulePromise = (async () => {\r\n try {\r\n const mod = await import('shiki-stream')\r\n // 检查是否是虚拟模块(虚拟模块返回 { default: null })\r\n if (mod && (mod as any).default === null) {\r\n return null\r\n }\r\n // 检查必要的方法是否存在\r\n if (!mod || !(mod as any).ShikiStreamTokenizer) {\r\n return null\r\n }\r\n return mod\r\n } catch {\r\n // 静默失败,返回 null\r\n return null\r\n }\r\n })()\r\n }\r\n return shikiStreamModulePromise\r\n}\r\n\r\nconst tokensToLineTokens = (tokens: HighlightToken[] | HighlightToken[][]): HighlightToken[][] => {\r\n if (!tokens.length) return [[]]\r\n\r\n // 检查是否已经是二维数组(shiki 3.x codeToTokens 的返回格式)\r\n if (Array.isArray(tokens[0])) {\r\n return tokens as HighlightToken[][]\r\n }\r\n\r\n // 处理一维数组(shiki-stream 的格式)\r\n const lines: HighlightToken[][] = [[]]\r\n let currentLine = lines[0]\r\n\r\n const startNewLine = () => {\r\n currentLine = []\r\n lines.push(currentLine)\r\n }\r\n\r\n ;(tokens as HighlightToken[]).forEach((token) => {\r\n const content = token.content ?? ''\r\n\r\n if (content === '\\n') {\r\n startNewLine()\r\n return\r\n }\r\n\r\n if (!content.includes('\\n')) {\r\n currentLine.push(token)\r\n return\r\n }\r\n\r\n const segments = content.split('\\n')\r\n segments.forEach((segment: string, index: number) => {\r\n if (segment) {\r\n currentLine.push({\r\n ...token,\r\n content: segment,\r\n })\r\n }\r\n\r\n if (index < segments.length - 1) {\r\n startNewLine()\r\n }\r\n })\r\n })\r\n\r\n return lines.length === 0 ? [[]] : lines\r\n}\r\n\r\nconst createPreStyle = (bg?: string, fg?: string): CSSProperties | undefined => {\r\n if (!bg && !fg) return undefined\r\n return {\r\n backgroundColor: bg,\r\n color: fg,\r\n }\r\n}\r\n\r\nexport function useHighlight(text: Ref<string>, options: UseHighlightOptions) {\r\n const streaming = ref<StreamingHighlightResult>()\r\n const isLoading = ref(false)\r\n const error = ref<Error | null>(null)\r\n\r\n let tokenizer: any | null = null\r\n let previousText = ''\r\n let highlighter: any | null = null\r\n let currentUsedLang = ''\r\n let lastRequestedLang = ''\r\n\r\n const effectiveTheme = computed(() => {\r\n const theme = isRef(options.theme) ? options.theme.value : options.theme\r\n return theme || 'slack-dark'\r\n })\r\n\r\n const effectiveLanguage = computed(() => {\r\n return toValue(options.language) || 'text'\r\n })\r\n\r\n const lines = computed(() => streaming.value?.lines || [[]])\r\n const preStyle = computed(() => streaming.value?.preStyle)\r\n\r\n const updateTokens = async (nextText: string, forceReset = false) => {\r\n // 当有 tokenizer 时使用流式处理\r\n if (tokenizer) {\r\n if (forceReset) {\r\n tokenizer.clear()\r\n previousText = ''\r\n }\r\n\r\n const canAppend = !forceReset && nextText.startsWith(previousText)\r\n let chunk = nextText\r\n\r\n if (canAppend) {\r\n chunk = nextText.slice(previousText.length)\r\n } else if (!forceReset) {\r\n tokenizer.clear()\r\n }\r\n\r\n previousText = nextText\r\n\r\n if (!chunk) {\r\n const mergedTokens = [...tokenizer.tokensStable, ...tokenizer.tokensUnstable]\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: mergedTokens.length ? tokensToLineTokens(mergedTokens) : [[]],\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n return\r\n }\r\n\r\n try {\r\n await tokenizer.enqueue(chunk)\r\n\r\n const mergedTokens = [...tokenizer.tokensStable, ...tokenizer.tokensUnstable]\r\n\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: tokensToLineTokens(mergedTokens),\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n } catch (err) {\r\n // 静默失败,降级为纯文本\r\n error.value = err as Error\r\n }\r\n } else if (highlighter) {\r\n // 当没有 tokenizer 但有 highlighter 时,使用非流式方式高亮\r\n // 这发生在 shiki 可用但 shiki-stream 不可用时\r\n try {\r\n const currentLang = currentUsedLang || 'plaintext'\r\n const currentTheme = effectiveTheme.value\r\n const tokens = highlighter.codeToTokens(nextText, {\r\n lang: currentLang,\r\n theme: currentTheme,\r\n })\r\n\r\n // shiki 3.x 的 codeToTokens 返回一个对象,包含 tokens、fg、bg 等属性\r\n // 需要从返回对象中提取 tokens 数组\r\n const tokensArray = (tokens as any).tokens || tokens\r\n\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: tokensToLineTokens(tokensArray),\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n } catch (err) {\r\n // 静默降级为纯文本\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[{ content: nextText }]],\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n }\r\n }\r\n }\r\n\r\n const initHighlighter = async () => {\r\n isLoading.value = true\r\n error.value = null\r\n\r\n let currentLang = effectiveLanguage.value\r\n const currentTheme = effectiveTheme.value\r\n const cacheKey = getHighlighterCacheKey(currentTheme)\r\n\r\n try {\r\n const mod = await loadShiki()\r\n if (!mod) {\r\n // shiki 完全不可用,降级为纯文本\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[{ content: text.value }]],\r\n preStyle: undefined,\r\n }\r\n showShikiHint()\r\n showShikiStreamHint()\r\n return\r\n }\r\n\r\n // 检查缓存,如果已有相同主题的 highlighter,直接复用\r\n // 这避免了 Shiki 单例警告并提高了性能\r\n if (!highlighterCache.has(cacheKey)) {\r\n highlighter = await mod.createHighlighter({\r\n themes: [currentTheme],\r\n langs: [], // 将动态加载语言\r\n })\r\n highlighterCache.set(cacheKey, highlighter)\r\n } else {\r\n highlighter = highlighterCache.get(cacheKey)\r\n }\r\n\r\n lastRequestedLang = currentLang\r\n\r\n // 尝试加载请求的语言,失败则降级为纯文本\r\n try {\r\n await highlighter.loadLanguage(currentLang as any)\r\n currentUsedLang = currentLang\r\n } catch {\r\n currentLang = 'plaintext'\r\n currentUsedLang = 'plaintext'\r\n }\r\n\r\n // 动态加载 shiki-stream\r\n const shikiStreamMod = await loadShikiStream()\r\n if (shikiStreamMod) {\r\n tokenizer = new shikiStreamMod.ShikiStreamTokenizer({\r\n highlighter: highlighter,\r\n lang: currentLang,\r\n theme: currentTheme,\r\n })\r\n } else {\r\n // shiki 可用但 shiki-stream 不可用\r\n showShikiStreamHint()\r\n }\r\n\r\n previousText = ''\r\n\r\n const themeInfo = highlighter.getTheme(currentTheme)\r\n const preStyleValue = createPreStyle(themeInfo?.bg, themeInfo?.fg)\r\n\r\n if (text.value) {\r\n await updateTokens(text.value, true)\r\n if (streaming.value) {\r\n streaming.value.preStyle = preStyleValue\r\n }\r\n } else {\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[]],\r\n preStyle: preStyleValue,\r\n }\r\n }\r\n } catch (err) {\r\n // 静默降级\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[{ content: text.value }]],\r\n preStyle: undefined,\r\n }\r\n } finally {\r\n isLoading.value = false\r\n }\r\n }\r\n\r\n watch(\r\n () => [effectiveLanguage.value, effectiveTheme.value],\r\n async ([newLang]) => {\r\n const requestedLang = newLang as string\r\n\r\n if (\r\n highlighter &&\r\n currentUsedLang === 'plaintext' &&\r\n requestedLang !== lastRequestedLang &&\r\n requestedLang !== 'plaintext'\r\n ) {\r\n try {\r\n await highlighter.loadLanguage(requestedLang as any)\r\n initHighlighter()\r\n return\r\n } catch {\r\n lastRequestedLang = requestedLang\r\n return\r\n }\r\n }\r\n\r\n initHighlighter()\r\n },\r\n { immediate: true },\r\n )\r\n\r\n watch(text, async (newText) => {\r\n const requestedLang = effectiveLanguage.value\r\n if (\r\n highlighter &&\r\n currentUsedLang === 'plaintext' &&\r\n requestedLang !== lastRequestedLang &&\r\n requestedLang !== 'plaintext'\r\n ) {\r\n try {\r\n await highlighter.loadLanguage(requestedLang as any)\r\n await initHighlighter()\r\n return\r\n } catch {\r\n lastRequestedLang = requestedLang\r\n }\r\n }\r\n\r\n if (tokenizer || highlighter) {\r\n // 当有 tokenizer 或 highlighter 时都调用 updateTokens\r\n // updateTokens 内部会处理两种情况\r\n updateTokens(newText)\r\n } else {\r\n // 两者都没有时降级为纯文本\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[{ content: newText }]],\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n }\r\n })\r\n\r\n onUnmounted(() => {\r\n tokenizer?.clear()\r\n tokenizer = null\r\n previousText = ''\r\n })\r\n\r\n return {\r\n streaming,\r\n lines,\r\n preStyle,\r\n isLoading,\r\n error,\r\n }\r\n}\r\n"],"names":[],"mappings":"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"}
|
package/dist/x-markdown.es9.js
CHANGED
|
@@ -118,7 +118,12 @@ async function loadMermaid() {
|
|
|
118
118
|
showMermaidHint();
|
|
119
119
|
return null;
|
|
120
120
|
}
|
|
121
|
-
|
|
121
|
+
const mermaidInstance = mod?.default;
|
|
122
|
+
if (!mermaidInstance || typeof mermaidInstance.initialize !== "function") {
|
|
123
|
+
showMermaidHint();
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
return mermaidInstance;
|
|
122
127
|
} catch {
|
|
123
128
|
showMermaidHint();
|
|
124
129
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"x-markdown.es9.js","sources":["../src/hooks/useMermaid.ts"],"sourcesContent":["import type { Ref } from 'vue'\r\nimport { throttle } from 'lodash-es'\r\nimport { computed, ref, watch, onUnmounted } from 'vue'\r\nimport type { MermaidZoomControls, UseMermaidZoomOptions, UseMermaidResult } from '../components/Mermaid/types'\r\n\r\n// 获取是否启用控制台提示的辅助函数\r\nconst consoleHintsEnabled = () => {\r\n if (typeof __X_MARKDOWN_CONSOLE_HINTS_ENABLED__ === 'boolean') {\r\n return __X_MARKDOWN_CONSOLE_HINTS_ENABLED__\r\n }\r\n return true // 默认启用\r\n}\r\n\r\nexport function downloadSvgAsPng(svg: string): void {\r\n if (!svg) return\r\n\r\n try {\r\n const svgDataUrl = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`\r\n const img = new Image()\r\n\r\n img.onload = () => {\r\n try {\r\n const canvas = document.createElement('canvas')\r\n const ctx = canvas.getContext('2d', { willReadFrequently: false })\r\n if (!ctx) return\r\n\r\n const scale = 2\r\n canvas.width = img.width * scale\r\n canvas.height = img.height * scale\r\n ctx.imageSmoothingEnabled = true\r\n ctx.imageSmoothingQuality = 'high'\r\n\r\n ctx.fillStyle = '#ffffff'\r\n ctx.fillRect(0, 0, canvas.width, canvas.height)\r\n ctx.drawImage(img, 0, 0, canvas.width, canvas.height)\r\n\r\n const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, '-')\r\n\r\n try {\r\n canvas.toBlob(\r\n (blob) => {\r\n if (!blob) return\r\n const url = URL.createObjectURL(blob)\r\n const link = document.createElement('a')\r\n link.href = url\r\n link.download = `mermaid-diagram-${timestamp}.png`\r\n document.body.appendChild(link)\r\n link.click()\r\n document.body.removeChild(link)\r\n URL.revokeObjectURL(url)\r\n },\r\n 'image/png',\r\n 0.95,\r\n )\r\n } catch (toBlobError) {\r\n console.error('Failed to convert canvas to blob:', toBlobError)\r\n try {\r\n const dataUrl = canvas.toDataURL('image/png', 0.95)\r\n const link = document.createElement('a')\r\n link.href = dataUrl\r\n link.download = `mermaid-diagram-${timestamp}.png`\r\n document.body.appendChild(link)\r\n link.click()\r\n document.body.removeChild(link)\r\n } catch (dataUrlError) {\r\n console.error('Failed to convert canvas to data URL:', dataUrlError)\r\n }\r\n }\r\n } catch (canvasError) {\r\n console.error('Canvas operation failed:', canvasError)\r\n }\r\n }\r\n\r\n img.onerror = (error) => {\r\n console.error('Failed to load image:', error)\r\n }\r\n\r\n img.src = svgDataUrl\r\n } catch (error) {\r\n console.error('Failed to download SVG:', error)\r\n }\r\n}\r\n\r\ninterface UseMermaidOptions {\r\n id?: string\r\n theme?: 'default' | 'dark' | 'forest' | 'neutral' | string\r\n config?: any\r\n container?: HTMLElement | Ref<HTMLElement | null> | null\r\n}\r\n\r\ntype UseMermaidOptionsInput = UseMermaidOptions | Ref<UseMermaidOptions>\r\n\r\nlet mermaidPromise: Promise<any> | null = null\r\nlet hasShownMermaidHint = false\r\nlet mermaidAvailableCache: boolean | null = null\r\nlet mermaidCheckPromise: Promise<boolean | null> | null = null\r\n\r\n/**\r\n * 显示 mermaid 安装提示\r\n */\r\nconst showMermaidHint = () => {\r\n if (hasShownMermaidHint) return\r\n if (!consoleHintsEnabled()) return\r\n\r\n hasShownMermaidHint = true\r\n\r\n console.log(\r\n '%c[x-markdown]%c 图表可选: %cpnpm add mermaid%c',\r\n 'font-weight: bold; color: #9333ea;',\r\n 'color: #666;',\r\n 'color: #9333ea; font-family: monospace;',\r\n 'color: #999;'\r\n )\r\n}\r\n\r\n/**\r\n * 同步检查缓存状态(不触发检测)\r\n * @returns 缓存状态,null 表示未检测\r\n */\r\nexport function getMermaidAvailableCache(): boolean | null {\r\n return mermaidAvailableCache\r\n}\r\n\r\n/**\r\n * 检测 mermaid 是否可用(全局缓存,只检测一次)\r\n */\r\nexport async function checkMermaidAvailable(): Promise<boolean> {\r\n // 如果已经有缓存结果,直接返回\r\n if (mermaidAvailableCache !== null) {\r\n return mermaidAvailableCache\r\n }\r\n\r\n // 如果正在检测,返回检测 Promise\r\n if (mermaidCheckPromise) {\r\n return mermaidCheckPromise as Promise<boolean>\r\n }\r\n\r\n // 开始检测\r\n mermaidCheckPromise = (async () => {\r\n try {\r\n const mod = await import('mermaid')\r\n // 检查模块是否有实际的 mermaid 功能\r\n // 虚拟模块返回 { default: null },所以需要检查 default 是否存在且有效\r\n const mermaidInstance = (mod as any)?.default\r\n // 检查是否有 mermaid 的关键方法或属性\r\n const hasMermaidAPI = mermaidInstance && (\r\n typeof mermaidInstance.render === 'function' ||\r\n typeof mermaidInstance.initialize === 'function' ||\r\n typeof mermaidInstance.run === 'function'\r\n )\r\n mermaidAvailableCache = hasMermaidAPI\r\n\r\n // 当 mermaid 不可用时,显示提示\r\n if (!hasMermaidAPI) {\r\n showMermaidHint()\r\n }\r\n\r\n return mermaidAvailableCache\r\n } catch {\r\n // 静默失败,mermaid 不可用\r\n mermaidAvailableCache = false\r\n showMermaidHint()\r\n return false\r\n }\r\n })()\r\n\r\n return mermaidCheckPromise as Promise<boolean>\r\n}\r\n\r\nasync function loadMermaid() {\r\n if (typeof window === 'undefined') return null\r\n if (!mermaidPromise) {\r\n mermaidPromise = (async () => {\r\n try {\r\n const mod = await import('mermaid')\r\n // 检查是否是虚拟模块(虚拟模块返回 { default: null })\r\n if (mod && (mod as any).default === null) {\r\n showMermaidHint()\r\n return null\r\n }\r\n return (mod as any)?.default\r\n } catch {\r\n // 静默失败,显示友好提示\r\n showMermaidHint()\r\n return null\r\n }\r\n })()\r\n }\r\n return mermaidPromise\r\n}\r\n\r\ntype RenderTask = () => Promise<void>\r\nconst renderQueue: RenderTask[] = []\r\nlet isProcessingQueue = false\r\n\r\nasync function processRenderQueue() {\r\n if (isProcessingQueue) return\r\n isProcessingQueue = true\r\n\r\n while (renderQueue.length > 0) {\r\n const task = renderQueue.shift()\r\n if (task) {\r\n try {\r\n await task()\r\n } catch (err) {\r\n console.error('Mermaid render queue error:', err)\r\n }\r\n }\r\n }\r\n\r\n isProcessingQueue = false\r\n}\r\n\r\nfunction addToRenderQueue(task: RenderTask) {\r\n renderQueue.push(task)\r\n processRenderQueue()\r\n}\r\n\r\nexport function useMermaid(content: string | Ref<string>, options: UseMermaidOptionsInput = {}): UseMermaidResult {\r\n const optionsRef = computed(() => (typeof options === 'object' && 'value' in options ? options.value : options))\r\n const mermaidConfig = computed(() => ({\r\n suppressErrorRendering: true,\r\n startOnLoad: false,\r\n securityLevel: 'loose',\r\n theme: optionsRef.value.theme || 'default',\r\n ...(optionsRef.value.config || {}),\r\n }))\r\n const data = ref('')\r\n const error = ref<unknown>(null)\r\n const isLoading = ref(false)\r\n\r\n let isUnmounted = false\r\n\r\n const getRenderContainer = () => {\r\n const containerOption = optionsRef.value.container\r\n if (containerOption) {\r\n return typeof containerOption === 'object' && 'value' in containerOption ? containerOption.value : containerOption\r\n }\r\n return null\r\n }\r\n\r\n const throttledRender = throttle(\r\n () => {\r\n const contentValue = typeof content === 'string' ? content : content.value\r\n if (!contentValue?.trim()) {\r\n data.value = ''\r\n error.value = null\r\n isLoading.value = false\r\n return\r\n }\r\n\r\n isLoading.value = true\r\n\r\n addToRenderQueue(async () => {\r\n if (isUnmounted) return\r\n\r\n try {\r\n const mermaidInstance = await loadMermaid()\r\n if (!mermaidInstance) {\r\n data.value = contentValue\r\n error.value = null\r\n isLoading.value = false\r\n return\r\n }\r\n\r\n mermaidInstance.initialize(mermaidConfig.value)\r\n\r\n const isValid = await mermaidInstance.parse(contentValue.trim())\r\n if (!isValid) {\r\n data.value = ''\r\n error.value = new Error('Mermaid parse error: Invalid syntax')\r\n isLoading.value = false\r\n return\r\n }\r\n\r\n const renderId = `${optionsRef.value.id || 'mermaid'}-${Math.random().toString(36).substring(2, 11)}`\r\n const container = getRenderContainer()\r\n if (!container) {\r\n isLoading.value = false\r\n return\r\n }\r\n\r\n const { svg } = await mermaidInstance.render(renderId, contentValue, container)\r\n data.value = svg\r\n error.value = null\r\n isLoading.value = false\r\n } catch (err) {\r\n // Mermaid render error\r\n data.value = ''\r\n error.value = err\r\n isLoading.value = false\r\n }\r\n })\r\n },\r\n 100,\r\n { leading: false, trailing: true },\r\n )\r\n\r\n watch(\r\n [() => (typeof content === 'string' ? content : content.value), () => mermaidConfig.value],\r\n () => {\r\n throttledRender()\r\n },\r\n { immediate: true },\r\n )\r\n\r\n onUnmounted(() => {\r\n isUnmounted = true\r\n })\r\n\r\n return {\r\n data,\r\n error,\r\n isLoading,\r\n }\r\n}\r\n\r\nexport function useMermaidZoom(options: UseMermaidZoomOptions): MermaidZoomControls {\r\n const { container } = options\r\n\r\n const scale = ref(1)\r\n const posX = ref(0)\r\n const posY = ref(0)\r\n const isDragging = ref(false)\r\n\r\n let removeEvents: (() => void) | null = null\r\n\r\n const getSvg = () => container.value?.querySelector('.syntax-mermaid__content svg') as HTMLElement\r\n\r\n const updateTransform = (svg: HTMLElement) => {\r\n svg.style.transformOrigin = 'center center'\r\n svg.style.transform = `translate(${posX.value}px, ${posY.value}px) scale(${scale.value})`\r\n }\r\n\r\n const resetState = () => {\r\n scale.value = 1\r\n posX.value = 0\r\n posY.value = 0\r\n isDragging.value = false\r\n }\r\n\r\n const addInteractionEvents = (containerEl: HTMLElement) => {\r\n let startX = 0\r\n let startY = 0\r\n let isInteractingWithMermaid = false\r\n\r\n const onStart = (clientX: number, clientY: number) => {\r\n isDragging.value = true\r\n startX = clientX - posX.value\r\n startY = clientY - posY.value\r\n document.body.style.userSelect = 'none'\r\n }\r\n\r\n const onMove = (clientX: number, clientY: number) => {\r\n if (isDragging.value && isInteractingWithMermaid) {\r\n posX.value = clientX - startX\r\n posY.value = clientY - startY\r\n const svg = getSvg()\r\n if (svg) {\r\n updateTransform(svg)\r\n }\r\n }\r\n }\r\n\r\n const onEnd = () => {\r\n isDragging.value = false\r\n isInteractingWithMermaid = false\r\n document.body.style.userSelect = ''\r\n }\r\n\r\n const onMouseDown = (e: MouseEvent) => {\r\n if (e.button !== 0) return\r\n if (e.target === containerEl || containerEl.contains(e.target as Node)) {\r\n e.preventDefault()\r\n isInteractingWithMermaid = true\r\n onStart(e.clientX, e.clientY)\r\n }\r\n }\r\n\r\n const onMouseMove = (e: MouseEvent) => {\r\n if (isInteractingWithMermaid) {\r\n onMove(e.clientX, e.clientY)\r\n }\r\n }\r\n\r\n const handleWheelZoom = (e: WheelEvent) => {\r\n const svg = getSvg()\r\n if (!svg) return\r\n\r\n const containerRect = containerEl.getBoundingClientRect()\r\n const svgRect = svg.getBoundingClientRect()\r\n\r\n const mouseX = e.clientX - containerRect.left\r\n const mouseY = e.clientY - containerRect.top\r\n\r\n const svgCenterX = svgRect.left - containerRect.left + svgRect.width / 2\r\n const svgCenterY = svgRect.top - containerRect.top + svgRect.height / 2\r\n\r\n const offsetX = (mouseX - svgCenterX - posX.value) / scale.value\r\n const offsetY = (mouseY - svgCenterY - posY.value) / scale.value\r\n\r\n const delta = e.deltaY > 0 ? -0.05 : 0.05\r\n const newScale = Math.min(Math.max(scale.value + delta, 0.1), 10)\r\n\r\n if (newScale === scale.value) return\r\n\r\n scale.value = newScale\r\n\r\n posX.value = mouseX - svgCenterX - offsetX * scale.value\r\n posY.value = mouseY - svgCenterY - offsetY * scale.value\r\n\r\n updateTransform(svg)\r\n }\r\n\r\n const throttledWheelZoom = throttle(handleWheelZoom, 20, { leading: true, trailing: true })\r\n\r\n const onWheel = (e: WheelEvent) => {\r\n if (e.target === containerEl || containerEl.contains(e.target as Node)) {\r\n e.preventDefault()\r\n throttledWheelZoom(e)\r\n }\r\n }\r\n\r\n const onTouchStart = (e: TouchEvent) => {\r\n if (e.target === containerEl || containerEl.contains(e.target as Node)) {\r\n if (e.touches.length === 1) {\r\n e.preventDefault()\r\n isInteractingWithMermaid = true\r\n onStart(e.touches[0].clientX, e.touches[0].clientY)\r\n }\r\n }\r\n }\r\n\r\n const onTouchMove = (e: TouchEvent) => {\r\n if (isInteractingWithMermaid) {\r\n e.preventDefault()\r\n onMove(e.touches[0].clientX, e.touches[0].clientY)\r\n }\r\n }\r\n\r\n containerEl.addEventListener('mousedown', onMouseDown)\r\n document.addEventListener('mousemove', onMouseMove)\r\n document.addEventListener('mouseup', onEnd)\r\n containerEl.addEventListener('wheel', onWheel, { passive: false })\r\n containerEl.addEventListener('touchstart', onTouchStart, { passive: false })\r\n containerEl.addEventListener('touchmove', onTouchMove, { passive: false })\r\n document.addEventListener('touchend', onEnd)\r\n\r\n return () => {\r\n containerEl.removeEventListener('mousedown', onMouseDown)\r\n document.removeEventListener('mousemove', onMouseMove)\r\n document.removeEventListener('mouseup', onEnd)\r\n containerEl.removeEventListener('wheel', onWheel)\r\n containerEl.removeEventListener('touchstart', onTouchStart)\r\n containerEl.removeEventListener('touchmove', onTouchMove)\r\n document.removeEventListener('touchend', onEnd)\r\n document.body.style.userSelect = ''\r\n }\r\n }\r\n\r\n const zoomIn = () => {\r\n const svg = getSvg()\r\n if (svg) {\r\n scale.value = Math.min(scale.value + 0.2, 10)\r\n updateTransform(svg)\r\n }\r\n }\r\n\r\n const zoomOut = () => {\r\n const svg = getSvg()\r\n if (svg) {\r\n scale.value = Math.max(scale.value - 0.2, 0.1)\r\n updateTransform(svg)\r\n }\r\n }\r\n\r\n const reset = () => {\r\n const svg = getSvg()\r\n if (svg) {\r\n resetState()\r\n updateTransform(svg)\r\n }\r\n }\r\n\r\n const fullscreen = () => {\r\n if (!container.value) return\r\n\r\n if (document.fullscreenElement) {\r\n document.exitFullscreen()\r\n } else {\r\n container.value.requestFullscreen?.()\r\n }\r\n }\r\n\r\n const initialize = () => {\r\n if (!container.value) return\r\n\r\n resetState()\r\n\r\n removeEvents = addInteractionEvents(container.value)\r\n\r\n const svg = getSvg()\r\n if (svg) {\r\n updateTransform(svg)\r\n }\r\n }\r\n\r\n const destroy = () => {\r\n removeEvents?.()\r\n removeEvents = null\r\n resetState()\r\n }\r\n\r\n watch(\r\n () => container.value,\r\n () => {\r\n destroy()\r\n resetState()\r\n },\r\n )\r\n\r\n onUnmounted(destroy)\r\n\r\n return {\r\n zoomIn,\r\n zoomOut,\r\n reset,\r\n fullscreen,\r\n destroy,\r\n initialize,\r\n }\r\n}\r\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"x-markdown.es9.js","sources":["../src/hooks/useMermaid.ts"],"sourcesContent":["import type { Ref } from 'vue'\r\nimport { throttle } from 'lodash-es'\r\nimport { computed, ref, watch, onUnmounted } from 'vue'\r\nimport type { MermaidZoomControls, UseMermaidZoomOptions, UseMermaidResult } from '../components/Mermaid/types'\r\n\r\n// 获取是否启用控制台提示的辅助函数\r\nconst consoleHintsEnabled = () => {\r\n if (typeof __X_MARKDOWN_CONSOLE_HINTS_ENABLED__ === 'boolean') {\r\n return __X_MARKDOWN_CONSOLE_HINTS_ENABLED__\r\n }\r\n return true // 默认启用\r\n}\r\n\r\nexport function downloadSvgAsPng(svg: string): void {\r\n if (!svg) return\r\n\r\n try {\r\n const svgDataUrl = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`\r\n const img = new Image()\r\n\r\n img.onload = () => {\r\n try {\r\n const canvas = document.createElement('canvas')\r\n const ctx = canvas.getContext('2d', { willReadFrequently: false })\r\n if (!ctx) return\r\n\r\n const scale = 2\r\n canvas.width = img.width * scale\r\n canvas.height = img.height * scale\r\n ctx.imageSmoothingEnabled = true\r\n ctx.imageSmoothingQuality = 'high'\r\n\r\n ctx.fillStyle = '#ffffff'\r\n ctx.fillRect(0, 0, canvas.width, canvas.height)\r\n ctx.drawImage(img, 0, 0, canvas.width, canvas.height)\r\n\r\n const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, '-')\r\n\r\n try {\r\n canvas.toBlob(\r\n (blob) => {\r\n if (!blob) return\r\n const url = URL.createObjectURL(blob)\r\n const link = document.createElement('a')\r\n link.href = url\r\n link.download = `mermaid-diagram-${timestamp}.png`\r\n document.body.appendChild(link)\r\n link.click()\r\n document.body.removeChild(link)\r\n URL.revokeObjectURL(url)\r\n },\r\n 'image/png',\r\n 0.95,\r\n )\r\n } catch (toBlobError) {\r\n console.error('Failed to convert canvas to blob:', toBlobError)\r\n try {\r\n const dataUrl = canvas.toDataURL('image/png', 0.95)\r\n const link = document.createElement('a')\r\n link.href = dataUrl\r\n link.download = `mermaid-diagram-${timestamp}.png`\r\n document.body.appendChild(link)\r\n link.click()\r\n document.body.removeChild(link)\r\n } catch (dataUrlError) {\r\n console.error('Failed to convert canvas to data URL:', dataUrlError)\r\n }\r\n }\r\n } catch (canvasError) {\r\n console.error('Canvas operation failed:', canvasError)\r\n }\r\n }\r\n\r\n img.onerror = (error) => {\r\n console.error('Failed to load image:', error)\r\n }\r\n\r\n img.src = svgDataUrl\r\n } catch (error) {\r\n console.error('Failed to download SVG:', error)\r\n }\r\n}\r\n\r\ninterface UseMermaidOptions {\r\n id?: string\r\n theme?: 'default' | 'dark' | 'forest' | 'neutral' | string\r\n config?: any\r\n container?: HTMLElement | Ref<HTMLElement | null> | null\r\n}\r\n\r\ntype UseMermaidOptionsInput = UseMermaidOptions | Ref<UseMermaidOptions>\r\n\r\nlet mermaidPromise: Promise<any> | null = null\r\nlet hasShownMermaidHint = false\r\nlet mermaidAvailableCache: boolean | null = null\r\nlet mermaidCheckPromise: Promise<boolean | null> | null = null\r\n\r\n/**\r\n * 显示 mermaid 安装提示\r\n */\r\nconst showMermaidHint = () => {\r\n if (hasShownMermaidHint) return\r\n if (!consoleHintsEnabled()) return\r\n\r\n hasShownMermaidHint = true\r\n\r\n console.log(\r\n '%c[x-markdown]%c 图表可选: %cpnpm add mermaid%c',\r\n 'font-weight: bold; color: #9333ea;',\r\n 'color: #666;',\r\n 'color: #9333ea; font-family: monospace;',\r\n 'color: #999;'\r\n )\r\n}\r\n\r\n/**\r\n * 同步检查缓存状态(不触发检测)\r\n * @returns 缓存状态,null 表示未检测\r\n */\r\nexport function getMermaidAvailableCache(): boolean | null {\r\n return mermaidAvailableCache\r\n}\r\n\r\n/**\r\n * 检测 mermaid 是否可用(全局缓存,只检测一次)\r\n */\r\nexport async function checkMermaidAvailable(): Promise<boolean> {\r\n // 如果已经有缓存结果,直接返回\r\n if (mermaidAvailableCache !== null) {\r\n return mermaidAvailableCache\r\n }\r\n\r\n // 如果正在检测,返回检测 Promise\r\n if (mermaidCheckPromise) {\r\n return mermaidCheckPromise as Promise<boolean>\r\n }\r\n\r\n // 开始检测\r\n mermaidCheckPromise = (async () => {\r\n try {\r\n const mod = await import('mermaid')\r\n // 检查模块是否有实际的 mermaid 功能\r\n // 虚拟模块返回 { default: null },所以需要检查 default 是否存在且有效\r\n const mermaidInstance = (mod as any)?.default\r\n // 检查是否有 mermaid 的关键方法或属性\r\n const hasMermaidAPI = mermaidInstance && (\r\n typeof mermaidInstance.render === 'function' ||\r\n typeof mermaidInstance.initialize === 'function' ||\r\n typeof mermaidInstance.run === 'function'\r\n )\r\n mermaidAvailableCache = hasMermaidAPI\r\n\r\n // 当 mermaid 不可用时,显示提示\r\n if (!hasMermaidAPI) {\r\n showMermaidHint()\r\n }\r\n\r\n return mermaidAvailableCache\r\n } catch {\r\n // 静默失败,mermaid 不可用\r\n mermaidAvailableCache = false\r\n showMermaidHint()\r\n return false\r\n }\r\n })()\r\n\r\n return mermaidCheckPromise as Promise<boolean>\r\n}\r\n\r\nasync function loadMermaid() {\r\n if (typeof window === 'undefined') return null\r\n if (!mermaidPromise) {\r\n mermaidPromise = (async () => {\r\n try {\r\n const mod = await import('mermaid')\r\n // 检查是否是虚拟模块(虚拟模块返回 { default: null })\r\n if (mod && (mod as any).default === null) {\r\n showMermaidHint()\r\n return null\r\n }\r\n \r\n const mermaidInstance = (mod as any)?.default\r\n // 检查是否有 mermaid 的关键方法,确保模块可用\r\n if (!mermaidInstance || typeof mermaidInstance.initialize !== 'function') {\r\n showMermaidHint()\r\n return null\r\n }\r\n \r\n return mermaidInstance\r\n } catch {\r\n // 静默失败,显示友好提示\r\n showMermaidHint()\r\n return null\r\n }\r\n })()\r\n }\r\n return mermaidPromise\r\n}\r\n\r\ntype RenderTask = () => Promise<void>\r\nconst renderQueue: RenderTask[] = []\r\nlet isProcessingQueue = false\r\n\r\nasync function processRenderQueue() {\r\n if (isProcessingQueue) return\r\n isProcessingQueue = true\r\n\r\n while (renderQueue.length > 0) {\r\n const task = renderQueue.shift()\r\n if (task) {\r\n try {\r\n await task()\r\n } catch (err) {\r\n console.error('Mermaid render queue error:', err)\r\n }\r\n }\r\n }\r\n\r\n isProcessingQueue = false\r\n}\r\n\r\nfunction addToRenderQueue(task: RenderTask) {\r\n renderQueue.push(task)\r\n processRenderQueue()\r\n}\r\n\r\nexport function useMermaid(content: string | Ref<string>, options: UseMermaidOptionsInput = {}): UseMermaidResult {\r\n const optionsRef = computed(() => (typeof options === 'object' && 'value' in options ? options.value : options))\r\n const mermaidConfig = computed(() => ({\r\n suppressErrorRendering: true,\r\n startOnLoad: false,\r\n securityLevel: 'loose',\r\n theme: optionsRef.value.theme || 'default',\r\n ...(optionsRef.value.config || {}),\r\n }))\r\n const data = ref('')\r\n const error = ref<unknown>(null)\r\n const isLoading = ref(false)\r\n\r\n let isUnmounted = false\r\n\r\n const getRenderContainer = () => {\r\n const containerOption = optionsRef.value.container\r\n if (containerOption) {\r\n return typeof containerOption === 'object' && 'value' in containerOption ? containerOption.value : containerOption\r\n }\r\n return null\r\n }\r\n\r\n const throttledRender = throttle(\r\n () => {\r\n const contentValue = typeof content === 'string' ? content : content.value\r\n if (!contentValue?.trim()) {\r\n data.value = ''\r\n error.value = null\r\n isLoading.value = false\r\n return\r\n }\r\n\r\n isLoading.value = true\r\n\r\n addToRenderQueue(async () => {\r\n if (isUnmounted) return\r\n\r\n try {\r\n const mermaidInstance = await loadMermaid()\r\n if (!mermaidInstance) {\r\n data.value = contentValue\r\n error.value = null\r\n isLoading.value = false\r\n return\r\n }\r\n\r\n mermaidInstance.initialize(mermaidConfig.value)\r\n\r\n const isValid = await mermaidInstance.parse(contentValue.trim())\r\n if (!isValid) {\r\n data.value = ''\r\n error.value = new Error('Mermaid parse error: Invalid syntax')\r\n isLoading.value = false\r\n return\r\n }\r\n\r\n const renderId = `${optionsRef.value.id || 'mermaid'}-${Math.random().toString(36).substring(2, 11)}`\r\n const container = getRenderContainer()\r\n if (!container) {\r\n isLoading.value = false\r\n return\r\n }\r\n\r\n const { svg } = await mermaidInstance.render(renderId, contentValue, container)\r\n data.value = svg\r\n error.value = null\r\n isLoading.value = false\r\n } catch (err) {\r\n // Mermaid render error\r\n data.value = ''\r\n error.value = err\r\n isLoading.value = false\r\n }\r\n })\r\n },\r\n 100,\r\n { leading: false, trailing: true },\r\n )\r\n\r\n watch(\r\n [() => (typeof content === 'string' ? content : content.value), () => mermaidConfig.value],\r\n () => {\r\n throttledRender()\r\n },\r\n { immediate: true },\r\n )\r\n\r\n onUnmounted(() => {\r\n isUnmounted = true\r\n })\r\n\r\n return {\r\n data,\r\n error,\r\n isLoading,\r\n }\r\n}\r\n\r\nexport function useMermaidZoom(options: UseMermaidZoomOptions): MermaidZoomControls {\r\n const { container } = options\r\n\r\n const scale = ref(1)\r\n const posX = ref(0)\r\n const posY = ref(0)\r\n const isDragging = ref(false)\r\n\r\n let removeEvents: (() => void) | null = null\r\n\r\n const getSvg = () => container.value?.querySelector('.syntax-mermaid__content svg') as HTMLElement\r\n\r\n const updateTransform = (svg: HTMLElement) => {\r\n svg.style.transformOrigin = 'center center'\r\n svg.style.transform = `translate(${posX.value}px, ${posY.value}px) scale(${scale.value})`\r\n }\r\n\r\n const resetState = () => {\r\n scale.value = 1\r\n posX.value = 0\r\n posY.value = 0\r\n isDragging.value = false\r\n }\r\n\r\n const addInteractionEvents = (containerEl: HTMLElement) => {\r\n let startX = 0\r\n let startY = 0\r\n let isInteractingWithMermaid = false\r\n\r\n const onStart = (clientX: number, clientY: number) => {\r\n isDragging.value = true\r\n startX = clientX - posX.value\r\n startY = clientY - posY.value\r\n document.body.style.userSelect = 'none'\r\n }\r\n\r\n const onMove = (clientX: number, clientY: number) => {\r\n if (isDragging.value && isInteractingWithMermaid) {\r\n posX.value = clientX - startX\r\n posY.value = clientY - startY\r\n const svg = getSvg()\r\n if (svg) {\r\n updateTransform(svg)\r\n }\r\n }\r\n }\r\n\r\n const onEnd = () => {\r\n isDragging.value = false\r\n isInteractingWithMermaid = false\r\n document.body.style.userSelect = ''\r\n }\r\n\r\n const onMouseDown = (e: MouseEvent) => {\r\n if (e.button !== 0) return\r\n if (e.target === containerEl || containerEl.contains(e.target as Node)) {\r\n e.preventDefault()\r\n isInteractingWithMermaid = true\r\n onStart(e.clientX, e.clientY)\r\n }\r\n }\r\n\r\n const onMouseMove = (e: MouseEvent) => {\r\n if (isInteractingWithMermaid) {\r\n onMove(e.clientX, e.clientY)\r\n }\r\n }\r\n\r\n const handleWheelZoom = (e: WheelEvent) => {\r\n const svg = getSvg()\r\n if (!svg) return\r\n\r\n const containerRect = containerEl.getBoundingClientRect()\r\n const svgRect = svg.getBoundingClientRect()\r\n\r\n const mouseX = e.clientX - containerRect.left\r\n const mouseY = e.clientY - containerRect.top\r\n\r\n const svgCenterX = svgRect.left - containerRect.left + svgRect.width / 2\r\n const svgCenterY = svgRect.top - containerRect.top + svgRect.height / 2\r\n\r\n const offsetX = (mouseX - svgCenterX - posX.value) / scale.value\r\n const offsetY = (mouseY - svgCenterY - posY.value) / scale.value\r\n\r\n const delta = e.deltaY > 0 ? -0.05 : 0.05\r\n const newScale = Math.min(Math.max(scale.value + delta, 0.1), 10)\r\n\r\n if (newScale === scale.value) return\r\n\r\n scale.value = newScale\r\n\r\n posX.value = mouseX - svgCenterX - offsetX * scale.value\r\n posY.value = mouseY - svgCenterY - offsetY * scale.value\r\n\r\n updateTransform(svg)\r\n }\r\n\r\n const throttledWheelZoom = throttle(handleWheelZoom, 20, { leading: true, trailing: true })\r\n\r\n const onWheel = (e: WheelEvent) => {\r\n if (e.target === containerEl || containerEl.contains(e.target as Node)) {\r\n e.preventDefault()\r\n throttledWheelZoom(e)\r\n }\r\n }\r\n\r\n const onTouchStart = (e: TouchEvent) => {\r\n if (e.target === containerEl || containerEl.contains(e.target as Node)) {\r\n if (e.touches.length === 1) {\r\n e.preventDefault()\r\n isInteractingWithMermaid = true\r\n onStart(e.touches[0].clientX, e.touches[0].clientY)\r\n }\r\n }\r\n }\r\n\r\n const onTouchMove = (e: TouchEvent) => {\r\n if (isInteractingWithMermaid) {\r\n e.preventDefault()\r\n onMove(e.touches[0].clientX, e.touches[0].clientY)\r\n }\r\n }\r\n\r\n containerEl.addEventListener('mousedown', onMouseDown)\r\n document.addEventListener('mousemove', onMouseMove)\r\n document.addEventListener('mouseup', onEnd)\r\n containerEl.addEventListener('wheel', onWheel, { passive: false })\r\n containerEl.addEventListener('touchstart', onTouchStart, { passive: false })\r\n containerEl.addEventListener('touchmove', onTouchMove, { passive: false })\r\n document.addEventListener('touchend', onEnd)\r\n\r\n return () => {\r\n containerEl.removeEventListener('mousedown', onMouseDown)\r\n document.removeEventListener('mousemove', onMouseMove)\r\n document.removeEventListener('mouseup', onEnd)\r\n containerEl.removeEventListener('wheel', onWheel)\r\n containerEl.removeEventListener('touchstart', onTouchStart)\r\n containerEl.removeEventListener('touchmove', onTouchMove)\r\n document.removeEventListener('touchend', onEnd)\r\n document.body.style.userSelect = ''\r\n }\r\n }\r\n\r\n const zoomIn = () => {\r\n const svg = getSvg()\r\n if (svg) {\r\n scale.value = Math.min(scale.value + 0.2, 10)\r\n updateTransform(svg)\r\n }\r\n }\r\n\r\n const zoomOut = () => {\r\n const svg = getSvg()\r\n if (svg) {\r\n scale.value = Math.max(scale.value - 0.2, 0.1)\r\n updateTransform(svg)\r\n }\r\n }\r\n\r\n const reset = () => {\r\n const svg = getSvg()\r\n if (svg) {\r\n resetState()\r\n updateTransform(svg)\r\n }\r\n }\r\n\r\n const fullscreen = () => {\r\n if (!container.value) return\r\n\r\n if (document.fullscreenElement) {\r\n document.exitFullscreen()\r\n } else {\r\n container.value.requestFullscreen?.()\r\n }\r\n }\r\n\r\n const initialize = () => {\r\n if (!container.value) return\r\n\r\n resetState()\r\n\r\n removeEvents = addInteractionEvents(container.value)\r\n\r\n const svg = getSvg()\r\n if (svg) {\r\n updateTransform(svg)\r\n }\r\n }\r\n\r\n const destroy = () => {\r\n removeEvents?.()\r\n removeEvents = null\r\n resetState()\r\n }\r\n\r\n watch(\r\n () => container.value,\r\n () => {\r\n destroy()\r\n resetState()\r\n },\r\n )\r\n\r\n onUnmounted(destroy)\r\n\r\n return {\r\n zoomIn,\r\n zoomOut,\r\n reset,\r\n fullscreen,\r\n destroy,\r\n initialize,\r\n }\r\n}\r\n"],"names":[],"mappings":";;AAMA,MAAM,sBAAsB,MAAM;AAChC,MAAI,OAAO,yCAAyC,WAAW;AAC7D,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEO,SAAS,iBAAiB,KAAmB;AAClD,MAAI,CAAC,IAAK;AAEV,MAAI;AACF,UAAM,aAAa,oCAAoC,mBAAmB,GAAG,CAAC;AAC9E,UAAM,MAAM,IAAI,MAAA;AAEhB,QAAI,SAAS,MAAM;AACjB,UAAI;AACF,cAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,cAAM,MAAM,OAAO,WAAW,MAAM,EAAE,oBAAoB,OAAO;AACjE,YAAI,CAAC,IAAK;AAEV,cAAM,QAAQ;AACd,eAAO,QAAQ,IAAI,QAAQ;AAC3B,eAAO,SAAS,IAAI,SAAS;AAC7B,YAAI,wBAAwB;AAC5B,YAAI,wBAAwB;AAE5B,YAAI,YAAY;AAChB,YAAI,SAAS,GAAG,GAAG,OAAO,OAAO,OAAO,MAAM;AAC9C,YAAI,UAAU,KAAK,GAAG,GAAG,OAAO,OAAO,OAAO,MAAM;AAEpD,cAAM,aAAY,oBAAI,KAAA,GAAO,YAAA,EAAc,MAAM,GAAG,EAAE,EAAE,QAAQ,MAAM,GAAG;AAEzE,YAAI;AACF,iBAAO;AAAA,YACL,CAAC,SAAS;AACR,kBAAI,CAAC,KAAM;AACX,oBAAM,MAAM,IAAI,gBAAgB,IAAI;AACpC,oBAAM,OAAO,SAAS,cAAc,GAAG;AACvC,mBAAK,OAAO;AACZ,mBAAK,WAAW,mBAAmB,SAAS;AAC5C,uBAAS,KAAK,YAAY,IAAI;AAC9B,mBAAK,MAAA;AACL,uBAAS,KAAK,YAAY,IAAI;AAC9B,kBAAI,gBAAgB,GAAG;AAAA,YACzB;AAAA,YACA;AAAA,YACA;AAAA,UAAA;AAAA,QAEJ,SAAS,aAAa;AACpB,kBAAQ,MAAM,qCAAqC,WAAW;AAC9D,cAAI;AACF,kBAAM,UAAU,OAAO,UAAU,aAAa,IAAI;AAClD,kBAAM,OAAO,SAAS,cAAc,GAAG;AACvC,iBAAK,OAAO;AACZ,iBAAK,WAAW,mBAAmB,SAAS;AAC5C,qBAAS,KAAK,YAAY,IAAI;AAC9B,iBAAK,MAAA;AACL,qBAAS,KAAK,YAAY,IAAI;AAAA,UAChC,SAAS,cAAc;AACrB,oBAAQ,MAAM,yCAAyC,YAAY;AAAA,UACrE;AAAA,QACF;AAAA,MACF,SAAS,aAAa;AACpB,gBAAQ,MAAM,4BAA4B,WAAW;AAAA,MACvD;AAAA,IACF;AAEA,QAAI,UAAU,CAAC,UAAU;AACvB,cAAQ,MAAM,yBAAyB,KAAK;AAAA,IAC9C;AAEA,QAAI,MAAM;AAAA,EACZ,SAAS,OAAO;AACd,YAAQ,MAAM,2BAA2B,KAAK;AAAA,EAChD;AACF;AAWA,IAAI,iBAAsC;AAC1C,IAAI,sBAAsB;AAC1B,IAAI,wBAAwC;AAC5C,IAAI,sBAAsD;AAK1D,MAAM,kBAAkB,MAAM;AAC5B,MAAI,oBAAqB;AACzB,MAAI,CAAC,sBAAuB;AAE5B,wBAAsB;AAEtB,UAAQ;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;AAaA,eAAsB,wBAA0C;AAE9D,MAAI,0BAA0B,MAAM;AAClC,WAAO;AAAA,EACT;AAGA,MAAI,qBAAqB;AACvB,WAAO;AAAA,EACT;AAGA,yBAAuB,YAAY;AACjC,QAAI;AACF,YAAM,MAAM,MAAM,OAAO,SAAS;AAGlC,YAAM,kBAAmB,KAAa;AAEtC,YAAM,gBAAgB,oBACpB,OAAO,gBAAgB,WAAW,cAClC,OAAO,gBAAgB,eAAe,cACtC,OAAO,gBAAgB,QAAQ;AAEjC,8BAAwB;AAGxB,UAAI,CAAC,eAAe;AAClB,wBAAA;AAAA,MACF;AAEA,aAAO;AAAA,IACT,QAAQ;AAEN,8BAAwB;AACxB,sBAAA;AACA,aAAO;AAAA,IACT;AAAA,EACF,GAAA;AAEA,SAAO;AACT;AAEA,eAAe,cAAc;AAC3B,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,MAAI,CAAC,gBAAgB;AACnB,sBAAkB,YAAY;AAC5B,UAAI;AACF,cAAM,MAAM,MAAM,OAAO,SAAS;AAElC,YAAI,OAAQ,IAAY,YAAY,MAAM;AACxC,0BAAA;AACA,iBAAO;AAAA,QACT;AAEA,cAAM,kBAAmB,KAAa;AAEtC,YAAI,CAAC,mBAAmB,OAAO,gBAAgB,eAAe,YAAY;AACxE,0BAAA;AACA,iBAAO;AAAA,QACT;AAEA,eAAO;AAAA,MACT,QAAQ;AAEN,wBAAA;AACA,eAAO;AAAA,MACT;AAAA,IACF,GAAA;AAAA,EACF;AACA,SAAO;AACT;AAGA,MAAM,cAA4B,CAAA;AAClC,IAAI,oBAAoB;AAExB,eAAe,qBAAqB;AAClC,MAAI,kBAAmB;AACvB,sBAAoB;AAEpB,SAAO,YAAY,SAAS,GAAG;AAC7B,UAAM,OAAO,YAAY,MAAA;AACzB,QAAI,MAAM;AACR,UAAI;AACF,cAAM,KAAA;AAAA,MACR,SAAS,KAAK;AACZ,gBAAQ,MAAM,+BAA+B,GAAG;AAAA,MAClD;AAAA,IACF;AAAA,EACF;AAEA,sBAAoB;AACtB;AAEA,SAAS,iBAAiB,MAAkB;AAC1C,cAAY,KAAK,IAAI;AACrB,qBAAA;AACF;AAEO,SAAS,WAAW,SAA+B,UAAkC,IAAsB;AAChH,QAAM,aAAa,SAAS,MAAO,OAAO,YAAY,YAAY,WAAW,UAAU,QAAQ,QAAQ,OAAQ;AAC/G,QAAM,gBAAgB,SAAS,OAAO;AAAA,IACpC,wBAAwB;AAAA,IACxB,aAAa;AAAA,IACb,eAAe;AAAA,IACf,OAAO,WAAW,MAAM,SAAS;AAAA,IACjC,GAAI,WAAW,MAAM,UAAU,CAAA;AAAA,EAAC,EAChC;AACF,QAAM,OAAO,IAAI,EAAE;AACnB,QAAM,QAAQ,IAAa,IAAI;AAC/B,QAAM,YAAY,IAAI,KAAK;AAE3B,MAAI,cAAc;AAElB,QAAM,qBAAqB,MAAM;AAC/B,UAAM,kBAAkB,WAAW,MAAM;AACzC,QAAI,iBAAiB;AACnB,aAAO,OAAO,oBAAoB,YAAY,WAAW,kBAAkB,gBAAgB,QAAQ;AAAA,IACrG;AACA,WAAO;AAAA,EACT;AAEA,QAAM,kBAAkB;AAAA,IACtB,MAAM;AACJ,YAAM,eAAe,OAAO,YAAY,WAAW,UAAU,QAAQ;AACrE,UAAI,CAAC,cAAc,QAAQ;AACzB,aAAK,QAAQ;AACb,cAAM,QAAQ;AACd,kBAAU,QAAQ;AAClB;AAAA,MACF;AAEA,gBAAU,QAAQ;AAElB,uBAAiB,YAAY;AAC3B,YAAI,YAAa;AAEjB,YAAI;AACF,gBAAM,kBAAkB,MAAM,YAAA;AAC9B,cAAI,CAAC,iBAAiB;AACpB,iBAAK,QAAQ;AACb,kBAAM,QAAQ;AACd,sBAAU,QAAQ;AAClB;AAAA,UACF;AAEA,0BAAgB,WAAW,cAAc,KAAK;AAE9C,gBAAM,UAAU,MAAM,gBAAgB,MAAM,aAAa,MAAM;AAC/D,cAAI,CAAC,SAAS;AACZ,iBAAK,QAAQ;AACb,kBAAM,QAAQ,IAAI,MAAM,qCAAqC;AAC7D,sBAAU,QAAQ;AAClB;AAAA,UACF;AAEA,gBAAM,WAAW,GAAG,WAAW,MAAM,MAAM,SAAS,IAAI,KAAK,OAAA,EAAS,SAAS,EAAE,EAAE,UAAU,GAAG,EAAE,CAAC;AACnG,gBAAM,YAAY,mBAAA;AAClB,cAAI,CAAC,WAAW;AACd,sBAAU,QAAQ;AAClB;AAAA,UACF;AAEA,gBAAM,EAAE,QAAQ,MAAM,gBAAgB,OAAO,UAAU,cAAc,SAAS;AAC9E,eAAK,QAAQ;AACb,gBAAM,QAAQ;AACd,oBAAU,QAAQ;AAAA,QACpB,SAAS,KAAK;AAEZ,eAAK,QAAQ;AACb,gBAAM,QAAQ;AACd,oBAAU,QAAQ;AAAA,QACpB;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IACA;AAAA,IACA,EAAE,SAAS,OAAO,UAAU,KAAA;AAAA,EAAK;AAGnC;AAAA,IACE,CAAC,MAAO,OAAO,YAAY,WAAW,UAAU,QAAQ,OAAQ,MAAM,cAAc,KAAK;AAAA,IACzF,MAAM;AACJ,sBAAA;AAAA,IACF;AAAA,IACA,EAAE,WAAW,KAAA;AAAA,EAAK;AAGpB,cAAY,MAAM;AAChB,kBAAc;AAAA,EAChB,CAAC;AAED,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;AAEO,SAAS,eAAe,SAAqD;AAClF,QAAM,EAAE,cAAc;AAEtB,QAAM,QAAQ,IAAI,CAAC;AACnB,QAAM,OAAO,IAAI,CAAC;AAClB,QAAM,OAAO,IAAI,CAAC;AAClB,QAAM,aAAa,IAAI,KAAK;AAE5B,MAAI,eAAoC;AAExC,QAAM,SAAS,MAAM,UAAU,OAAO,cAAc,8BAA8B;AAElF,QAAM,kBAAkB,CAAC,QAAqB;AAC5C,QAAI,MAAM,kBAAkB;AAC5B,QAAI,MAAM,YAAY,aAAa,KAAK,KAAK,OAAO,KAAK,KAAK,aAAa,MAAM,KAAK;AAAA,EACxF;AAEA,QAAM,aAAa,MAAM;AACvB,UAAM,QAAQ;AACd,SAAK,QAAQ;AACb,SAAK,QAAQ;AACb,eAAW,QAAQ;AAAA,EACrB;AAEA,QAAM,uBAAuB,CAAC,gBAA6B;AACzD,QAAI,SAAS;AACb,QAAI,SAAS;AACb,QAAI,2BAA2B;AAE/B,UAAM,UAAU,CAAC,SAAiB,YAAoB;AACpD,iBAAW,QAAQ;AACnB,eAAS,UAAU,KAAK;AACxB,eAAS,UAAU,KAAK;AACxB,eAAS,KAAK,MAAM,aAAa;AAAA,IACnC;AAEA,UAAM,SAAS,CAAC,SAAiB,YAAoB;AACnD,UAAI,WAAW,SAAS,0BAA0B;AAChD,aAAK,QAAQ,UAAU;AACvB,aAAK,QAAQ,UAAU;AACvB,cAAM,MAAM,OAAA;AACZ,YAAI,KAAK;AACP,0BAAgB,GAAG;AAAA,QACrB;AAAA,MACF;AAAA,IACF;AAEA,UAAM,QAAQ,MAAM;AAClB,iBAAW,QAAQ;AACnB,iCAA2B;AAC3B,eAAS,KAAK,MAAM,aAAa;AAAA,IACnC;AAEA,UAAM,cAAc,CAAC,MAAkB;AACrC,UAAI,EAAE,WAAW,EAAG;AACpB,UAAI,EAAE,WAAW,eAAe,YAAY,SAAS,EAAE,MAAc,GAAG;AACtE,UAAE,eAAA;AACF,mCAA2B;AAC3B,gBAAQ,EAAE,SAAS,EAAE,OAAO;AAAA,MAC9B;AAAA,IACF;AAEA,UAAM,cAAc,CAAC,MAAkB;AACrC,UAAI,0BAA0B;AAC5B,eAAO,EAAE,SAAS,EAAE,OAAO;AAAA,MAC7B;AAAA,IACF;AAEA,UAAM,kBAAkB,CAAC,MAAkB;AACzC,YAAM,MAAM,OAAA;AACZ,UAAI,CAAC,IAAK;AAEV,YAAM,gBAAgB,YAAY,sBAAA;AAClC,YAAM,UAAU,IAAI,sBAAA;AAEpB,YAAM,SAAS,EAAE,UAAU,cAAc;AACzC,YAAM,SAAS,EAAE,UAAU,cAAc;AAEzC,YAAM,aAAa,QAAQ,OAAO,cAAc,OAAO,QAAQ,QAAQ;AACvE,YAAM,aAAa,QAAQ,MAAM,cAAc,MAAM,QAAQ,SAAS;AAEtE,YAAM,WAAW,SAAS,aAAa,KAAK,SAAS,MAAM;AAC3D,YAAM,WAAW,SAAS,aAAa,KAAK,SAAS,MAAM;AAE3D,YAAM,QAAQ,EAAE,SAAS,IAAI,QAAQ;AACrC,YAAM,WAAW,KAAK,IAAI,KAAK,IAAI,MAAM,QAAQ,OAAO,GAAG,GAAG,EAAE;AAEhE,UAAI,aAAa,MAAM,MAAO;AAE9B,YAAM,QAAQ;AAEd,WAAK,QAAQ,SAAS,aAAa,UAAU,MAAM;AACnD,WAAK,QAAQ,SAAS,aAAa,UAAU,MAAM;AAEnD,sBAAgB,GAAG;AAAA,IACrB;AAEA,UAAM,qBAAqB,SAAS,iBAAiB,IAAI,EAAE,SAAS,MAAM,UAAU,MAAM;AAE1F,UAAM,UAAU,CAAC,MAAkB;AACjC,UAAI,EAAE,WAAW,eAAe,YAAY,SAAS,EAAE,MAAc,GAAG;AACtE,UAAE,eAAA;AACF,2BAAmB,CAAC;AAAA,MACtB;AAAA,IACF;AAEA,UAAM,eAAe,CAAC,MAAkB;AACtC,UAAI,EAAE,WAAW,eAAe,YAAY,SAAS,EAAE,MAAc,GAAG;AACtE,YAAI,EAAE,QAAQ,WAAW,GAAG;AAC1B,YAAE,eAAA;AACF,qCAA2B;AAC3B,kBAAQ,EAAE,QAAQ,CAAC,EAAE,SAAS,EAAE,QAAQ,CAAC,EAAE,OAAO;AAAA,QACpD;AAAA,MACF;AAAA,IACF;AAEA,UAAM,cAAc,CAAC,MAAkB;AACrC,UAAI,0BAA0B;AAC5B,UAAE,eAAA;AACF,eAAO,EAAE,QAAQ,CAAC,EAAE,SAAS,EAAE,QAAQ,CAAC,EAAE,OAAO;AAAA,MACnD;AAAA,IACF;AAEA,gBAAY,iBAAiB,aAAa,WAAW;AACrD,aAAS,iBAAiB,aAAa,WAAW;AAClD,aAAS,iBAAiB,WAAW,KAAK;AAC1C,gBAAY,iBAAiB,SAAS,SAAS,EAAE,SAAS,OAAO;AACjE,gBAAY,iBAAiB,cAAc,cAAc,EAAE,SAAS,OAAO;AAC3E,gBAAY,iBAAiB,aAAa,aAAa,EAAE,SAAS,OAAO;AACzE,aAAS,iBAAiB,YAAY,KAAK;AAE3C,WAAO,MAAM;AACX,kBAAY,oBAAoB,aAAa,WAAW;AACxD,eAAS,oBAAoB,aAAa,WAAW;AACrD,eAAS,oBAAoB,WAAW,KAAK;AAC7C,kBAAY,oBAAoB,SAAS,OAAO;AAChD,kBAAY,oBAAoB,cAAc,YAAY;AAC1D,kBAAY,oBAAoB,aAAa,WAAW;AACxD,eAAS,oBAAoB,YAAY,KAAK;AAC9C,eAAS,KAAK,MAAM,aAAa;AAAA,IACnC;AAAA,EACF;AAEA,QAAM,SAAS,MAAM;AACnB,UAAM,MAAM,OAAA;AACZ,QAAI,KAAK;AACP,YAAM,QAAQ,KAAK,IAAI,MAAM,QAAQ,KAAK,EAAE;AAC5C,sBAAgB,GAAG;AAAA,IACrB;AAAA,EACF;AAEA,QAAM,UAAU,MAAM;AACpB,UAAM,MAAM,OAAA;AACZ,QAAI,KAAK;AACP,YAAM,QAAQ,KAAK,IAAI,MAAM,QAAQ,KAAK,GAAG;AAC7C,sBAAgB,GAAG;AAAA,IACrB;AAAA,EACF;AAEA,QAAM,QAAQ,MAAM;AAClB,UAAM,MAAM,OAAA;AACZ,QAAI,KAAK;AACP,iBAAA;AACA,sBAAgB,GAAG;AAAA,IACrB;AAAA,EACF;AAEA,QAAM,aAAa,MAAM;AACvB,QAAI,CAAC,UAAU,MAAO;AAEtB,QAAI,SAAS,mBAAmB;AAC9B,eAAS,eAAA;AAAA,IACX,OAAO;AACL,gBAAU,MAAM,oBAAA;AAAA,IAClB;AAAA,EACF;AAEA,QAAM,aAAa,MAAM;AACvB,QAAI,CAAC,UAAU,MAAO;AAEtB,eAAA;AAEA,mBAAe,qBAAqB,UAAU,KAAK;AAEnD,UAAM,MAAM,OAAA;AACZ,QAAI,KAAK;AACP,sBAAgB,GAAG;AAAA,IACrB;AAAA,EACF;AAEA,QAAM,UAAU,MAAM;AACpB,mBAAA;AACA,mBAAe;AACf,eAAA;AAAA,EACF;AAEA;AAAA,IACE,MAAM,UAAU;AAAA,IAChB,MAAM;AACJ,cAAA;AACA,iBAAA;AAAA,IACF;AAAA,EAAA;AAGF,cAAY,OAAO;AAEnB,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;"}
|