✨ feat: 新增 WYSIWYG 编辑器功能和相关工具栏事件处理
This commit is contained in:
27
web/include/vditor/src/ts/markdown/abcRender.ts
Normal file
27
web/include/vditor/src/ts/markdown/abcRender.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import {Constants} from "../constants";
|
||||
import {addScript} from "../util/addScript";
|
||||
import {abcRenderAdapter} from "./adapterRender";
|
||||
|
||||
declare const ABCJS: {
|
||||
renderAbc(element: HTMLElement, text: string): void;
|
||||
};
|
||||
|
||||
export const abcRender = (element: (HTMLElement | Document) = document, cdn = Constants.CDN) => {
|
||||
const abcElements = abcRenderAdapter.getElements(element);
|
||||
if (abcElements.length > 0) {
|
||||
addScript(`${cdn}/dist/js/abcjs/abcjs_basic.min.js`, "vditorAbcjsScript").then(() => {
|
||||
abcElements.forEach((item: HTMLDivElement) => {
|
||||
if (item.parentElement.classList.contains("vditor-wysiwyg__pre") ||
|
||||
item.parentElement.classList.contains("vditor-ir__marker--pre")) {
|
||||
return;
|
||||
}
|
||||
if (item.getAttribute("data-processed") === "true") {
|
||||
return;
|
||||
}
|
||||
ABCJS.renderAbc(item, abcRenderAdapter.getCode(item).trim());
|
||||
item.style.overflowX = "auto";
|
||||
item.setAttribute("data-processed", "true");
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
37
web/include/vditor/src/ts/markdown/adapterRender.ts
Normal file
37
web/include/vditor/src/ts/markdown/adapterRender.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
export const mathRenderAdapter = {
|
||||
getCode: (mathElement: Element) => mathElement.textContent,
|
||||
getElements: (element: HTMLElement) => element.querySelectorAll(".language-math"),
|
||||
};
|
||||
export const mermaidRenderAdapter = {
|
||||
/** 不仅要返回code,并且需要将 code 设置为 el 的 innerHTML */
|
||||
getCode: (el: Element) => el.textContent,
|
||||
getElements: (element: HTMLElement) => element.querySelectorAll(".language-mermaid"),
|
||||
};
|
||||
export const markmapRenderAdapter = {
|
||||
getCode: (el: Element) => el.textContent,
|
||||
getElements: (element: HTMLElement) => element.querySelectorAll(".language-markmap"),
|
||||
};
|
||||
export const mindmapRenderAdapter = {
|
||||
getCode: (el: Element) => el.getAttribute("data-code"),
|
||||
getElements: (el: HTMLElement | Document) => el.querySelectorAll(".language-mindmap"),
|
||||
};
|
||||
export const chartRenderAdapter = {
|
||||
getCode: (el: HTMLElement) => el.innerText,
|
||||
getElements: (el: HTMLElement | Document) => el.querySelectorAll(".language-echarts"),
|
||||
};
|
||||
export const abcRenderAdapter = {
|
||||
getCode: (el: Element) => el.textContent,
|
||||
getElements: (el: HTMLElement | Document) => el.querySelectorAll(".language-abc"),
|
||||
};
|
||||
export const graphvizRenderAdapter = {
|
||||
getCode: (el: Element) => el.textContent,
|
||||
getElements: (el: HTMLElement | Document) => el.querySelectorAll(".language-graphviz"),
|
||||
};
|
||||
export const flowchartRenderAdapter = {
|
||||
getCode: (el: Element) => el.textContent,
|
||||
getElements: (el: HTMLElement | Document) => el.querySelectorAll(".language-flowchart"),
|
||||
};
|
||||
export const plantumlRenderAdapter = {
|
||||
getCode: (el: Element) => el.textContent,
|
||||
getElements: (el: HTMLElement | Document) => el.querySelectorAll(".language-plantuml"),
|
||||
};
|
||||
19
web/include/vditor/src/ts/markdown/anchorRender.ts
Normal file
19
web/include/vditor/src/ts/markdown/anchorRender.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export const anchorRender = (type: number) => {
|
||||
document.querySelectorAll(".vditor-anchor").forEach((anchor: HTMLLinkElement) => {
|
||||
if (type === 1) {
|
||||
anchor.classList.add("vditor-anchor--left");
|
||||
}
|
||||
anchor.onclick = () => {
|
||||
const id = anchor.getAttribute("href").substr(1);
|
||||
const top = document.getElementById("vditorAnchor-" + id).offsetTop;
|
||||
document.querySelector("html").scrollTop = top;
|
||||
};
|
||||
});
|
||||
|
||||
window.onhashchange = () => {
|
||||
const element = document.getElementById("vditorAnchor-" + decodeURIComponent(window.location.hash.substr(1)));
|
||||
if (element) {
|
||||
document.querySelector("html").scrollTop = element.offsetTop;
|
||||
}
|
||||
};
|
||||
};
|
||||
38
web/include/vditor/src/ts/markdown/chartRender.ts
Normal file
38
web/include/vditor/src/ts/markdown/chartRender.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import {Constants} from "../constants";
|
||||
import {addScript} from "../util/addScript";
|
||||
import {chartRenderAdapter} from "./adapterRender";
|
||||
import {looseJsonParse} from "../util/function";
|
||||
|
||||
declare const echarts: {
|
||||
init(element: HTMLElement, theme?: string): IEChart;
|
||||
};
|
||||
|
||||
export const chartRender = (element: (HTMLElement | Document) = document, cdn = Constants.CDN, theme: string) => {
|
||||
const echartsElements = chartRenderAdapter.getElements(element);
|
||||
if (echartsElements.length > 0) {
|
||||
addScript(`${cdn}/dist/js/echarts/echarts.min.js?v=5.5.1`, "vditorEchartsScript").then(() => {
|
||||
echartsElements.forEach(async (e: HTMLDivElement) => {
|
||||
if (e.parentElement.classList.contains("vditor-wysiwyg__pre") ||
|
||||
e.parentElement.classList.contains("vditor-ir__marker--pre")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const text = chartRenderAdapter.getCode(e).trim();
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (e.getAttribute("data-processed") === "true") {
|
||||
return;
|
||||
}
|
||||
const option = await looseJsonParse(text);
|
||||
echarts.init(e, theme === "dark" ? "dark" : undefined).setOption(option);
|
||||
e.setAttribute("data-processed", "true");
|
||||
} catch (error) {
|
||||
e.className = "vditor-reset--error";
|
||||
e.innerHTML = `echarts render error: <br>${error}`;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
60
web/include/vditor/src/ts/markdown/codeRender.ts
Normal file
60
web/include/vditor/src/ts/markdown/codeRender.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import {code160to32} from "../util/code160to32";
|
||||
import {Constants} from "../constants";
|
||||
|
||||
export const codeRender = (element: HTMLElement, option?: IHljs) => {
|
||||
Array.from<HTMLElement>(element.querySelectorAll("pre > code")).filter((e, index) => {
|
||||
if (e.parentElement.classList.contains("vditor-wysiwyg__pre") ||
|
||||
e.parentElement.classList.contains("vditor-ir__marker--pre")) {
|
||||
return false;
|
||||
}
|
||||
if (e.classList.contains("language-mermaid") || e.classList.contains("language-flowchart") ||
|
||||
e.classList.contains("language-echarts") || e.classList.contains("language-mindmap") ||
|
||||
e.classList.contains("language-plantuml") || e.classList.contains("language-markmap") ||
|
||||
e.classList.contains("language-abc") || e.classList.contains("language-graphviz") ||
|
||||
e.classList.contains("language-math")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (e.style.maxHeight.indexOf("px") > -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 避免预览区在渲染后由于代码块过多产生性能问题 https://github.com/b3log/vditor/issues/67
|
||||
if (element.classList.contains("vditor-preview") && index > 5) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}).forEach((e) => {
|
||||
let codeText = e.innerText;
|
||||
if (e.classList.contains("highlight-chroma")) {
|
||||
const codeElement = e.cloneNode(true) as HTMLElement;
|
||||
codeElement.querySelectorAll(".highlight-ln").forEach((item: HTMLElement) => {
|
||||
item.remove();
|
||||
});
|
||||
codeText = codeElement.innerText;
|
||||
} else if (codeText.endsWith("\n")) {
|
||||
codeText = codeText.substr(0, codeText.length - 1)
|
||||
}
|
||||
let iconHTML = '<svg><use xlink:href="#vditor-icon-copy"></use></svg>';
|
||||
if (!document.getElementById("vditorIconScript")) {
|
||||
iconHTML = '<svg viewBox="0 0 32 32"><path d="M22.545-0h-17.455c-1.6 0-2.909 1.309-2.909 2.909v20.364h2.909v-20.364h17.455v-2.909zM26.909 5.818h-16c-1.6 0-2.909 1.309-2.909 2.909v20.364c0 1.6 1.309 2.909 2.909 2.909h16c1.6 0 2.909-1.309 2.909-2.909v-20.364c0-1.6-1.309-2.909-2.909-2.909zM26.909 29.091h-16v-20.364h16v20.364z"></path></svg>';
|
||||
}
|
||||
|
||||
const divElement = document.createElement("div");
|
||||
divElement.className = "vditor-copy";
|
||||
divElement.innerHTML = `<span aria-label="${window.VditorI18n?.copy || "复制"}"
|
||||
onmouseover="this.setAttribute('aria-label', '${window.VditorI18n?.copy || "复制"}')"
|
||||
class="vditor-tooltipped vditor-tooltipped__w"
|
||||
onclick="this.previousElementSibling.select();document.execCommand('copy');this.setAttribute('aria-label', '${window.VditorI18n?.copied || "已复制"}');this.previousElementSibling.blur()">${iconHTML}</span>`;
|
||||
const textarea = document.createElement("textarea");
|
||||
textarea.value = code160to32(codeText);
|
||||
divElement.insertAdjacentElement("afterbegin", textarea);
|
||||
if (option && option.renderMenu) {
|
||||
option.renderMenu(e, divElement);
|
||||
}
|
||||
e.before(divElement);
|
||||
e.style.maxHeight = (window.outerHeight - 40) + "px";
|
||||
// https://github.com/Vanessa219/vditor/issues/1356
|
||||
e.insertAdjacentHTML("afterend", `<span style="position: absolute">${Constants.ZWSP}</span>`)
|
||||
});
|
||||
};
|
||||
25
web/include/vditor/src/ts/markdown/flowchartRender.ts
Normal file
25
web/include/vditor/src/ts/markdown/flowchartRender.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import {Constants} from "../constants";
|
||||
import {addScript} from "../util/addScript";
|
||||
import {flowchartRenderAdapter} from "./adapterRender";
|
||||
|
||||
declare const flowchart: {
|
||||
parse(text: string): { drawSVG: (type: HTMLElement) => void };
|
||||
};
|
||||
|
||||
export const flowchartRender = (element: HTMLElement, cdn = Constants.CDN) => {
|
||||
const flowchartElements = flowchartRenderAdapter.getElements(element);
|
||||
if (flowchartElements.length === 0) {
|
||||
return;
|
||||
}
|
||||
addScript(`${cdn}/dist/js/flowchart.js/flowchart.min.js`, "vditorFlowchartScript").then(() => {
|
||||
flowchartElements.forEach((item: HTMLElement) => {
|
||||
if (item.getAttribute("data-processed") === "true") {
|
||||
return;
|
||||
}
|
||||
const flowchartObj = flowchart.parse(flowchartRenderAdapter.getCode(item));
|
||||
item.innerHTML = "";
|
||||
flowchartObj.drawSVG(item);
|
||||
item.setAttribute("data-processed", "true");
|
||||
});
|
||||
});
|
||||
};
|
||||
11
web/include/vditor/src/ts/markdown/getHTML.ts
Normal file
11
web/include/vditor/src/ts/markdown/getHTML.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import {getMarkdown} from "./getMarkdown";
|
||||
|
||||
export const getHTML = (vditor: IVditor) => {
|
||||
if (vditor.currentMode === "sv") {
|
||||
return vditor.lute.Md2HTML(getMarkdown(vditor));
|
||||
} else if (vditor.currentMode === "wysiwyg") {
|
||||
return vditor.lute.VditorDOM2HTML(vditor.wysiwyg.element.innerHTML);
|
||||
} else if (vditor.currentMode === "ir") {
|
||||
return vditor.lute.VditorIRDOM2HTML(vditor.ir.element.innerHTML);
|
||||
}
|
||||
};
|
||||
12
web/include/vditor/src/ts/markdown/getMarkdown.ts
Normal file
12
web/include/vditor/src/ts/markdown/getMarkdown.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import {code160to32} from "../util/code160to32";
|
||||
|
||||
export const getMarkdown = (vditor: IVditor) => {
|
||||
if (vditor.currentMode === "sv") {
|
||||
return code160to32(`${vditor.sv.element.textContent}\n`.replace(/\n\n$/, "\n"));
|
||||
} else if (vditor.currentMode === "wysiwyg") {
|
||||
return vditor.lute.VditorDOM2Md(vditor.wysiwyg.element.innerHTML);
|
||||
} else if (vditor.currentMode === "ir") {
|
||||
return vditor.lute.VditorIRDOM2Md(vditor.ir.element.innerHTML);
|
||||
}
|
||||
return "";
|
||||
};
|
||||
49
web/include/vditor/src/ts/markdown/graphvizRender.ts
Normal file
49
web/include/vditor/src/ts/markdown/graphvizRender.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import {Constants} from "../constants";
|
||||
import {addScript} from "../util/addScript";
|
||||
import {graphvizRenderAdapter} from "./adapterRender";
|
||||
|
||||
declare class Viz {
|
||||
public renderSVGElement: (code: string) => Promise<any>;
|
||||
|
||||
constructor({ }: { worker: Worker });
|
||||
}
|
||||
|
||||
export const graphvizRender = (element: HTMLElement, cdn = Constants.CDN) => {
|
||||
const graphvizElements = graphvizRenderAdapter.getElements(element);
|
||||
|
||||
if (graphvizElements.length === 0) {
|
||||
return;
|
||||
}
|
||||
addScript(`${cdn}/dist/js/graphviz/viz.js`, "vditorGraphVizScript").then(() => {
|
||||
graphvizElements.forEach((e: HTMLDivElement) => {
|
||||
const code = graphvizRenderAdapter.getCode(e);
|
||||
if (e.parentElement.classList.contains("vditor-wysiwyg__pre") ||
|
||||
e.parentElement.classList.contains("vditor-ir__marker--pre")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.getAttribute("data-processed") === "true" || code.trim() === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const blob = new Blob([`importScripts('${(document.getElementById("vditorGraphVizScript") as HTMLScriptElement).src.replace("viz.js", "full.render.js")}');`],
|
||||
{ type: "application/javascript" });
|
||||
const url = window.URL || window.webkitURL;
|
||||
const blobUrl = url.createObjectURL(blob);
|
||||
const worker = new Worker(blobUrl);
|
||||
new Viz({ worker })
|
||||
.renderSVGElement(code).then((result: HTMLElement) => {
|
||||
e.innerHTML = result.outerHTML;
|
||||
}).catch((error) => {
|
||||
e.innerHTML = `graphviz render error: <br>${error}`;
|
||||
e.className = "vditor-reset--error";
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("graphviz error", e);
|
||||
}
|
||||
|
||||
e.setAttribute("data-processed", "true");
|
||||
});
|
||||
});
|
||||
};
|
||||
90
web/include/vditor/src/ts/markdown/highlightRender.ts
Normal file
90
web/include/vditor/src/ts/markdown/highlightRender.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import {Constants} from "../constants";
|
||||
import {addScript} from "../util/addScript";
|
||||
import {addStyle} from "../util/addStyle";
|
||||
|
||||
declare const hljs: {
|
||||
highlightElement(element: Element): void;
|
||||
};
|
||||
|
||||
export const highlightRender = (hljsOption?: IHljs, element: HTMLElement | Document = document,
|
||||
cdn = Constants.CDN) => {
|
||||
let style = hljsOption.style;
|
||||
if (!Constants.CODE_THEME.includes(style)) {
|
||||
style = "github";
|
||||
}
|
||||
const vditorHljsStyle = document.getElementById("vditorHljsStyle") as HTMLLinkElement;
|
||||
const href = `${cdn}/dist/js/highlight.js/styles/${style}.css`;
|
||||
if (vditorHljsStyle && vditorHljsStyle.getAttribute('href') !== href) {
|
||||
vditorHljsStyle.remove();
|
||||
}
|
||||
addStyle(`${cdn}/dist/js/highlight.js/styles/${style}.css`, "vditorHljsStyle");
|
||||
|
||||
if (hljsOption.enable === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
const codes = element.querySelectorAll("pre > code");
|
||||
if (codes.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
addScript(`${cdn}/dist/js/highlight.js/highlight.pack.js`, "vditorHljsScript").then(() => {
|
||||
addScript(`${cdn}/dist/js/highlight.js/solidity.min.js`, "vditorHljsSolidityScript").then(() => {
|
||||
addScript(`${cdn}/dist/js/highlight.js/yul.min.js`, "vditorHljsYulScript").then(() => {
|
||||
element.querySelectorAll("pre > code").forEach((block) => {
|
||||
// ir & wysiwyg 区域不渲染
|
||||
if (block.parentElement.classList.contains("vditor-ir__marker--pre") ||
|
||||
block.parentElement.classList.contains("vditor-wysiwyg__pre")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (block.classList.contains("language-mermaid") || block.classList.contains("language-flowchart") ||
|
||||
block.classList.contains("language-echarts") || block.classList.contains("language-mindmap") ||
|
||||
block.classList.contains("language-plantuml") ||
|
||||
block.classList.contains("language-abc") || block.classList.contains("language-graphviz") ||
|
||||
block.classList.contains("language-math")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (hljsOption.defaultLang !== "" && block.className.indexOf("language-") === -1) {
|
||||
block.classList.add("language-" + hljsOption.defaultLang)
|
||||
}
|
||||
|
||||
hljs.highlightElement(block);
|
||||
|
||||
if (!hljsOption.lineNumber) {
|
||||
return;
|
||||
}
|
||||
|
||||
block.classList.add("vditor-linenumber");
|
||||
let linenNumberTemp: HTMLDivElement = block.querySelector(".vditor-linenumber__temp");
|
||||
if (!linenNumberTemp) {
|
||||
linenNumberTemp = document.createElement("div");
|
||||
linenNumberTemp.className = "vditor-linenumber__temp";
|
||||
block.insertAdjacentElement("beforeend", linenNumberTemp);
|
||||
}
|
||||
const whiteSpace = getComputedStyle(block).whiteSpace;
|
||||
let isSoftWrap = false;
|
||||
if (whiteSpace === "pre-wrap" || whiteSpace === "pre-line") {
|
||||
isSoftWrap = true;
|
||||
}
|
||||
let lineNumberHTML = "";
|
||||
const lineList = block.textContent.split(/\r\n|\r|\n/g);
|
||||
lineList.pop();
|
||||
lineList.map((line) => {
|
||||
let lineHeight = "";
|
||||
if (isSoftWrap) {
|
||||
linenNumberTemp.textContent = line || "\n";
|
||||
lineHeight = ` style="height:${linenNumberTemp.getBoundingClientRect().height}px"`;
|
||||
}
|
||||
lineNumberHTML += `<span${lineHeight}></span>`;
|
||||
});
|
||||
|
||||
linenNumberTemp.style.display = "none";
|
||||
lineNumberHTML = `<span class="vditor-linenumber__rows">${lineNumberHTML}</span>`;
|
||||
block.insertAdjacentHTML("beforeend", lineNumberHTML);
|
||||
});
|
||||
})
|
||||
})
|
||||
});
|
||||
};
|
||||
56
web/include/vditor/src/ts/markdown/lazyLoadImageRender.ts
Normal file
56
web/include/vditor/src/ts/markdown/lazyLoadImageRender.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
declare global {
|
||||
interface Window {
|
||||
vditorImageIntersectionObserver: IntersectionObserver;
|
||||
}
|
||||
}
|
||||
|
||||
export const lazyLoadImageRender = (element: (HTMLElement | Document) = document) => {
|
||||
const loadImg = (it: HTMLImageElement) => {
|
||||
const testImage = document.createElement("img");
|
||||
testImage.src = it.getAttribute("data-src");
|
||||
testImage.addEventListener("load", () => {
|
||||
if (!it.getAttribute("style") && !it.getAttribute("class") &&
|
||||
!it.getAttribute("width") && !it.getAttribute("height")) {
|
||||
if (testImage.naturalHeight > testImage.naturalWidth &&
|
||||
testImage.naturalWidth / testImage.naturalHeight <
|
||||
document.querySelector(".vditor-reset").clientWidth / (window.innerHeight - 40) &&
|
||||
testImage.naturalHeight > (window.innerHeight - 40)) {
|
||||
it.style.height = (window.innerHeight - 40) + "px";
|
||||
}
|
||||
}
|
||||
|
||||
it.src = testImage.src;
|
||||
});
|
||||
it.removeAttribute("data-src");
|
||||
};
|
||||
|
||||
if (!("IntersectionObserver" in window)) {
|
||||
element.querySelectorAll("img").forEach((imgElement: HTMLImageElement) => {
|
||||
if (imgElement.getAttribute("data-src")) {
|
||||
loadImg(imgElement);
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (window.vditorImageIntersectionObserver) {
|
||||
window.vditorImageIntersectionObserver.disconnect();
|
||||
element.querySelectorAll("img").forEach((imgElement) => {
|
||||
window.vditorImageIntersectionObserver.observe(imgElement);
|
||||
});
|
||||
} else {
|
||||
window.vditorImageIntersectionObserver = new IntersectionObserver((entries) => {
|
||||
entries.forEach((entrie: IntersectionObserverEntry & { target: HTMLImageElement }) => {
|
||||
if ((typeof entrie.isIntersecting === "undefined"
|
||||
? entrie.intersectionRatio !== 0
|
||||
: entrie.isIntersecting)
|
||||
&& entrie.target.getAttribute("data-src")) {
|
||||
loadImg(entrie.target);
|
||||
}
|
||||
});
|
||||
});
|
||||
element.querySelectorAll("img").forEach((imgElement) => {
|
||||
window.vditorImageIntersectionObserver.observe(imgElement);
|
||||
});
|
||||
}
|
||||
};
|
||||
57
web/include/vditor/src/ts/markdown/markmapRender.ts
Normal file
57
web/include/vditor/src/ts/markdown/markmapRender.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import {Constants} from "../constants";
|
||||
import {addScript} from "../util/addScript";
|
||||
import {markmapRenderAdapter} from "./adapterRender";
|
||||
|
||||
declare const window: any;
|
||||
const enabled: Record<string, boolean> = {};
|
||||
|
||||
const transform = (transformer: any,content: string)=>{
|
||||
const result = transformer.transform(content);
|
||||
const keys = Object.keys(result.features).filter((key) => !enabled[key]);
|
||||
keys.forEach((key) => {
|
||||
enabled[key] = true;
|
||||
});
|
||||
const { styles, scripts } = transformer.getAssets(keys);
|
||||
const { markmap } = window;
|
||||
if (styles) markmap.loadCSS(styles);
|
||||
if (scripts) markmap.loadJS(scripts);
|
||||
return result;
|
||||
}
|
||||
|
||||
const init = (el: HTMLElement,code: string) => {
|
||||
const { Transformer, Markmap, deriveOptions , globalCSS} = window.markmap;
|
||||
const transformer = new Transformer();
|
||||
el.innerHTML = '<svg style="width:100%"></svg>';
|
||||
const svg = el.firstChild as SVGElement;
|
||||
const mm = Markmap.create(svg, null);
|
||||
const { root, frontmatter } = transform(transformer, code);
|
||||
const markmapOptions = frontmatter?.markmap;
|
||||
const frontmatterOptions = deriveOptions(markmapOptions);
|
||||
mm.setData(root, frontmatterOptions);
|
||||
mm.fit();
|
||||
}
|
||||
|
||||
|
||||
export const markmapRender = (element: HTMLElement, cdn = Constants.CDN, theme: string) => {
|
||||
const markmapElements = markmapRenderAdapter.getElements(element);
|
||||
if (markmapElements.length === 0) {
|
||||
return;
|
||||
}
|
||||
addScript(`${cdn}/dist/js/markmap/markmap.min.js`, "vditorMermaidScript").then(() => {
|
||||
markmapElements.forEach((item) => {
|
||||
const code = markmapRenderAdapter.getCode(item);
|
||||
if (item.getAttribute("data-processed") === "true" || code.trim() === "") {
|
||||
return;
|
||||
}
|
||||
const render = document.createElement("div")
|
||||
render.className = "language-markmap"
|
||||
item.parentNode.appendChild(render)
|
||||
init(render,code)
|
||||
|
||||
if(item.parentNode.childNodes[0].nodeName == "CODE"){
|
||||
item.parentNode.removeChild(item.parentNode.childNodes[0])
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
};
|
||||
148
web/include/vditor/src/ts/markdown/mathRender.ts
Normal file
148
web/include/vditor/src/ts/markdown/mathRender.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
import {Constants} from "../constants";
|
||||
import {addScript, addScriptSync} from "../util/addScript";
|
||||
import {addStyle} from "../util/addStyle";
|
||||
import {code160to32} from "../util/code160to32";
|
||||
import {mathRenderAdapter} from "./adapterRender";
|
||||
|
||||
declare const katex: {
|
||||
renderToString(math: string, option: {
|
||||
displayMode: boolean;
|
||||
output: string;
|
||||
macros: object;
|
||||
}): string;
|
||||
};
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
MathJax: any;
|
||||
}
|
||||
}
|
||||
|
||||
export const mathRender = (element: HTMLElement, options?: { cdn?: string, math?: IMath }) => {
|
||||
const mathElements = mathRenderAdapter.getElements(element);
|
||||
|
||||
if (mathElements.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const defaultOptions = {
|
||||
cdn: Constants.CDN,
|
||||
math: {
|
||||
engine: "KaTeX",
|
||||
inlineDigit: false,
|
||||
macros: {},
|
||||
},
|
||||
};
|
||||
|
||||
if (options && options.math) {
|
||||
options.math =
|
||||
Object.assign({}, defaultOptions.math, options.math);
|
||||
}
|
||||
options = Object.assign({}, defaultOptions, options);
|
||||
|
||||
if (options.math.engine === "KaTeX") {
|
||||
addStyle(`${options.cdn}/dist/js/katex/katex.min.css?v=0.16.9`, "vditorKatexStyle");
|
||||
addScript(`${options.cdn}/dist/js/katex/katex.min.js?v=0.16.9`, "vditorKatexScript").then(() => {
|
||||
addScript(`${options.cdn}/dist/js/katex/mhchem.min.js?v=0.16.9`, "vditorKatexChemScript").then(() => {
|
||||
mathElements.forEach((mathElement) => {
|
||||
if (mathElement.parentElement.classList.contains("vditor-wysiwyg__pre") ||
|
||||
mathElement.parentElement.classList.contains("vditor-ir__marker--pre")) {
|
||||
return;
|
||||
}
|
||||
if (mathElement.getAttribute("data-math")) {
|
||||
return;
|
||||
}
|
||||
const math = code160to32(mathRenderAdapter.getCode(mathElement));
|
||||
mathElement.setAttribute("data-math", math);
|
||||
try {
|
||||
mathElement.innerHTML = katex.renderToString(math, {
|
||||
displayMode: mathElement.tagName === "DIV",
|
||||
output: "html",
|
||||
macros: options.math.macros,
|
||||
});
|
||||
} catch (e) {
|
||||
mathElement.innerHTML = e.message;
|
||||
mathElement.className = "language-math vditor-reset--error";
|
||||
}
|
||||
|
||||
mathElement.addEventListener("copy", (event: ClipboardEvent) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
const vditorMathElement = (event.currentTarget as HTMLElement).closest(".language-math");
|
||||
event.clipboardData.setData("text/html", vditorMathElement.innerHTML);
|
||||
event.clipboardData.setData("text/plain",
|
||||
vditorMathElement.getAttribute("data-math"));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
} else if (options.math.engine === "MathJax") {
|
||||
const chainAsync = (fns: any) => {
|
||||
if (fns.length === 0) {
|
||||
return;
|
||||
}
|
||||
let curr = 0;
|
||||
const last = fns[fns.length - 1];
|
||||
const next = () => {
|
||||
const fn = fns[curr++];
|
||||
fn === last ? fn() : fn(next);
|
||||
};
|
||||
next();
|
||||
};
|
||||
if (!window.MathJax) {
|
||||
window.MathJax = {
|
||||
loader: {
|
||||
paths: {mathjax: `${options.cdn}/dist/js/mathjax`},
|
||||
},
|
||||
startup: {
|
||||
typeset: false,
|
||||
},
|
||||
tex: {
|
||||
macros: options.math.macros,
|
||||
},
|
||||
};
|
||||
// https://github.com/Vanessa219/vditor/issues/1453
|
||||
Object.assign(window.MathJax, options.math.mathJaxOptions);
|
||||
}
|
||||
// 循环加载会抛异常
|
||||
addScriptSync(`${options.cdn}/dist/js/mathjax/tex-svg-full.js`, "protyleMathJaxScript");
|
||||
const renderMath = (mathElement: Element, next?: () => void) => {
|
||||
const math = code160to32(mathElement.textContent).trim();
|
||||
const mathOptions = window.MathJax.getMetricsFor(mathElement);
|
||||
mathOptions.display = mathElement.tagName === "DIV";
|
||||
window.MathJax.tex2svgPromise(math, mathOptions).then((node: Element) => {
|
||||
mathElement.innerHTML = "";
|
||||
mathElement.setAttribute("data-math", math);
|
||||
mathElement.append(node);
|
||||
window.MathJax.startup.document.clear();
|
||||
window.MathJax.startup.document.updateDocument();
|
||||
const errorTextElement = node.querySelector('[data-mml-node="merror"]');
|
||||
if (errorTextElement && errorTextElement.textContent.trim() !== "") {
|
||||
mathElement.innerHTML = errorTextElement.textContent.trim();
|
||||
mathElement.className = "vditor-reset--error";
|
||||
}
|
||||
if (next) {
|
||||
next();
|
||||
}
|
||||
});
|
||||
};
|
||||
window.MathJax.startup.promise.then(() => {
|
||||
const chains: any[] = [];
|
||||
for (let i = 0; i < mathElements.length; i++) {
|
||||
const mathElement = mathElements[i];
|
||||
if (!mathElement.parentElement.classList.contains("vditor-wysiwyg__pre") &&
|
||||
!mathElement.parentElement.classList.contains("vditor-ir__marker--pre") &&
|
||||
!mathElement.getAttribute("data-math") && code160to32(mathElement.textContent).trim()) {
|
||||
chains.push((next: () => void) => {
|
||||
if (i === mathElements.length - 1) {
|
||||
renderMath(mathElement);
|
||||
} else {
|
||||
renderMath(mathElement, next);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
chainAsync(chains);
|
||||
});
|
||||
}
|
||||
};
|
||||
105
web/include/vditor/src/ts/markdown/mediaRender.ts
Normal file
105
web/include/vditor/src/ts/markdown/mediaRender.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import {getSearch} from "../util/function";
|
||||
|
||||
const videoRender = (element: HTMLElement, url: string) => {
|
||||
element.insertAdjacentHTML("afterend", `<video controls="controls" src="${url}"></video>`);
|
||||
element.remove();
|
||||
};
|
||||
|
||||
const audioRender = (element: HTMLElement, url: string) => {
|
||||
element.insertAdjacentHTML("afterend", `<audio controls="controls" src="${url}"></audio>`);
|
||||
element.remove();
|
||||
};
|
||||
|
||||
const iframeRender = (element: HTMLElement, url: string) => {
|
||||
const youtubeMatch = url.match(/\/\/(?:www\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))([\w|-]{11})(?:(?:[\?&]t=)(\S+))?/);
|
||||
const youkuMatch = url.match(/\/\/v\.youku\.com\/v_show\/id_(\w+)=*\.html/);
|
||||
const qqMatch = url.match(/\/\/v\.qq\.com\/x\/cover\/.*\/([^\/]+)\.html\??.*/);
|
||||
const coubMatch = url.match(/(?:www\.|\/\/)coub\.com\/view\/(\w+)/);
|
||||
const facebookMatch = url.match(/(?:www\.|\/\/)facebook\.com\/([^\/]+)\/videos\/([0-9]+)/);
|
||||
const dailymotionMatch = url.match(/.+dailymotion.com\/(video|hub)\/(\w+)\?/);
|
||||
const bilibiliMatch = url.match(/(?:www\.|\/\/)bilibili\.com\/video\/(\w+)/);
|
||||
const tedMatch = url.match(/(?:www\.|\/\/)ted\.com\/talks\/(\w+)/);
|
||||
|
||||
if (youtubeMatch && youtubeMatch[1].length === 11) {
|
||||
element.insertAdjacentHTML("afterend",
|
||||
`<iframe class="iframe__video" src="//www.youtube.com/embed/${youtubeMatch[1] +
|
||||
(youtubeMatch[2] ? "?start=" + youtubeMatch[2] : "")}"></iframe>`);
|
||||
element.remove();
|
||||
} else if (youkuMatch && youkuMatch[1]) {
|
||||
element.insertAdjacentHTML("afterend",
|
||||
`<iframe class="iframe__video" src="//player.youku.com/embed/${youkuMatch[1]}"></iframe>`);
|
||||
element.remove();
|
||||
} else if (qqMatch && qqMatch[1]) {
|
||||
element.insertAdjacentHTML("afterend",
|
||||
`<iframe class="iframe__video" src="https://v.qq.com/txp/iframe/player.html?vid=${qqMatch[1]}"></iframe>`);
|
||||
element.remove();
|
||||
} else if (coubMatch && coubMatch[1]) {
|
||||
element.insertAdjacentHTML("afterend",
|
||||
`<iframe class="iframe__video"
|
||||
src="//coub.com/embed/${coubMatch[1]}?muted=false&autostart=false&originalSize=true&startWithHD=true"></iframe>`);
|
||||
element.remove();
|
||||
} else if (facebookMatch && facebookMatch[0]) {
|
||||
element.insertAdjacentHTML("afterend",
|
||||
`<iframe class="iframe__video"
|
||||
src="https://www.facebook.com/plugins/video.php?href=${encodeURIComponent(facebookMatch[0])}"></iframe>`);
|
||||
element.remove();
|
||||
} else if (dailymotionMatch && dailymotionMatch[2]) {
|
||||
element.insertAdjacentHTML("afterend",
|
||||
`<iframe class="iframe__video"
|
||||
src="https://www.dailymotion.com/embed/video/${dailymotionMatch[2]}"></iframe>`);
|
||||
element.remove();
|
||||
} else if (url.indexOf("bilibili.com") > -1 && (url.indexOf("bvid=") > -1 || (bilibiliMatch && bilibiliMatch[1]))) {
|
||||
const params: IObject = {
|
||||
bvid: getSearch("bvid", url) || (bilibiliMatch && bilibiliMatch[1]),
|
||||
page: "1",
|
||||
high_quality: "1",
|
||||
as_wide: "1",
|
||||
allowfullscreen: "true",
|
||||
autoplay: "0"
|
||||
};
|
||||
new URL(url.startsWith("http") ? url : "https:" + url).search.split("&").forEach((item, index) => {
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
if (index === 0) {
|
||||
item = item.substr(1);
|
||||
}
|
||||
const keyValue = item.split("=");
|
||||
params[keyValue[0]] = keyValue[1];
|
||||
});
|
||||
let src = "https://player.bilibili.com/player.html?";
|
||||
const keys = Object.keys(params);
|
||||
keys.forEach((key, index) => {
|
||||
src += `${key}=${params[key]}`;
|
||||
if (index < keys.length - 1) {
|
||||
src += "&";
|
||||
}
|
||||
});
|
||||
element.insertAdjacentHTML("afterend",
|
||||
`<iframe class="iframe__video" src="${src}"></iframe>`);
|
||||
element.remove();
|
||||
} else if (tedMatch && tedMatch[1]) {
|
||||
element.insertAdjacentHTML("afterend",
|
||||
`<iframe class="iframe__video" src="//embed.ted.com/talks/${tedMatch[1]}"></iframe>`);
|
||||
element.remove();
|
||||
}
|
||||
};
|
||||
|
||||
export const mediaRender = (element: HTMLElement) => {
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
element.querySelectorAll("a").forEach((aElement) => {
|
||||
const url = aElement.getAttribute("href");
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
if (url.match(/^.+.(mp4|m4v|ogg|ogv|webm)$/)) {
|
||||
videoRender(aElement, url);
|
||||
} else if (url.match(/^.+.(mp3|wav|flac)$/)) {
|
||||
audioRender(aElement, url);
|
||||
} else {
|
||||
iframeRender(aElement, url);
|
||||
}
|
||||
});
|
||||
};
|
||||
60
web/include/vditor/src/ts/markdown/mermaidRender.ts
Normal file
60
web/include/vditor/src/ts/markdown/mermaidRender.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import {Constants} from "../constants";
|
||||
import {addScript} from "../util/addScript";
|
||||
import {mermaidRenderAdapter} from "./adapterRender";
|
||||
import {genUUID} from "../util/function";
|
||||
|
||||
declare const mermaid: {
|
||||
initialize(options: any): void,
|
||||
render(id: string, text: string): { svg: string }
|
||||
};
|
||||
|
||||
export const mermaidRender = (element: HTMLElement, cdn = Constants.CDN, theme: string) => {
|
||||
const mermaidElements = mermaidRenderAdapter.getElements(element);
|
||||
if (mermaidElements.length === 0) {
|
||||
return;
|
||||
}
|
||||
addScript(`${cdn}/dist/js/mermaid/mermaid.min.js`, "vditorMermaidScript").then(() => {
|
||||
const config: any = {
|
||||
securityLevel: "loose", // 升级后无 https://github.com/siyuan-note/siyuan/issues/3587,可使用该选项
|
||||
altFontFamily: "sans-serif",
|
||||
fontFamily: "sans-serif",
|
||||
startOnLoad: false,
|
||||
flowchart: {
|
||||
htmlLabels: true,
|
||||
useMaxWidth: !0
|
||||
},
|
||||
sequence: {
|
||||
useMaxWidth: true,
|
||||
diagramMarginX: 8,
|
||||
diagramMarginY: 8,
|
||||
boxMargin: 8,
|
||||
showSequenceNumbers: true // Mermaid 时序图增加序号 https://github.com/siyuan-note/siyuan/pull/6992 https://mermaid.js.org/syntax/sequenceDiagram.html#sequencenumbers
|
||||
},
|
||||
gantt: {
|
||||
leftPadding: 75,
|
||||
rightPadding: 20
|
||||
}
|
||||
};
|
||||
if (theme === "dark") {
|
||||
config.theme = "dark";
|
||||
}
|
||||
mermaid.initialize(config);
|
||||
mermaidElements.forEach(async (item) => {
|
||||
const code = mermaidRenderAdapter.getCode(item);
|
||||
if (item.getAttribute("data-processed") === "true" || code.trim() === "") {
|
||||
return;
|
||||
}
|
||||
const id = "mermaid"+genUUID()
|
||||
try {
|
||||
const mermaidData = await mermaid.render(id, item.textContent);
|
||||
item.innerHTML = mermaidData.svg;
|
||||
} catch (e) {
|
||||
const errorElement = document.querySelector("#" + id);
|
||||
item.innerHTML = `${errorElement.outerHTML}<br>
|
||||
<div style="text-align: left"><small>${e.message.replace(/\n/, "<br>")}</small></div>`;
|
||||
errorElement.parentElement.remove();
|
||||
}
|
||||
item.setAttribute("data-processed", "true");
|
||||
});
|
||||
});
|
||||
};
|
||||
74
web/include/vditor/src/ts/markdown/mindmapRender.ts
Normal file
74
web/include/vditor/src/ts/markdown/mindmapRender.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import {Constants} from "../constants";
|
||||
import {addScript} from "../util/addScript";
|
||||
import {mindmapRenderAdapter} from "./adapterRender";
|
||||
|
||||
declare const echarts: {
|
||||
init(element: HTMLElement, theme?: string): IEChart;
|
||||
};
|
||||
|
||||
export const mindmapRender = (element: (HTMLElement | Document) = document, cdn = Constants.CDN, theme: string) => {
|
||||
const mindmapElements = mindmapRenderAdapter.getElements(element);
|
||||
if (mindmapElements.length > 0) {
|
||||
addScript(`${cdn}/dist/js/echarts/echarts.min.js?v=5.5.1`, "vditorEchartsScript").then(() => {
|
||||
mindmapElements.forEach((e: HTMLDivElement) => {
|
||||
if (e.parentElement.classList.contains("vditor-wysiwyg__pre") ||
|
||||
e.parentElement.classList.contains("vditor-ir__marker--pre")) {
|
||||
return;
|
||||
}
|
||||
const text = mindmapRenderAdapter.getCode(e);
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (e.getAttribute("data-processed") === "true") {
|
||||
return;
|
||||
}
|
||||
echarts.init(e, theme === "dark" ? "dark" : undefined).setOption({
|
||||
series: [
|
||||
{
|
||||
data: [JSON.parse(decodeURIComponent(text))],
|
||||
initialTreeDepth: -1,
|
||||
itemStyle: {
|
||||
borderWidth: 0,
|
||||
color: "#4285f4",
|
||||
},
|
||||
label: {
|
||||
backgroundColor: "#f6f8fa",
|
||||
borderColor: "#d1d5da",
|
||||
borderRadius: 5,
|
||||
borderWidth: 0.5,
|
||||
color: "#586069",
|
||||
lineHeight: 20,
|
||||
offset: [-5, 0],
|
||||
padding: [0, 5],
|
||||
position: "insideRight",
|
||||
},
|
||||
lineStyle: {
|
||||
color: "#d1d5da",
|
||||
width: 1,
|
||||
},
|
||||
roam: true,
|
||||
symbol: (value: number, params: { data?: { children?: object } }) => {
|
||||
if (params?.data?.children) {
|
||||
return "circle";
|
||||
} else {
|
||||
return "path://";
|
||||
}
|
||||
},
|
||||
type: "tree",
|
||||
},
|
||||
],
|
||||
tooltip: {
|
||||
trigger: "item",
|
||||
triggerOn: "mousemove",
|
||||
},
|
||||
});
|
||||
e.setAttribute("data-processed", "true");
|
||||
} catch (error) {
|
||||
e.className = "vditor-reset--error";
|
||||
e.innerHTML = `mindmap render error: <br>${error}`;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
110
web/include/vditor/src/ts/markdown/outlineRender.ts
Normal file
110
web/include/vditor/src/ts/markdown/outlineRender.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import {hasClosestByHeadings} from "../util/hasClosestByHeadings";
|
||||
import {mathRender} from "./mathRender";
|
||||
|
||||
export const outlineRender = (contentElement: HTMLElement, targetElement: Element, vditor?: IVditor) => {
|
||||
let tocHTML = "";
|
||||
const ids: string[] = [];
|
||||
Array.from(contentElement.children).forEach((item: HTMLElement, index: number) => {
|
||||
if (hasClosestByHeadings(item)) {
|
||||
if (vditor) {
|
||||
const lastIndex = item.id.lastIndexOf("_");
|
||||
item.id = item.id.substring(0, lastIndex === -1 ? undefined : lastIndex) + "_" + index;
|
||||
}
|
||||
ids.push(item.id);
|
||||
tocHTML += item.outerHTML.replace("<wbr>", "");
|
||||
}
|
||||
});
|
||||
if (tocHTML === "") {
|
||||
targetElement.innerHTML = "";
|
||||
return "";
|
||||
}
|
||||
const tempElement = document.createElement("div");
|
||||
if (vditor) {
|
||||
vditor.lute.SetToC(true);
|
||||
if (vditor.currentMode === "wysiwyg" && !vditor.preview.element.contains(contentElement)) {
|
||||
tempElement.innerHTML = vditor.lute.SpinVditorDOM("<p>[ToC]</p>" + tocHTML);
|
||||
} else if (vditor.currentMode === "ir" && !vditor.preview.element.contains(contentElement)) {
|
||||
tempElement.innerHTML = vditor.lute.SpinVditorIRDOM("<p>[ToC]</p>" + tocHTML);
|
||||
} else {
|
||||
tempElement.innerHTML = vditor.lute.HTML2VditorDOM("<p>[ToC]</p>" + tocHTML);
|
||||
}
|
||||
vditor.lute.SetToC(vditor.options.preview.markdown.toc);
|
||||
} else {
|
||||
targetElement.classList.add("vditor-outline");
|
||||
const lute = Lute.New();
|
||||
lute.SetToC(true);
|
||||
tempElement.innerHTML = lute.HTML2VditorDOM("<p>[ToC]</p>" + tocHTML);
|
||||
}
|
||||
const headingsElement = tempElement.firstElementChild.querySelectorAll("li > span[data-target-id]");
|
||||
headingsElement.forEach((item, index) => {
|
||||
if (item.nextElementSibling && item.nextElementSibling.tagName === "UL") {
|
||||
let iconHTML = "<svg class='vditor-outline__action'><use xlink:href='#vditor-icon-down'></use></svg>";
|
||||
if (!document.getElementById("vditorIconScript")) {
|
||||
iconHTML = '<svg class="vditor-outline__action" viewBox="0 0 32 32"><path d="M3.76 6.12l12.24 12.213 12.24-12.213 3.76 3.76-16 16-16-16 3.76-3.76z"></path></svg>';
|
||||
}
|
||||
item.innerHTML = `${iconHTML}<span>${item.innerHTML}</span>`;
|
||||
} else {
|
||||
item.innerHTML = `<svg></svg><span>${item.innerHTML}</span>`;
|
||||
}
|
||||
item.setAttribute("data-target-id", ids[index]);
|
||||
});
|
||||
tocHTML = tempElement.firstElementChild.innerHTML;
|
||||
if (headingsElement.length === 0) {
|
||||
targetElement.innerHTML = "";
|
||||
return tocHTML;
|
||||
}
|
||||
targetElement.innerHTML = tocHTML;
|
||||
if (vditor) {
|
||||
mathRender(targetElement as HTMLElement, {
|
||||
cdn: vditor.options.cdn,
|
||||
math: vditor.options.preview.math,
|
||||
});
|
||||
}
|
||||
targetElement.firstElementChild.addEventListener("click", (event: Event) => {
|
||||
let target = event.target as HTMLElement;
|
||||
while (target && !target.isEqualNode(targetElement)) {
|
||||
if (target.classList.contains("vditor-outline__action")) {
|
||||
if (target.classList.contains("vditor-outline__action--close")) {
|
||||
target.classList.remove("vditor-outline__action--close");
|
||||
target.parentElement.nextElementSibling.setAttribute("style", "display:block");
|
||||
} else {
|
||||
target.classList.add("vditor-outline__action--close");
|
||||
target.parentElement.nextElementSibling.setAttribute("style", "display:none");
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
break;
|
||||
} else if (target.getAttribute("data-target-id")) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const idElement = document.getElementById(target.getAttribute("data-target-id"));
|
||||
if (!idElement) {
|
||||
return;
|
||||
}
|
||||
if (vditor) {
|
||||
if (vditor.options.height === "auto") {
|
||||
let windowScrollY = idElement.offsetTop + vditor.element.offsetTop;
|
||||
if (!vditor.options.toolbarConfig.pin) {
|
||||
windowScrollY += vditor.toolbar.element.offsetHeight;
|
||||
}
|
||||
window.scrollTo(window.scrollX, windowScrollY);
|
||||
} else {
|
||||
if (vditor.element.offsetTop < window.scrollY) {
|
||||
window.scrollTo(window.scrollX, vditor.element.offsetTop);
|
||||
}
|
||||
if (vditor.preview.element.contains(contentElement)) {
|
||||
contentElement.parentElement.scrollTop = idElement.offsetTop;
|
||||
} else {
|
||||
contentElement.scrollTop = idElement.offsetTop;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
window.scrollTo(window.scrollX, idElement.offsetTop);
|
||||
}
|
||||
break;
|
||||
}
|
||||
target = target.parentElement;
|
||||
}
|
||||
});
|
||||
return tocHTML;
|
||||
};
|
||||
32
web/include/vditor/src/ts/markdown/plantumlRender.ts
Normal file
32
web/include/vditor/src/ts/markdown/plantumlRender.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import {Constants} from "../constants";
|
||||
import {addScript} from "../util/addScript";
|
||||
import {plantumlRenderAdapter} from "./adapterRender";
|
||||
|
||||
declare const plantumlEncoder: {
|
||||
encode(options: string): string,
|
||||
};
|
||||
|
||||
export const plantumlRender = (element: (HTMLElement | Document) = document, cdn = Constants.CDN) => {
|
||||
const plantumlElements = plantumlRenderAdapter.getElements(element);
|
||||
if (plantumlElements.length === 0) {
|
||||
return;
|
||||
}
|
||||
addScript(`${cdn}/dist/js/plantuml/plantuml-encoder.min.js`, "vditorPlantumlScript").then(() => {
|
||||
plantumlElements.forEach((e: HTMLDivElement) => {
|
||||
if (e.parentElement.classList.contains("vditor-wysiwyg__pre") ||
|
||||
e.parentElement.classList.contains("vditor-ir__marker--pre")) {
|
||||
return;
|
||||
}
|
||||
const text = plantumlRenderAdapter.getCode(e).trim();
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
e.innerHTML = `<object type="image/svg+xml" data="https://www.plantuml.com/plantuml/svg/~1${plantumlEncoder.encode(text)}"/>`;
|
||||
} catch (error) {
|
||||
e.className = "vditor-reset--error";
|
||||
e.innerHTML = `plantuml render error: <br>${error}`;
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
167
web/include/vditor/src/ts/markdown/previewRender.ts
Normal file
167
web/include/vditor/src/ts/markdown/previewRender.ts
Normal file
@@ -0,0 +1,167 @@
|
||||
import {Constants} from "../constants";
|
||||
import {setContentTheme} from "../ui/setContentTheme";
|
||||
import {addScript, addScriptSync} from "../util/addScript";
|
||||
import {hasClosestByClassName, hasClosestByMatchTag} from "../util/hasClosest";
|
||||
import {merge} from "../util/merge";
|
||||
import {abcRender} from "./abcRender";
|
||||
import {anchorRender} from "./anchorRender";
|
||||
import {chartRender} from "./chartRender";
|
||||
import {codeRender} from "./codeRender";
|
||||
import {flowchartRender} from "./flowchartRender";
|
||||
import {graphvizRender} from "./graphvizRender";
|
||||
import {highlightRender} from "./highlightRender";
|
||||
import {lazyLoadImageRender} from "./lazyLoadImageRender";
|
||||
import {mathRender} from "./mathRender";
|
||||
import {mediaRender} from "./mediaRender";
|
||||
import {mermaidRender} from "./mermaidRender";
|
||||
import {markmapRender} from "../markdown/markmapRender";
|
||||
import {mindmapRender} from "./mindmapRender";
|
||||
import {plantumlRender} from "./plantumlRender";
|
||||
import {setLute} from "./setLute";
|
||||
import {speechRender} from "./speechRender";
|
||||
|
||||
const mergeOptions = (options?: IPreviewOptions) => {
|
||||
const defaultOption: IPreviewOptions = {
|
||||
anchor: 0,
|
||||
cdn: Constants.CDN,
|
||||
customEmoji: {},
|
||||
emojiPath: `${Constants.CDN}/dist/images/emoji`,
|
||||
hljs: Constants.HLJS_OPTIONS,
|
||||
icon: "ant",
|
||||
lang: "zh_CN",
|
||||
markdown: Constants.MARKDOWN_OPTIONS,
|
||||
math: Constants.MATH_OPTIONS,
|
||||
mode: "light",
|
||||
speech: {
|
||||
enable: false,
|
||||
},
|
||||
render: {
|
||||
media: {
|
||||
enable: true,
|
||||
}
|
||||
},
|
||||
theme: Constants.THEME_OPTIONS,
|
||||
};
|
||||
if (options.cdn) {
|
||||
if (!options.theme?.path) {
|
||||
defaultOption.theme.path = `${options.cdn}/dist/css/content-theme`
|
||||
}
|
||||
if (!options.emojiPath) {
|
||||
defaultOption.emojiPath = `${options.cdn}/dist/images/emoji`;
|
||||
}
|
||||
}
|
||||
return merge(defaultOption, options);
|
||||
};
|
||||
|
||||
export const md2html = (mdText: string, options?: IPreviewOptions) => {
|
||||
const mergedOptions = mergeOptions(options);
|
||||
return addScript(`${mergedOptions.cdn}/dist/js/lute/lute.min.js`, "vditorLuteScript").then(() => {
|
||||
const lute = setLute({
|
||||
autoSpace: mergedOptions.markdown.autoSpace,
|
||||
gfmAutoLink: mergedOptions.markdown.gfmAutoLink,
|
||||
codeBlockPreview: mergedOptions.markdown.codeBlockPreview,
|
||||
emojiSite: mergedOptions.emojiPath,
|
||||
emojis: mergedOptions.customEmoji,
|
||||
fixTermTypo: mergedOptions.markdown.fixTermTypo,
|
||||
footnotes: mergedOptions.markdown.footnotes,
|
||||
headingAnchor: mergedOptions.anchor !== 0,
|
||||
inlineMathDigit: mergedOptions.math.inlineDigit,
|
||||
lazyLoadImage: mergedOptions.lazyLoadImage,
|
||||
linkBase: mergedOptions.markdown.linkBase,
|
||||
linkPrefix: mergedOptions.markdown.linkPrefix,
|
||||
listStyle: mergedOptions.markdown.listStyle,
|
||||
mark: mergedOptions.markdown.mark,
|
||||
mathBlockPreview: mergedOptions.markdown.mathBlockPreview,
|
||||
paragraphBeginningSpace: mergedOptions.markdown.paragraphBeginningSpace,
|
||||
sanitize: mergedOptions.markdown.sanitize,
|
||||
toc: mergedOptions.markdown.toc,
|
||||
});
|
||||
if (options?.renderers) {
|
||||
lute.SetJSRenderers({
|
||||
renderers: {
|
||||
Md2HTML: options.renderers,
|
||||
},
|
||||
});
|
||||
}
|
||||
lute.SetHeadingID(true);
|
||||
return lute.Md2HTML(mdText);
|
||||
});
|
||||
};
|
||||
|
||||
export const previewRender = async (previewElement: HTMLDivElement, markdown: string, options?: IPreviewOptions) => {
|
||||
const mergedOptions: IPreviewOptions = mergeOptions(options);
|
||||
let html = await md2html(markdown, mergedOptions);
|
||||
if (mergedOptions.transform) {
|
||||
html = mergedOptions.transform(html);
|
||||
}
|
||||
previewElement.innerHTML = html;
|
||||
previewElement.classList.add("vditor-reset");
|
||||
|
||||
if (!mergedOptions.i18n) {
|
||||
if (!["en_US", "fr_FR", "pt_BR", "ja_JP", "ko_KR", "ru_RU", "sv_SE", "zh_CN", "zh_TW"].includes(mergedOptions.lang)) {
|
||||
throw new Error(
|
||||
"options.lang error, see https://ld246.com/article/1549638745630#options",
|
||||
);
|
||||
} else {
|
||||
const i18nScriptPrefix = "vditorI18nScript";
|
||||
const i18nScriptID = i18nScriptPrefix + mergedOptions.lang;
|
||||
document.querySelectorAll(`head script[id^="${i18nScriptPrefix}"]`).forEach((el) => {
|
||||
if (el.id !== i18nScriptID) {
|
||||
document.head.removeChild(el);
|
||||
}
|
||||
});
|
||||
await addScript(`${mergedOptions.cdn}/dist/js/i18n/${mergedOptions.lang}.js`, i18nScriptID);
|
||||
}
|
||||
} else {
|
||||
window.VditorI18n = mergedOptions.i18n;
|
||||
}
|
||||
|
||||
if (mergedOptions.icon) {
|
||||
await addScript(`${mergedOptions.cdn}/dist/js/icons/${mergedOptions.icon}.js`, "vditorIconScript");
|
||||
}
|
||||
|
||||
setContentTheme(mergedOptions.theme.current, mergedOptions.theme.path);
|
||||
if (mergedOptions.anchor === 1) {
|
||||
previewElement.classList.add("vditor-reset--anchor");
|
||||
}
|
||||
codeRender(previewElement, mergedOptions.hljs);
|
||||
highlightRender(mergedOptions.hljs, previewElement, mergedOptions.cdn);
|
||||
mathRender(previewElement, {
|
||||
cdn: mergedOptions.cdn,
|
||||
math: mergedOptions.math,
|
||||
});
|
||||
mermaidRender(previewElement, mergedOptions.cdn, mergedOptions.mode);
|
||||
markmapRender(previewElement, mergedOptions.cdn, mergedOptions.mode);
|
||||
flowchartRender(previewElement, mergedOptions.cdn);
|
||||
graphvizRender(previewElement, mergedOptions.cdn);
|
||||
chartRender(previewElement, mergedOptions.cdn, mergedOptions.mode);
|
||||
mindmapRender(previewElement, mergedOptions.cdn, mergedOptions.mode);
|
||||
plantumlRender(previewElement, mergedOptions.cdn);
|
||||
abcRender(previewElement, mergedOptions.cdn);
|
||||
if (mergedOptions.render.media.enable) {
|
||||
mediaRender(previewElement);
|
||||
}
|
||||
if (mergedOptions.speech.enable) {
|
||||
speechRender(previewElement);
|
||||
}
|
||||
if (mergedOptions.anchor !== 0) {
|
||||
anchorRender(mergedOptions.anchor);
|
||||
}
|
||||
if (mergedOptions.after) {
|
||||
mergedOptions.after();
|
||||
}
|
||||
if (mergedOptions.lazyLoadImage) {
|
||||
lazyLoadImageRender(previewElement);
|
||||
}
|
||||
previewElement.addEventListener("click", (event: MouseEvent & { target: HTMLElement }) => {
|
||||
const spanElement = hasClosestByMatchTag(event.target, "SPAN");
|
||||
if (spanElement && hasClosestByClassName(spanElement, "vditor-toc")) {
|
||||
const headingElement =
|
||||
previewElement.querySelector("#" + spanElement.getAttribute("data-target-id")) as HTMLElement;
|
||||
if (headingElement) {
|
||||
window.scrollTo(window.scrollX, headingElement.offsetTop);
|
||||
}
|
||||
return;
|
||||
}
|
||||
});
|
||||
};
|
||||
24
web/include/vditor/src/ts/markdown/setLute.ts
Normal file
24
web/include/vditor/src/ts/markdown/setLute.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
export const setLute = (options: ILuteOptions) => {
|
||||
const lute: Lute = Lute.New();
|
||||
lute.PutEmojis(options.emojis);
|
||||
lute.SetEmojiSite(options.emojiSite);
|
||||
lute.SetHeadingAnchor(options.headingAnchor);
|
||||
lute.SetInlineMathAllowDigitAfterOpenMarker(options.inlineMathDigit);
|
||||
lute.SetAutoSpace(options.autoSpace);
|
||||
lute.SetToC(options.toc);
|
||||
lute.SetFootnotes(options.footnotes);
|
||||
lute.SetFixTermTypo(options.fixTermTypo);
|
||||
lute.SetVditorCodeBlockPreview(options.codeBlockPreview);
|
||||
lute.SetVditorMathBlockPreview(options.mathBlockPreview);
|
||||
lute.SetSanitize(options.sanitize);
|
||||
lute.SetChineseParagraphBeginningSpace(options.paragraphBeginningSpace);
|
||||
lute.SetRenderListStyle(options.listStyle);
|
||||
lute.SetLinkBase(options.linkBase);
|
||||
lute.SetLinkPrefix(options.linkPrefix);
|
||||
lute.SetMark(options.mark);
|
||||
lute.SetGFMAutoLink(options.gfmAutoLink);
|
||||
if (options.lazyLoadImage) {
|
||||
lute.SetImageLazyLoading(options.lazyLoadImage);
|
||||
}
|
||||
return lute;
|
||||
};
|
||||
107
web/include/vditor/src/ts/markdown/speechRender.ts
Normal file
107
web/include/vditor/src/ts/markdown/speechRender.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import {setSelectionFocus} from "../util/selection";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
vditorSpeechRange: Range;
|
||||
}
|
||||
}
|
||||
|
||||
export const speechRender = (element: HTMLElement, lang: keyof II18n = "zh_CN") => {
|
||||
if (typeof speechSynthesis === "undefined" || typeof SpeechSynthesisUtterance === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
const getVoice = () => {
|
||||
const voices = speechSynthesis.getVoices();
|
||||
let currentVoice: SpeechSynthesisVoice;
|
||||
let defaultVoice;
|
||||
voices.forEach((item) => {
|
||||
if (item.lang === lang.replace("_", "-")) {
|
||||
currentVoice = item;
|
||||
}
|
||||
if (item.default) {
|
||||
defaultVoice = item;
|
||||
}
|
||||
});
|
||||
if (!currentVoice) {
|
||||
currentVoice = defaultVoice;
|
||||
}
|
||||
return currentVoice;
|
||||
};
|
||||
|
||||
let playSVG = '<svg><use xlink:href="#vditor-icon-play"></use></svg>';
|
||||
let pauseSVG = '<svg><use xlink:href="#vditor-icon-pause"></use></svg>';
|
||||
if (!document.getElementById("vditorIconScript")) {
|
||||
playSVG = '<svg viewBox="0 0 32 32"><path d="M3.436 0l25.128 16-25.128 16v-32z"></path></svg>';
|
||||
pauseSVG = '<svg viewBox="0 0 32 32"><path d="M20.617 0h9.128v32h-9.128v-32zM2.255 32v-32h9.128v32h-9.128z"></path></svg>';
|
||||
}
|
||||
|
||||
let speechDom: HTMLButtonElement = document.querySelector(".vditor-speech");
|
||||
if (!speechDom) {
|
||||
speechDom = document.createElement("button");
|
||||
speechDom.className = "vditor-speech";
|
||||
element.insertAdjacentElement("beforeend", speechDom);
|
||||
if (speechSynthesis.onvoiceschanged !== undefined) {
|
||||
speechSynthesis.onvoiceschanged = getVoice;
|
||||
}
|
||||
}
|
||||
const voice = getVoice();
|
||||
const utterThis = new SpeechSynthesisUtterance();
|
||||
utterThis.voice = voice;
|
||||
utterThis.onend = utterThis.onerror = () => {
|
||||
speechDom.style.display = "none";
|
||||
speechSynthesis.cancel();
|
||||
speechDom.classList.remove("vditor-speech--current");
|
||||
speechDom.innerHTML = playSVG;
|
||||
};
|
||||
|
||||
element.addEventListener(window.ontouchstart !== undefined ? "touchend" : "click", (event) => {
|
||||
const target = event.target as HTMLElement
|
||||
if (target.classList.contains("vditor-speech") || target.parentElement.classList.contains("vditor-speech")) {
|
||||
if (!speechDom.classList.contains("vditor-speech--current")) {
|
||||
utterThis.text = speechDom.getAttribute("data-text");
|
||||
speechSynthesis.speak(utterThis);
|
||||
speechDom.classList.add("vditor-speech--current");
|
||||
speechDom.innerHTML = pauseSVG;
|
||||
} else {
|
||||
if (speechSynthesis.speaking) {
|
||||
if (speechSynthesis.paused) {
|
||||
speechSynthesis.resume();
|
||||
speechDom.innerHTML = pauseSVG;
|
||||
} else {
|
||||
speechSynthesis.pause();
|
||||
speechDom.innerHTML = playSVG;
|
||||
}
|
||||
}
|
||||
}
|
||||
setSelectionFocus(window.vditorSpeechRange);
|
||||
element.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
speechDom.style.display = "none";
|
||||
speechSynthesis.cancel();
|
||||
speechDom.classList.remove("vditor-speech--current");
|
||||
speechDom.innerHTML = playSVG;
|
||||
|
||||
if (getSelection().rangeCount === 0) {
|
||||
return;
|
||||
}
|
||||
const range = getSelection().getRangeAt(0)
|
||||
const text = range.toString().trim();
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
window.vditorSpeechRange = range.cloneRange();
|
||||
const rect = range.getBoundingClientRect();
|
||||
speechDom.innerHTML = playSVG;
|
||||
speechDom.style.display = "block";
|
||||
speechDom.style.top = (rect.top + rect.height + document.querySelector("html").scrollTop - 20) + "px";
|
||||
if (window.ontouchstart !== undefined) {
|
||||
speechDom.style.left = ((event as TouchEvent).changedTouches[(event as TouchEvent).changedTouches.length - 1].pageX + 2) + "px";
|
||||
} else {
|
||||
speechDom.style.left = ((event as MouseEvent).clientX + 2) + "px";
|
||||
}
|
||||
speechDom.setAttribute("data-text", text);
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user