feat: 支持解析 plantUML
This commit is contained in:
parent
a8c8a72bd6
commit
bb7f6e2772
7
package-lock.json
generated
7
package-lock.json
generated
@ -14,6 +14,7 @@
|
|||||||
"@tauri-apps/plugin-opener": "^2",
|
"@tauri-apps/plugin-opener": "^2",
|
||||||
"@vavt/v3-extension": "^3.0.0",
|
"@vavt/v3-extension": "^3.0.0",
|
||||||
"md-editor-v3": "^5.5.0",
|
"md-editor-v3": "^5.5.0",
|
||||||
|
"plantuml-encoder": "^1.4.0",
|
||||||
"scriptjs": "^2.5.9",
|
"scriptjs": "^2.5.9",
|
||||||
"view-ui-plus": "^1.3.19",
|
"view-ui-plus": "^1.3.19",
|
||||||
"vue": "^3.5.13"
|
"vue": "^3.5.13"
|
||||||
@ -2091,6 +2092,12 @@
|
|||||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/plantuml-encoder": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/plantuml-encoder/-/plantuml-encoder-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-sxMwpDw/ySY1WB2CE3+IdMuEcWibJ72DDOsXLkSmEaSzwEUaYBT6DWgOfBiHGCux4q433X6+OEFWjlVqp7gL6g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/popper.js": {
|
"node_modules/popper.js": {
|
||||||
"version": "1.16.1",
|
"version": "1.16.1",
|
||||||
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
|
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
|
||||||
|
|||||||
@ -16,6 +16,7 @@
|
|||||||
"@tauri-apps/plugin-opener": "^2",
|
"@tauri-apps/plugin-opener": "^2",
|
||||||
"@vavt/v3-extension": "^3.0.0",
|
"@vavt/v3-extension": "^3.0.0",
|
||||||
"md-editor-v3": "^5.5.0",
|
"md-editor-v3": "^5.5.0",
|
||||||
|
"plantuml-encoder": "^1.4.0",
|
||||||
"scriptjs": "^2.5.9",
|
"scriptjs": "^2.5.9",
|
||||||
"view-ui-plus": "^1.3.19",
|
"view-ui-plus": "^1.3.19",
|
||||||
"vue": "^3.5.13"
|
"vue": "^3.5.13"
|
||||||
|
|||||||
@ -3,8 +3,8 @@
|
|||||||
<template #left>
|
<template #left>
|
||||||
<MdEditor ref="editorRef" v-model="editorState.text" :id="editorState.id" :theme="editorState.theme"
|
<MdEditor ref="editorRef" v-model="editorState.text" :id="editorState.id" :theme="editorState.theme"
|
||||||
:previewTheme="editorState.previewTheme" :style="{ height: editorState.height }"
|
:previewTheme="editorState.previewTheme" :style="{ height: editorState.height }"
|
||||||
:toolbars="editorState.toolbars" :toolbarsExclude="editorState.toolbarsExclude" @onSave="onSave"
|
:toolbars="editorState.toolbars" :toolbarsExclude="editorState.toolbarsExclude" :sanitize="sanitize"
|
||||||
@onHtmlChanged="getPreviewedHTML" @onUploadImg="onUploadImg">
|
@onSave="onSave" @onHtmlChanged="getPreviewedHTML" @onUploadImg="onUploadImg">
|
||||||
<template #defToolbars>
|
<template #defToolbars>
|
||||||
<NormalToolbar :title="leftSidebarState == 'open' ? '隐藏文件树' : '显示文件树'" @onClick="toggleLeftSideBar">
|
<NormalToolbar :title="leftSidebarState == 'open' ? '隐藏文件树' : '显示文件树'" @onClick="toggleLeftSideBar">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
@ -55,6 +55,7 @@ import { ThemeSwitch, PreviewThemeSwitch, ExportPDF } from '@vavt/v3-extension';
|
|||||||
import { lineNumbers } from '@codemirror/view';
|
import { lineNumbers } from '@codemirror/view';
|
||||||
import { ref, reactive, watch, nextTick, onMounted } from "vue";
|
import { ref, reactive, watch, nextTick, onMounted } from "vue";
|
||||||
import { Message } from 'view-ui-plus'
|
import { Message } from 'view-ui-plus'
|
||||||
|
import { encode as plantumlEncoder } from 'plantuml-encoder';
|
||||||
|
|
||||||
// fetchScript 用于将其他 JS 脚本加载进来
|
// fetchScript 用于将其他 JS 脚本加载进来
|
||||||
const fetchScript = (url) => new Promise((resolve) => scriptjs(url, () => resolve()));
|
const fetchScript = (url) => new Promise((resolve) => scriptjs(url, () => resolve()));
|
||||||
@ -188,6 +189,54 @@ const toggleCatalog = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sanitize = (html) => {
|
||||||
|
// 防 XSS 攻击。使用 sanitize-html 处理不安全的 html 内容
|
||||||
|
// !!! 但是会导致目录跳转失败,因为 <h> 标签的部分属性被剔除掉了
|
||||||
|
// html = sanitizeHtml(html);
|
||||||
|
|
||||||
|
// PlantUML 渲染核心逻辑
|
||||||
|
html = html.replace(/@startuml([\s\S]*?)@enduml/g, (_, umlCode) => {
|
||||||
|
const lines = umlCode.split('\n');
|
||||||
|
// 去除换行作用的首尾
|
||||||
|
if (lines[0] == '<br>') {
|
||||||
|
lines.shift();
|
||||||
|
}
|
||||||
|
if (/<p\s+[^>]*>/i.test(lines[lines.length - 1])) {
|
||||||
|
lines.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeLineBreak = /(.*?)(?=<br>|<\/p>)/i;
|
||||||
|
const extractedLines = lines.map(line => {
|
||||||
|
// 提取类似于 <p data-line="2">text<br> 中的文本
|
||||||
|
line = line.trim().replace(/<\w+\s+[^>]*>(.*?)<[^>]*>/i, '$1');
|
||||||
|
// 去除换行符
|
||||||
|
let match = line.match(removeLineBreak);
|
||||||
|
line = match ? match[1].trim() : line;
|
||||||
|
// 特殊字符串处理,解码 HTML 实体。比如 > 反转义为 >
|
||||||
|
const decodedText = decodeHTMLEntities(line);
|
||||||
|
return decodedText;
|
||||||
|
});
|
||||||
|
extractedLines.unshift('@startuml');
|
||||||
|
extractedLines.push('@enduml');
|
||||||
|
const plantUMLCode = extractedLines.join('\n');
|
||||||
|
console.log(plantUMLCode);
|
||||||
|
|
||||||
|
// 生成 PlantUML 编码字符串
|
||||||
|
const encoded = plantumlEncoder(plantUMLCode);
|
||||||
|
return `<img class="plantuml-diagram"
|
||||||
|
src="https://www.plantuml.com/plantuml/svg/${encoded}"
|
||||||
|
alt="PlantUML Diagram">`;
|
||||||
|
});
|
||||||
|
|
||||||
|
return html;
|
||||||
|
};
|
||||||
|
|
||||||
|
const decodeHTMLEntities = (text) => {
|
||||||
|
const textArea = document.createElement('textarea');
|
||||||
|
textArea.innerHTML = text;
|
||||||
|
return textArea.value;
|
||||||
|
};
|
||||||
|
|
||||||
const onSave = (v, h) => {
|
const onSave = (v, h) => {
|
||||||
// 调用 save 方法保存代码(参数 v 为 markdown code)
|
// 调用 save 方法保存代码(参数 v 为 markdown code)
|
||||||
props.save(v);
|
props.save(v);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user