This commit is contained in:
Achuan-2 2025-12-16 17:04:19 +08:00 committed by GitHub
commit 8ca6d2c8dd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 476 additions and 220 deletions

View file

@ -1,21 +1,21 @@
import {hideMessage, showMessage} from "../../dialog/message";
import {Constants} from "../../constants";
import { hideMessage, showMessage } from "../../dialog/message";
import { Constants } from "../../constants";
/// #if !BROWSER
import {ipcRenderer} from "electron";
import { ipcRenderer } from "electron";
import * as fs from "fs";
import * as path from "path";
import {afterExport} from "./util";
import { afterExport } from "./util";
/// #endif
import {confirmDialog} from "../../dialog/confirmDialog";
import {getThemeMode, setInlineStyle} from "../../util/assets";
import {fetchPost, fetchSyncPost} from "../../util/fetch";
import {Dialog} from "../../dialog";
import {replaceLocalPath} from "../../editor/rename";
import {getScreenWidth, isInAndroid, isInHarmony, isInIOS, setStorageVal} from "../util/compatibility";
import {getFrontend} from "../../util/functions";
import { confirmDialog } from "../../dialog/confirmDialog";
import { getThemeMode, setInlineStyle } from "../../util/assets";
import { fetchPost, fetchSyncPost } from "../../util/fetch";
import { Dialog } from "../../dialog";
import { replaceLocalPath } from "../../editor/rename";
import { getScreenWidth, isInAndroid, isInHarmony, isInIOS, setStorageVal } from "../util/compatibility";
import { getFrontend } from "../../util/functions";
const getPluginStyle = async () => {
const response = await fetchSyncPost("/api/petal/loadPetals", {frontend: getFrontend()});
const response = await fetchSyncPost("/api/petal/loadPetals", { frontend: getFrontend() });
let css = "";
// 为加快启动速度,不进行 await
response.data.forEach((item: IPluginData) => {
@ -103,7 +103,7 @@ export const saveExport = (option: IExportOptions) => {
btnsElement[1].addEventListener("click", () => {
const removeAssets = (wordDialog.element.querySelector("#removeAssets") as HTMLInputElement).checked;
const mergeSubdocs = (wordDialog.element.querySelector("#mergeSubdocs") as HTMLInputElement).checked;
window.siyuan.storage[Constants.LOCAL_EXPORTWORD] = {removeAssets, mergeSubdocs};
window.siyuan.storage[Constants.LOCAL_EXPORTWORD] = { removeAssets, mergeSubdocs };
setStorageVal(Constants.LOCAL_EXPORTWORD, window.siyuan.storage[Constants.LOCAL_EXPORTWORD]);
getExportPath(option, removeAssets, mergeSubdocs);
wordDialog.destroy();
@ -476,7 +476,7 @@ ${getIconScript(servePath)}
Protyle.flowchartRender(wysElement, "${servePath}stage/protyle");
Protyle.graphvizRender(wysElement, "${servePath}stage/protyle");
Protyle.chartRender(wysElement, "${servePath}stage/protyle");
Protyle.mindmapRender(wysElement, "${servePath}stage/protyle");
Protyle.mindmapRender(wysElement, "${servePath}stage/protyle", {zoom: false, pan: false});
Protyle.abcRender(wysElement, "${servePath}stage/protyle");
Protyle.htmlRender(wysElement);
Protyle.plantumlRender(wysElement, "${servePath}stage/protyle");
@ -637,7 +637,7 @@ ${getIconScript(servePath)}
</script>
${getSnippetJS()}
</body></html>`;
fetchPost("/api/export/exportTempContent", {content: html}, (response) => {
fetchPost("/api/export/exportTempContent", { content: html }, (response) => {
ipcRenderer.send(Constants.SIYUAN_EXPORT_NEWWINDOW, response.data.url);
});
};
@ -722,7 +722,7 @@ export const onExport = async (data: IWebSocketData, filePath: string, servePath
js: `document.body.style.minWidth = "${screenWidth}px";`,
css: `@page { size: A4; margin: 10mm 0 10mm 0; background-color: var(--b3-theme-background); }
.protyle-wysiwyg {padding: 0; margin: 0;}`
} : {js: "", css: ""};
} : { js: "", css: "" };
const html = `<!DOCTYPE html>
<html lang="${window.siyuan.config.appearance.lang}" data-theme-mode="${isInMobile ? "light" : getThemeMode()}" data-light-theme="${window.siyuan.config.appearance.themeLight}" data-dark-theme="${window.siyuan.config.appearance.themeDark}">
<head>
@ -775,7 +775,7 @@ ${getIconScript(servePath)}
Protyle.flowchartRender(previewElement, "stage/protyle");
Protyle.graphvizRender(previewElement, "stage/protyle");
Protyle.chartRender(previewElement, "stage/protyle");
Protyle.mindmapRender(previewElement, "stage/protyle");
Protyle.mindmapRender(previewElement, "stage/protyle", {zoom: false, pan: false});
Protyle.abcRender(previewElement, "stage/protyle");
Protyle.htmlRender(previewElement);
Protyle.plantumlRender(previewElement, "stage/protyle");

View file

@ -1,9 +1,9 @@
import {addScript} from "../util/addScript";
import {Constants} from "../../constants";
import {hasClosestByClassName} from "../util/hasClosest";
import {genIconHTML} from "./util";
import { addScript } from "../util/addScript";
import { Constants } from "../../constants";
import { hasClosestByClassName } from "../util/hasClosest";
import { genIconHTML } from "./util";
export const mindmapRender = (element: Element, cdn = Constants.PROTYLE_CDN) => {
export const mindmapRender = (element: Element, cdn = Constants.PROTYLE_CDN, markmapOptions: { zoom?: boolean; pan?: boolean } = {}) => {
let mindmapElements: Element[] = [];
if (element.getAttribute("data-subtype") === "mindmap") {
// 编辑器内代码块编辑渲染
@ -14,78 +14,158 @@ export const mindmapRender = (element: Element, cdn = Constants.PROTYLE_CDN) =>
if (mindmapElements.length === 0) {
return;
}
addScript(`${cdn}/js/echarts/echarts.min.js?v=0.0.0`, "protyleEchartsScript").then(() => {
const wysiswgElement = hasClosestByClassName(element, "protyle-wysiwyg", true);
let width: number = undefined;
if (wysiswgElement && wysiswgElement.clientWidth > 0 && mindmapElements[0].firstElementChild.clientWidth === 0 && wysiswgElement.firstElementChild) {
width = wysiswgElement.firstElementChild.clientWidth;
}
mindmapElements.forEach((e: HTMLDivElement) => {
if (e.getAttribute("data-render") === "true") {
return;
// load d3 first, then markmap-lib, then markmap-view (in order)
addScript(`${cdn}/js/d3/d3.min.js?v6.7.0`, "protyleD3Script")
.then(() => addScript(`${cdn}/js/markmap/markmap-lib.min.js?v0.14.4`, "protyleMarkmapLibScript"))
.then(() => addScript(`${cdn}/js/markmap/markmap-view.min.js?v0.14.4`, "protyleMarkmapScript"))
.then(() => {
const wysiswgElement = hasClosestByClassName(element, "protyle-wysiwyg", true);
let width: number = undefined;
if (wysiswgElement && wysiswgElement.clientWidth > 0 && mindmapElements[0].firstElementChild.clientWidth === 0 && wysiswgElement.firstElementChild) {
width = wysiswgElement.firstElementChild.clientWidth;
}
if (!e.firstElementChild.classList.contains("protyle-icons")) {
e.insertAdjacentHTML("afterbegin", genIconHTML(wysiswgElement));
}
const renderElement = e.firstElementChild.nextElementSibling as HTMLElement;
if (!e.getAttribute("data-content")) {
renderElement.innerHTML = `<span style="position: absolute;left:0;top:0;width: 1px;">${Constants.ZWSP}</span>`;
return;
}
try {
if (!renderElement.lastElementChild || renderElement.childElementCount === 1) {
renderElement.innerHTML = `<span style="position: absolute;left:0;top:0;width: 1px;">${Constants.ZWSP}</span><div style="height:${e.style.height || "420px"}" contenteditable="false"></div>`;
} else {
renderElement.lastElementChild.classList.remove("ft__error");
mindmapElements.forEach((e: HTMLDivElement) => {
if (e.getAttribute("data-render") === "true") {
return;
}
window.echarts.init(renderElement.lastElementChild, window.siyuan.config.appearance.mode === 1 ? "dark" : undefined, {
width,
}).setOption({
series: [
{
data: [JSON.parse(Lute.EChartsMindmapStr(Lute.UnEscapeHTMLStr(e.getAttribute("data-content"))))],
initialTreeDepth: -1,
itemStyle: {
borderWidth: 0,
color: "#4285f4",
},
label: {
backgroundColor: "#f6f8fa",
borderColor: "#d1d5da",
borderRadius: 6,
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?: string } }) => {
if (params?.data?.children) {
return "circle";
} else {
return "path://";
if (!e.firstElementChild.classList.contains("protyle-icons")) {
// Add home icon for mindmap (reset view), edit and more
e.insertAdjacentHTML("afterbegin", genIconHTML(wysiswgElement, ["home", "edit", "more"]));
}
const renderElement = e.firstElementChild.nextElementSibling as HTMLElement;
if (!e.getAttribute("data-content")) {
renderElement.innerHTML = `<span style="position: absolute;left:0;top:0;width: 1px;">${Constants.ZWSP}</span>`;
return;
}
let transformer: any = null;
try {
// create or reuse container for markmap
if (!renderElement.lastElementChild || renderElement.childElementCount === 1) {
renderElement.innerHTML = `<span style="position: absolute;left:0;top:0;width: 1px;">${Constants.ZWSP}</span><div style="height:${e.style.height || "420px"}" contenteditable="false"></div>`;
} else {
renderElement.lastElementChild.classList.remove("ft__error");
}
// Convert stored content to markdown using Lute (prefer existing instance), then transform/render with markmap
const raw = Lute.UnEscapeHTMLStr(e.getAttribute("data-content"));
let md: string = raw;
// prefer protyle's lute instance if available
if ((window as any).protyle && (window as any).protyle.lute && typeof (window as any).protyle.lute.BlockDOM2Md === "function") {
md = (window as any).protyle.lute.BlockDOM2Md(raw);
} else if (typeof Lute === "function" && typeof Lute.New === "function") {
try {
const luteInst = Lute.New();
if (luteInst && typeof luteInst.BlockDOM2Md === "function") {
md = luteInst.BlockDOM2Md(raw);
} else if (luteInst && typeof luteInst.BlockDOM2HTML === "function") {
md = luteInst.BlockDOM2HTML(raw);
}
} catch (e) {
// fallback to raw
md = raw;
}
}
// Try to obtain markmap entry from loaded bundles (single unified reference)
const mm: any = (window as any).markmap || (window as any).Markmap || null;
// Prefer the Transformer API when available (transform -> getUsedAssets -> load assets -> create)
let rootData: any = null;
if (mm) {
if (typeof mm.Transformer === "function") {
transformer = new mm.Transformer();
// transform markdown -> { root, features }
const tx = transformer.transform(md) || {};
rootData = tx.root || null;
// select asset getter: prefer getUsedAssets then getAssets
const assetsGetter = typeof transformer.getUsedAssets === "function" ? "getUsedAssets" : (typeof transformer.getAssets === "function" ? "getAssets" : null);
if (assetsGetter) {
const assets = (transformer as any)[assetsGetter](tx.features || {});
const styles = assets && assets.styles;
const scripts = assets && assets.scripts;
const loadCSS = typeof mm.loadCSS === "function" ? mm.loadCSS : null;
const loadJS = typeof mm.loadJS === "function" ? mm.loadJS : null;
if (styles && loadCSS) {
try { loadCSS(styles); } catch (err) { /* ignore */ }
}
},
type: "tree",
},
],
tooltip: {
trigger: "item",
triggerOn: "mousemove",
},
backgroundColor: "transparent",
});
} catch (error) {
window.echarts.dispose(renderElement.lastElementChild);
renderElement.innerHTML = `<span style="position: absolute;left:0;top:0;width: 1px;">${Constants.ZWSP}</span><div class="ft__error" style="height:${e.style.height || "420px"}" contenteditable="false">Mindmap render error: <br>${error}</div>`;
}
e.setAttribute("data-render", "true");
if (scripts && loadJS) {
try { loadJS(scripts, { getMarkmap: () => (window as any).markmap || mm }); } catch (err) { /* ignore */ }
}
}
} else {
// fallback: try older transform functions (may return root-like object)
const transformFn = mm.transform || (window as any).markmap && (window as any).markmap.transform;
if (typeof transformFn === "function") {
try {
const tx = transformFn(md) || {};
rootData = tx.root || tx || null;
} catch (err) {
rootData = null;
}
}
}
}
// container for svg
const container = renderElement.lastElementChild as HTMLElement;
// clear existing content and append an svg for markmap
container.innerHTML = "";
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
// use width if calculated earlier
if (typeof width === "number" && width > 0) {
svg.setAttribute("width", String(width));
} else {
svg.setAttribute("width", "100%");
}
svg.setAttribute("height", "100%");
container.appendChild(svg);
// prefer Markmap.create if available
const MarkmapCtor = (mm && (mm.Markmap || mm.default || mm)) || (window as any).Markmap;
// default options, allow overriding via markmapOptions (e.g. in export we can pass zoom/pan false)
const options = Object.assign({
duration: 0, // 🔥 禁用动画设为0
}, markmapOptions || {});
// create and store markmap + transformer on the element so callers can update instead of re-creating
if (MarkmapCtor && typeof MarkmapCtor.create === "function") {
if (rootData) {
const markmapInstance = MarkmapCtor.create(svg, options, rootData);
const mmEntry: any = (e as any).__markmap || {};
mmEntry.transformer = transformer || mmEntry.transformer || null;
mmEntry.markmap = markmapInstance;
mmEntry.options = options;
(e as any).__markmap = mmEntry;
}
} else {
throw new Error("Markmap not available");
}
} catch (error) {
renderElement.innerHTML = `<span style="position: absolute;left:0;top:0;width: 1px;">${Constants.ZWSP}</span><div class="ft__error" style="height:${e.style.height || "420px"}" contenteditable="false">Mindmap render error: <br>${error}</div>`;
}
e.setAttribute("data-render", "true");
// expose a small helper to update content (callable by toolbar)
try {
const mmEntry: any = (e as any).__markmap;
if (mmEntry && mmEntry.transformer && mmEntry.markmap) {
(e as any).__markmap.updateContent = (newMarkdown: string) => {
try {
const tx2 = mmEntry.transformer.transform(newMarkdown) || {};
const root2 = tx2.root || null;
if (mmEntry.markmap && typeof mmEntry.markmap.setData === "function") {
mmEntry.markmap.setData(root2, mmEntry.options);
}
} catch (e) {
// ignore update errors
console.error("markmap updateContent error", e);
}
};
}
} catch (e) {
// ignore
}
});
});
});
};

View file

@ -1,5 +1,5 @@
import {isInEmbedBlock} from "../util/hasClosest";
import {Constants} from "../../constants";
import { isInEmbedBlock } from "../util/hasClosest";
import { Constants } from "../../constants";
export const genIconHTML = (element?: false | HTMLElement, actions = ["edit", "more"]) => {
let enable = true;
@ -11,18 +11,51 @@ export const genIconHTML = (element?: false | HTMLElement, actions = ["edit", "m
return '<div class="protyle-icons"></div>';
}
}
if (actions.length === 3) {
return `<div class="protyle-icons">
<span aria-label="${window.siyuan.languages.refresh}" data-position="4north" class="ariaLabel protyle-icon protyle-icon--first protyle-action__reload"><svg><use xlink:href="#iconRefresh"></use></svg></span>
<span aria-label="${window.siyuan.languages.edit}" data-position="4north" class="ariaLabel protyle-icon protyle-action__edit${enable ? "" : " fn__none"}"><svg><use xlink:href="#iconEdit"></use></svg></span>
<span aria-label="${window.siyuan.languages.more}" data-position="4north" class="ariaLabel protyle-icon protyle-action__menu protyle-icon--last"><svg><use xlink:href="#iconMore"></use></svg></span>
</div>`;
} else {
return `<div class="protyle-icons">
<span aria-label="${window.siyuan.languages.edit}" data-position="4north" class="ariaLabel protyle-icon protyle-icon--first protyle-action__edit${enable ? "" : " fn__none"}"><svg><use xlink:href="#iconEdit"></use></svg></span>
<span aria-label="${window.siyuan.languages.more}" data-position="4north" class="ariaLabel protyle-icon protyle-action__menu protyle-icon--last${enable ? "" : " protyle-icon--first"}"><svg><use xlink:href="#iconMore"></use></svg></span>
</div>`;
const mapActionToHTML = (action: string, isFirst: boolean, isLast: boolean) => {
const classList = ["ariaLabel", "protyle-icon"];
if (isFirst) classList.push("protyle-icon--first");
if (isLast) classList.push("protyle-icon--last");
let aria = "";
let className = "";
let icon = "";
switch (action) {
case "reload":
case "refresh":
aria = window.siyuan.languages.refresh;
className = "protyle-action__reload";
icon = "iconRefresh";
break;
case "home":
case "fit":
aria = window.siyuan.languages.reset;
className = "protyle-action__home";
icon = "iconHistory";
break;
case "edit":
aria = window.siyuan.languages.edit;
className = "protyle-action__edit";
icon = "iconEdit";
break;
case "more":
default:
aria = window.siyuan.languages.more;
className = "protyle-action__menu";
icon = "iconMore";
break;
}
// Only the edit button honors read-only enable
const hidden = (action === "edit" && !enable) ? " fn__none" : "";
return `<span aria-label="${aria}" data-position="4north" class="${classList.join(" ")} ${className}${hidden}"><svg><use xlink:href="#${icon}"></use></svg></span>`;
};
const res: string[] = [];
for (let i = 0; i < actions.length; i++) {
const isFirst = i === 0;
const isLast = i === actions.length - 1;
res.push(mapActionToHTML(actions[i], isFirst, isLast));
}
return `<div class="protyle-icons">
${res.join("\n ")}
</div>`;
};
export const genRenderFrame = (renderElement: Element) => {

View file

@ -1,6 +1,6 @@
import {Divider} from "./Divider";
import {Font, hasSameTextStyle, setFontStyle} from "./Font";
import {ToolbarItem} from "./ToolbarItem";
import { Divider } from "./Divider";
import { Font, hasSameTextStyle, setFontStyle } from "./Font";
import { ToolbarItem } from "./ToolbarItem";
import {
fixTableRange,
focusBlock,
@ -12,40 +12,40 @@ import {
setFirstNodeRange,
setLastNodeRange
} from "../util/selection";
import {hasClosestBlock, hasClosestByAttribute, hasClosestByClassName} from "../util/hasClosest";
import {Link} from "./Link";
import {setPosition} from "../../util/setPosition";
import {transaction, updateTransaction} from "../wysiwyg/transaction";
import {Constants} from "../../constants";
import {copyPlainText, openByMobile, readClipboard, setStorageVal} from "../util/compatibility";
import {upDownHint} from "../../util/upDownHint";
import {highlightRender} from "../render/highlightRender";
import {getContenteditableElement, hasNextSibling, hasPreviousSibling} from "../wysiwyg/getBlock";
import {processRender} from "../util/processCode";
import {BlockRef} from "./BlockRef";
import {hintRenderTemplate, hintRenderWidget} from "../hint/extend";
import {blockRender} from "../render/blockRender";
import { hasClosestBlock, hasClosestByAttribute, hasClosestByClassName } from "../util/hasClosest";
import { Link } from "./Link";
import { setPosition } from "../../util/setPosition";
import { transaction, updateTransaction } from "../wysiwyg/transaction";
import { Constants } from "../../constants";
import { copyPlainText, openByMobile, readClipboard, setStorageVal } from "../util/compatibility";
import { upDownHint } from "../../util/upDownHint";
import { highlightRender } from "../render/highlightRender";
import { getContenteditableElement, hasNextSibling, hasPreviousSibling } from "../wysiwyg/getBlock";
import { processRender } from "../util/processCode";
import { BlockRef } from "./BlockRef";
import { hintRenderTemplate, hintRenderWidget } from "../hint/extend";
import { blockRender } from "../render/blockRender";
/// #if !BROWSER
import {openBy} from "../../editor/util";
import { openBy } from "../../editor/util";
/// #endif
import {fetchPost} from "../../util/fetch";
import {isArrayEqual, isMobile} from "../../util/functions";
import { fetchPost } from "../../util/fetch";
import { isArrayEqual, isMobile } from "../../util/functions";
import * as dayjs from "dayjs";
import {insertEmptyBlock} from "../../block/util";
import {matchHotKey} from "../util/hotKey";
import {hideElements} from "../ui/hideElements";
import {electronUndo} from "../undo";
import {previewTemplate, toolbarKeyToMenu} from "./util";
import {hideMessage, showMessage} from "../../dialog/message";
import {InlineMath} from "./InlineMath";
import {InlineMemo} from "./InlineMemo";
import {mathRender} from "../render/mathRender";
import {linkMenu} from "../../menus/protyle";
import {addScript} from "../util/addScript";
import {confirmDialog} from "../../dialog/confirmDialog";
import {paste, pasteAsPlainText, pasteEscaped} from "../util/paste";
import {escapeHtml} from "../../util/escape";
import {resizeSide} from "../../history/resizeSide";
import { insertEmptyBlock } from "../../block/util";
import { matchHotKey } from "../util/hotKey";
import { hideElements } from "../ui/hideElements";
import { electronUndo } from "../undo";
import { previewTemplate, toolbarKeyToMenu } from "./util";
import { hideMessage, showMessage } from "../../dialog/message";
import { InlineMath } from "./InlineMath";
import { InlineMemo } from "./InlineMemo";
import { mathRender } from "../render/mathRender";
import { linkMenu } from "../../menus/protyle";
import { addScript } from "../util/addScript";
import { confirmDialog } from "../../dialog/confirmDialog";
import { paste, pasteAsPlainText, pasteEscaped } from "../util/paste";
import { escapeHtml } from "../../util/escape";
import { resizeSide } from "../../history/resizeSide";
export class Toolbar {
public element: HTMLElement;
@ -309,12 +309,12 @@ export class Toolbar {
const startPreviousSibling = hasPreviousSibling(this.range.startContainer);
const endNextSibling = hasNextSibling(this.range.endContainer);
if ((
this.range.startOffset !== 0 ||
// https://github.com/siyuan-note/siyuan/issues/14869
(this.range.startOffset === 0 && startPreviousSibling &&
(startPreviousSibling.nodeType === 3 || (startPreviousSibling as HTMLElement).tagName === "BR") &&
this.range.startContainer.previousSibling.parentElement === this.range.startContainer.parentElement)
) && (
this.range.startOffset !== 0 ||
// https://github.com/siyuan-note/siyuan/issues/14869
(this.range.startOffset === 0 && startPreviousSibling &&
(startPreviousSibling.nodeType === 3 || (startPreviousSibling as HTMLElement).tagName === "BR") &&
this.range.startContainer.previousSibling.parentElement === this.range.startContainer.parentElement)
) && (
this.range.endOffset !== this.range.endContainer.textContent.length ||
// https://github.com/siyuan-note/siyuan/issues/14869#issuecomment-2911553387
(
@ -987,6 +987,17 @@ export class Toolbar {
break;
case "refresh":
btnElement.classList.toggle("block__icon--active");
// If this is a mindmap, call markmap instance fit() to reset view
if (renderElement.getAttribute("data-subtype") === "mindmap") {
const mmEntry: any = (renderElement as any).__markmap || (nodeElement as any).__markmap || null;
if (mmEntry && mmEntry.markmap && typeof mmEntry.markmap.fit === "function") {
try {
mmEntry.markmap.fit();
} catch (e) {
console.error("markmap fit error", e);
}
}
}
break;
case "before":
insertEmptyBlock(protyle, "beforebegin", id);
@ -1017,6 +1028,55 @@ export class Toolbar {
});
return;
}
// mindmap: try to export SVG directly using the rendered SVG
if (renderElement.getAttribute("data-subtype") === "mindmap") {
try {
// find rendered svg inside the renderElement
const svgElement = renderElement.querySelector("svg.markmap") as SVGSVGElement;
if (!svgElement) {
throw new Error("SVG not found");
}
const clonedSvg = svgElement.cloneNode(true) as SVGSVGElement;
const bbox = svgElement.getBBox();
clonedSvg.setAttribute("viewBox", `${bbox.x - 20} ${bbox.y - 20} ${bbox.width + 40} ${bbox.height + 40}`);
clonedSvg.setAttribute("width", String(bbox.width + 40));
clonedSvg.setAttribute("height", String(bbox.height + 40));
clonedSvg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
clonedSvg.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
// inline styles
const styles = Array.from(document.styleSheets)
.filter(sheet => {
try {
return sheet.cssRules;
} catch (e) {
return false;
}
})
.reduce((acc, sheet) => {
return acc + Array.from((sheet as CSSStyleSheet).cssRules).map(rule => rule.cssText).join("\n");
}, "");
const styleElement = document.createElementNS("http://www.w3.org/2000/svg", "style");
styleElement.textContent = styles;
clonedSvg.insertBefore(styleElement, clonedSvg.firstChild);
const serializer = new XMLSerializer();
const svgString = serializer.serializeToString(clonedSvg);
const blob = new Blob([svgString], { type: "image/svg+xml;charset=utf-8" });
const formData = new FormData();
formData.append("file", blob);
formData.append("type", "image/svg+xml");
fetchPost("/api/export/exportAsFile", formData, (response) => {
openByMobile(response.data.file);
hideMessage(msgId);
});
return;
} catch (err) {
console.error("mindmap export error", err);
// fall through to html-to-image fallback
}
}
setTimeout(() => {
addScript("/stage/protyle/js/html-to-image.min.js?v=1.11.13", "protyleHtml2image").then(() => {
(renderElement as HTMLHtmlElement).style.display = "inline-block";
@ -1067,11 +1127,32 @@ export class Toolbar {
}
});
} else {
renderElement.setAttribute("data-content", Lute.EscapeHTMLStr(textElement.value));
renderElement.removeAttribute("data-render");
// mindmap: try incremental update via stored markmap instance
const isMindmap = renderElement.getAttribute("data-subtype") === "mindmap";
const mmEntry: any = (renderElement as any).__markmap || (nodeElement as any).__markmap || null;
if (isMindmap && mmEntry && mmEntry.transformer && mmEntry.markmap && this.subElement.querySelector('[data-type="refresh"]').classList.contains("block__icon--active")) {
try {
// update markmap in-place
const txu = mmEntry.transformer.transform(textElement.value) || {};
const rootu = txu.root || null;
mmEntry.markmap.setData(rootu, mmEntry.options);
renderElement.setAttribute("data-content", Lute.EscapeHTMLStr(textElement.value));
} catch (err) {
// fallback to re-render
renderElement.setAttribute("data-content", Lute.EscapeHTMLStr(textElement.value));
renderElement.removeAttribute("data-render");
}
} else {
renderElement.setAttribute("data-content", Lute.EscapeHTMLStr(textElement.value));
renderElement.removeAttribute("data-render");
}
}
if (!types.includes("NodeBlockQueryEmbed") || !types.includes("NodeHTMLBlock") || !isInlineMemo) {
processRender(renderElement);
// If mindmap was updated via markmap.setData above, no need to run full processRender.
const didIncrementalUpdate = renderElement.getAttribute("data-subtype") === "mindmap" && ((renderElement as any).__markmap && (renderElement as any).__markmap.markmap) && this.subElement.querySelector('[data-type="refresh"]').classList.contains("block__icon--active");
if (!didIncrementalUpdate) {
if (!types.includes("NodeBlockQueryEmbed") || !types.includes("NodeHTMLBlock") || !isInlineMemo) {
processRender(renderElement);
}
}
event.stopPropagation();
});
@ -1158,11 +1239,30 @@ export class Toolbar {
} else {
renderElement.setAttribute("data-content", Lute.EscapeHTMLStr(textElement.value));
renderElement.removeAttribute("data-render");
if (types.includes("NodeBlockQueryEmbed")) {
blockRender(protyle, renderElement);
(renderElement as HTMLElement).style.height = "";
// If this is a mindmap and we have a stored markmap instance, prefer incremental update
const isMindmap = renderElement.getAttribute("data-subtype") === "mindmap";
const mmEntry: any = (renderElement as any).__markmap || (nodeElement as any).__markmap || null;
if (isMindmap && mmEntry && mmEntry.transformer && mmEntry.markmap) {
try {
const txu = mmEntry.transformer.transform(textElement.value) || {};
const rootu = txu.root || null;
mmEntry.markmap.setData(rootu, mmEntry.options);
} catch (err) {
// fallback to full render
if (types.includes("NodeBlockQueryEmbed")) {
blockRender(protyle, renderElement);
(renderElement as HTMLElement).style.height = "";
} else {
processRender(renderElement);
}
}
} else {
processRender(renderElement);
if (types.includes("NodeBlockQueryEmbed")) {
blockRender(protyle, renderElement);
(renderElement as HTMLElement).style.height = "";
} else {
processRender(renderElement);
}
}
}
@ -1248,7 +1348,7 @@ export class Toolbar {
let html = `<div data-id="clearLanguage" class="b3-list-item">${window.siyuan.languages.clear}</div>`;
let hljsLanguages = Constants.ALIAS_CODE_LANGUAGES.concat(window.hljs?.listLanguages() ?? []).sort();
const eventDetail = {languages: hljsLanguages, type: "init", listElement};
const eventDetail = { languages: hljsLanguages, type: "init", listElement };
if (protyle.app && protyle.app.plugins) {
protyle.app.plugins.forEach((plugin: any) => {
plugin.eventBus.emit("code-language-update", eventDetail);
@ -1322,7 +1422,7 @@ export class Toolbar {
}
}
const eventDetail = {languages: value ? matchLanguages : hljsLanguages, type: "match", value, listElement};
const eventDetail = { languages: value ? matchLanguages : hljsLanguages, type: "match", value, listElement };
if (protyle.app && protyle.app.plugins) {
protyle.app.plugins.forEach((plugin: any) => {
plugin.eventBus.emit("code-language-update", eventDetail);
@ -1489,7 +1589,7 @@ export class Toolbar {
/// #endif
if (iconElement && iconElement.getAttribute("data-type") === "remove") {
confirmDialog(window.siyuan.languages.remove, window.siyuan.languages.confirmDelete + "?", () => {
fetchPost("/api/search/removeTemplate", {path: iconElement.parentElement.getAttribute("data-value")}, () => {
fetchPost("/api/search/removeTemplate", { path: iconElement.parentElement.getAttribute("data-value") }, () => {
if (iconElement.parentElement.parentElement.childElementCount === 1) {
iconElement.parentElement.parentElement.innerHTML = `<li class="b3-list--empty">${window.siyuan.languages.emptyContent}</li>`;
previewTemplate("", previewElement, protyle.block.parentID);
@ -1513,13 +1613,13 @@ export class Toolbar {
}
const previousElement = hasClosestByAttribute(target, "data-type", "previous");
if (previousElement) {
inputElement.dispatchEvent(new KeyboardEvent("keydown", {key: "ArrowUp"}));
inputElement.dispatchEvent(new KeyboardEvent("keydown", { key: "ArrowUp" }));
event.stopPropagation();
return;
}
const nextElement = hasClosestByAttribute(target, "data-type", "next");
if (nextElement) {
inputElement.dispatchEvent(new KeyboardEvent("keydown", {key: "ArrowDown"}));
inputElement.dispatchEvent(new KeyboardEvent("keydown", { key: "ArrowDown" }));
event.stopPropagation();
return;
}
@ -1691,7 +1791,7 @@ ${item.name}
} else {
try {
const text = await readClipboard();
paste(protyle, Object.assign(text, {target: nodeElement as HTMLElement}));
paste(protyle, Object.assign(text, { target: nodeElement as HTMLElement }));
} catch (e) {
console.log(e);
}

View file

@ -1,4 +1,4 @@
import {enableLuteMarkdownSyntax, getTextStar, paste, restoreLuteMarkdownSyntax} from "../util/paste";
import { enableLuteMarkdownSyntax, getTextStar, paste, restoreLuteMarkdownSyntax } from "../util/paste";
import {
hasClosestBlock,
hasClosestByAttribute,
@ -18,9 +18,9 @@ import {
setInsertWbrHTML,
setLastNodeRange,
} from "../util/selection";
import {Constants} from "../../constants";
import {isMobile} from "../../util/functions";
import {previewDocImage} from "../preview/image";
import { Constants } from "../../constants";
import { isMobile } from "../../util/functions";
import { previewDocImage } from "../preview/image";
import {
contentMenu,
enterBack,
@ -34,8 +34,8 @@ import {
zoomOut
} from "../../menus/protyle";
import * as dayjs from "dayjs";
import {dropEvent} from "../util/editorCommonEvent";
import {input} from "./input";
import { dropEvent } from "../util/editorCommonEvent";
import { input } from "./input";
import {
getContenteditableElement,
getNextBlock,
@ -45,43 +45,43 @@ import {
isEndOfBlock,
isNotEditBlock
} from "./getBlock";
import {transaction, updateTransaction} from "./transaction";
import {hideElements} from "../ui/hideElements";
import { transaction, updateTransaction } from "./transaction";
import { hideElements } from "../ui/hideElements";
/// #if !BROWSER
import {ipcRenderer} from "electron";
import { ipcRenderer } from "electron";
/// #endif
import {getEnableHTML, removeEmbed} from "./removeEmbed";
import {keydown} from "./keydown";
import {openMobileFileById} from "../../mobile/editor";
import {removeBlock} from "./remove";
import {highlightRender} from "../render/highlightRender";
import {openAttr} from "../../menus/commonMenuItem";
import {blockRender} from "../render/blockRender";
import { getEnableHTML, removeEmbed } from "./removeEmbed";
import { keydown } from "./keydown";
import { openMobileFileById } from "../../mobile/editor";
import { removeBlock } from "./remove";
import { highlightRender } from "../render/highlightRender";
import { openAttr } from "../../menus/commonMenuItem";
import { blockRender } from "../render/blockRender";
/// #if !MOBILE
import {getAllModels} from "../../layout/getAll";
import {pushBack} from "../../util/backForward";
import {openFileById} from "../../editor/util";
import {openGlobalSearch} from "../../search/util";
import { getAllModels } from "../../layout/getAll";
import { pushBack } from "../../util/backForward";
import { openFileById } from "../../editor/util";
import { openGlobalSearch } from "../../search/util";
/// #else
import {popSearch} from "../../mobile/menu/search";
import { popSearch } from "../../mobile/menu/search";
/// #endif
import {BlockPanel} from "../../block/Panel";
import {copyPlainText, isInIOS, isMac, isOnlyMeta, readClipboard, encodeBase64} from "../util/compatibility";
import {MenuItem} from "../../menus/Menu";
import {fetchPost, fetchSyncPost} from "../../util/fetch";
import {onGet} from "../util/onGet";
import {clearTableCell, isIncludeCell, setTableAlign} from "../util/table";
import {countBlockWord, countSelectWord} from "../../layout/status";
import {showMessage} from "../../dialog/message";
import {getBacklinkHeadingMore, loadBreadcrumb} from "./renderBacklink";
import {removeSearchMark} from "../toolbar/util";
import {activeBlur} from "../../mobile/util/keyboardToolbar";
import {commonClick} from "./commonClick";
import {avClick, avContextmenu, updateAVName} from "../render/av/action";
import {selectRow, stickyRow} from "../render/av/row";
import {showColMenu} from "../render/av/col";
import {openViewMenu} from "../render/av/view";
import {checkFold} from "../../util/noRelyPCFunction";
import { BlockPanel } from "../../block/Panel";
import { copyPlainText, isInIOS, isMac, isOnlyMeta, readClipboard, encodeBase64 } from "../util/compatibility";
import { MenuItem } from "../../menus/Menu";
import { fetchPost, fetchSyncPost } from "../../util/fetch";
import { onGet } from "../util/onGet";
import { clearTableCell, isIncludeCell, setTableAlign } from "../util/table";
import { countBlockWord, countSelectWord } from "../../layout/status";
import { showMessage } from "../../dialog/message";
import { getBacklinkHeadingMore, loadBreadcrumb } from "./renderBacklink";
import { removeSearchMark } from "../toolbar/util";
import { activeBlur } from "../../mobile/util/keyboardToolbar";
import { commonClick } from "./commonClick";
import { avClick, avContextmenu, updateAVName } from "../render/av/action";
import { selectRow, stickyRow } from "../render/av/row";
import { showColMenu } from "../render/av/col";
import { openViewMenu } from "../render/av/view";
import { checkFold } from "../../util/noRelyPCFunction";
import {
addDragFill,
dragFillCellsValue,
@ -91,6 +91,16 @@ import {
getTypeByCellElement,
updateCellsValue
} from "../render/av/cell";
import { openEmojiPanel, unicode2Emoji } from "../../emoji";
import { openLink } from "../../editor/openLink";
import { mathRender } from "../render/mathRender";
import { editAssetItem } from "../render/av/asset";
import { img3115 } from "../../boot/compatibleVersion";
import { globalClickHideMenu } from "../../boot/globalEvent/click";
import { hideTooltip } from "../../dialog/tooltip";
import { openGalleryItemMenu } from "../render/av/gallery/util";
import { clearSelect } from "../util/clearSelect";
import { chartRender } from "../render/chartRender";
import {openEmojiPanel, unicode2Emoji} from "../../emoji";
import {openLink} from "../../editor/openLink";
import {mathRender} from "../render/mathRender";
@ -1554,11 +1564,11 @@ export class WYSIWYG {
const scrollTop = tableBlockElement.querySelector("table").scrollTop;
tableBlockElement.querySelectorAll("th, td").forEach((item: HTMLTableCellElement) => {
if (!item.classList.contains("fn__none") && isIncludeCell({
tableSelectElement,
scrollLeft,
scrollTop,
item,
}) &&
tableSelectElement,
scrollLeft,
scrollTop,
item,
}) &&
(selectCellElements.length === 0 || (selectCellElements.length > 0 && item.offsetTop === selectCellElements[0].offsetTop))) {
selectCellElements.push(item);
}
@ -1604,11 +1614,11 @@ export class WYSIWYG {
const scrollTop = tableBlockElement.querySelector("table").scrollTop;
tableBlockElement.querySelectorAll("th, td").forEach((item: HTMLTableCellElement) => {
if (!item.classList.contains("fn__none") && isIncludeCell({
tableSelectElement,
scrollLeft,
scrollTop,
item,
}) &&
tableSelectElement,
scrollLeft,
scrollTop,
item,
}) &&
(selectCellElements.length === 0 || (selectCellElements.length > 0 && item.offsetTop === selectCellElements[0].offsetTop))) {
selectCellElements.push(item);
}
@ -1700,7 +1710,7 @@ export class WYSIWYG {
} else if (tableBlockElement) {
try {
const text = await readClipboard();
paste(protyle, Object.assign(text, {target: tableBlockElement as HTMLElement}));
paste(protyle, Object.assign(text, { target: tableBlockElement as HTMLElement }));
} catch (e) {
console.log(e);
}
@ -1708,7 +1718,7 @@ export class WYSIWYG {
}
}).element);
}
window.siyuan.menus.menu.popup({x: mouseUpEvent.clientX - 8, y: mouseUpEvent.clientY - 16});
window.siyuan.menus.menu.popup({ x: mouseUpEvent.clientX - 8, y: mouseUpEvent.clientY - 16 });
}
}
@ -2104,7 +2114,7 @@ export class WYSIWYG {
// 多选块
hideElements(["util"], protyle);
protyle.gutter.renderMenu(protyle, selectElements[0]);
window.siyuan.menus.menu.popup({x, y});
window.siyuan.menus.menu.popup({ x, y });
return;
}
const target = event.detail.target || event.target as HTMLElement;
@ -2117,7 +2127,7 @@ export class WYSIWYG {
/// #if MOBILE
window.siyuan.menus.menu.fullscreen();
/// #else
window.siyuan.menus.menu.popup({x, y});
window.siyuan.menus.menu.popup({ x, y });
/// #endif
return false;
}
@ -2191,7 +2201,7 @@ export class WYSIWYG {
const avTabHeaderElement = hasClosestByClassName(target, "item");
if (nodeElement.classList.contains("av") && avTabHeaderElement) {
if (avTabHeaderElement.classList.contains("item--focus")) {
openViewMenu({protyle, blockElement: nodeElement, element: avTabHeaderElement});
openViewMenu({ protyle, blockElement: nodeElement, element: avTabHeaderElement });
} else {
transaction(protyle, [{
action: "setAttrViewBlockView",
@ -2275,7 +2285,7 @@ export class WYSIWYG {
) {
if ((!isMobile() || protyle.toolbar?.element.classList.contains("fn__none")) && !nodeElement.classList.contains("av")) {
contentMenu(protyle, nodeElement);
window.siyuan.menus.menu.popup({x, y: y + 13, h: 26});
window.siyuan.menus.menu.popup({ x, y: y + 13, h: 26 });
protyle.toolbar?.element.classList.add("fn__none");
if (nodeElement.classList.contains("table")) {
nodeElement.querySelector(".table__select").removeAttribute("style");
@ -2289,7 +2299,7 @@ export class WYSIWYG {
/// #if MOBILE
window.siyuan.menus.menu.fullscreen();
/// #else
window.siyuan.menus.menu.popup({x, y});
window.siyuan.menus.menu.popup({ x, y });
/// #endif
protyle.toolbar?.element.classList.add("fn__none");
}
@ -2360,7 +2370,7 @@ export class WYSIWYG {
window.siyuan.menus.menu.remove();
}
}
}, {passive: true});
}, { passive: true });
this.element.addEventListener("paste", (event: ClipboardEvent & { target: HTMLElement }) => {
// https://github.com/siyuan-note/siyuan/issues/11241
@ -2696,7 +2706,7 @@ export class WYSIWYG {
zoomIn,
scrollPosition: "start"
});
window.dispatchEvent(new KeyboardEvent("keydown", {key: "Escape"}));
window.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape" }));
} else if (event.altKey) {
openFileById({
app: protyle.app,
@ -2809,7 +2819,7 @@ export class WYSIWYG {
const tagElement = hasClosestByAttribute(event.target, "data-type", "tag");
if (tagElement && !event.altKey && !event.shiftKey && range.toString() === "") {
/// #if !MOBILE
openGlobalSearch(protyle.app, `#${tagElement.textContent}#`, !ctrlIsPressed, {method: 0});
openGlobalSearch(protyle.app, `#${tagElement.textContent}#`, !ctrlIsPressed, { method: 0 });
hideElements(["dialog"]);
/// #else
popSearch(protyle.app, {
@ -2863,7 +2873,7 @@ export class WYSIWYG {
app: protyle.app,
targetElement: embedItemElement,
isBacklink: false,
refDefs: [{refID: embedId}]
refDefs: [{ refID: embedId }]
}));
}
/// #endif
@ -2927,6 +2937,24 @@ export class WYSIWYG {
return;
}
const fitElement = hasClosestByClassName(event.target, "protyle-action__home");
if (fitElement) {
const blockElement = hasClosestBlock(fitElement);
if (blockElement && blockElement.getAttribute("data-subtype") === "mindmap") {
try {
const mmEntry: any = (blockElement as any).__markmap || null;
if (mmEntry && mmEntry.markmap && typeof mmEntry.markmap.fit === "function") {
mmEntry.markmap.fit();
}
} catch (e) {
console.error("markmap fit error", e);
}
}
event.stopPropagation();
event.preventDefault();
return;
}
const languageElement = hasClosestByClassName(event.target, "protyle-action__language");
if (languageElement && !protyle.disabled && !ctrlIsPressed) {
protyle.toolbar.showCodeLanguage(protyle, [languageElement]);
@ -2986,7 +3014,7 @@ export class WYSIWYG {
} else if (event.shiftKey && !protyle.disabled) {
openAttr(actionElement.parentElement, "bookmark", protyle);
} else if (ctrlIsPressed) {
zoomOut({protyle, id: actionId});
zoomOut({ protyle, id: actionId });
} else {
if (actionElement.classList.contains("protyle-action--task")) {
if (!protyle.disabled) {
@ -3005,7 +3033,7 @@ export class WYSIWYG {
if (protyle.block.showAll && protyle.block.id === actionId) {
enterBack(protyle, actionId);
} else {
zoomOut({protyle, id: actionId});
zoomOut({ protyle, id: actionId });
}
}
}

2
app/stage/protyle/js/d3/d3.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long