This commit is contained in:
Achuan-2 2025-12-16 10:56:48 +08:00 committed by GitHub
commit b3003249e2
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 { hideMessage, showMessage } from "../../dialog/message";
import {Constants} from "../../constants"; import { Constants } from "../../constants";
/// #if !BROWSER /// #if !BROWSER
import {ipcRenderer} from "electron"; import { ipcRenderer } from "electron";
import * as fs from "fs"; import * as fs from "fs";
import * as path from "path"; import * as path from "path";
import {afterExport} from "./util"; import { afterExport } from "./util";
/// #endif /// #endif
import {confirmDialog} from "../../dialog/confirmDialog"; import { confirmDialog } from "../../dialog/confirmDialog";
import {getThemeMode, setInlineStyle} from "../../util/assets"; import { getThemeMode, setInlineStyle } from "../../util/assets";
import {fetchPost, fetchSyncPost} from "../../util/fetch"; import { fetchPost, fetchSyncPost } from "../../util/fetch";
import {Dialog} from "../../dialog"; import { Dialog } from "../../dialog";
import {replaceLocalPath} from "../../editor/rename"; import { replaceLocalPath } from "../../editor/rename";
import {getScreenWidth, isInAndroid, isInHarmony, isInIOS, setStorageVal} from "../util/compatibility"; import { getScreenWidth, isInAndroid, isInHarmony, isInIOS, setStorageVal } from "../util/compatibility";
import {getFrontend} from "../../util/functions"; import { getFrontend } from "../../util/functions";
const getPluginStyle = async () => { const getPluginStyle = async () => {
const response = await fetchSyncPost("/api/petal/loadPetals", {frontend: getFrontend()}); const response = await fetchSyncPost("/api/petal/loadPetals", { frontend: getFrontend() });
let css = ""; let css = "";
// 为加快启动速度,不进行 await // 为加快启动速度,不进行 await
response.data.forEach((item: IPluginData) => { response.data.forEach((item: IPluginData) => {
@ -103,7 +103,7 @@ export const saveExport = (option: IExportOptions) => {
btnsElement[1].addEventListener("click", () => { btnsElement[1].addEventListener("click", () => {
const removeAssets = (wordDialog.element.querySelector("#removeAssets") as HTMLInputElement).checked; const removeAssets = (wordDialog.element.querySelector("#removeAssets") as HTMLInputElement).checked;
const mergeSubdocs = (wordDialog.element.querySelector("#mergeSubdocs") 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]); setStorageVal(Constants.LOCAL_EXPORTWORD, window.siyuan.storage[Constants.LOCAL_EXPORTWORD]);
getExportPath(option, removeAssets, mergeSubdocs); getExportPath(option, removeAssets, mergeSubdocs);
wordDialog.destroy(); wordDialog.destroy();
@ -476,7 +476,7 @@ ${getIconScript(servePath)}
Protyle.flowchartRender(wysElement, "${servePath}stage/protyle"); Protyle.flowchartRender(wysElement, "${servePath}stage/protyle");
Protyle.graphvizRender(wysElement, "${servePath}stage/protyle"); Protyle.graphvizRender(wysElement, "${servePath}stage/protyle");
Protyle.chartRender(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.abcRender(wysElement, "${servePath}stage/protyle");
Protyle.htmlRender(wysElement); Protyle.htmlRender(wysElement);
Protyle.plantumlRender(wysElement, "${servePath}stage/protyle"); Protyle.plantumlRender(wysElement, "${servePath}stage/protyle");
@ -637,7 +637,7 @@ ${getIconScript(servePath)}
</script> </script>
${getSnippetJS()} ${getSnippetJS()}
</body></html>`; </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); 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";`, js: `document.body.style.minWidth = "${screenWidth}px";`,
css: `@page { size: A4; margin: 10mm 0 10mm 0; background-color: var(--b3-theme-background); } css: `@page { size: A4; margin: 10mm 0 10mm 0; background-color: var(--b3-theme-background); }
.protyle-wysiwyg {padding: 0; margin: 0;}` .protyle-wysiwyg {padding: 0; margin: 0;}`
} : {js: "", css: ""}; } : { js: "", css: "" };
const html = `<!DOCTYPE html> 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}"> <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> <head>
@ -775,7 +775,7 @@ ${getIconScript(servePath)}
Protyle.flowchartRender(previewElement, "stage/protyle"); Protyle.flowchartRender(previewElement, "stage/protyle");
Protyle.graphvizRender(previewElement, "stage/protyle"); Protyle.graphvizRender(previewElement, "stage/protyle");
Protyle.chartRender(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.abcRender(previewElement, "stage/protyle");
Protyle.htmlRender(previewElement); Protyle.htmlRender(previewElement);
Protyle.plantumlRender(previewElement, "stage/protyle"); Protyle.plantumlRender(previewElement, "stage/protyle");

View file

@ -1,9 +1,9 @@
import {addScript} from "../util/addScript"; import { addScript } from "../util/addScript";
import {Constants} from "../../constants"; import { Constants } from "../../constants";
import {hasClosestByClassName} from "../util/hasClosest"; import { hasClosestByClassName } from "../util/hasClosest";
import {genIconHTML} from "./util"; 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[] = []; let mindmapElements: Element[] = [];
if (element.getAttribute("data-subtype") === "mindmap") { if (element.getAttribute("data-subtype") === "mindmap") {
// 编辑器内代码块编辑渲染 // 编辑器内代码块编辑渲染
@ -14,78 +14,158 @@ export const mindmapRender = (element: Element, cdn = Constants.PROTYLE_CDN) =>
if (mindmapElements.length === 0) { if (mindmapElements.length === 0) {
return; return;
} }
addScript(`${cdn}/js/echarts/echarts.min.js?v=0.0.0`, "protyleEchartsScript").then(() => { // load d3 first, then markmap-lib, then markmap-view (in order)
const wysiswgElement = hasClosestByClassName(element, "protyle-wysiwyg", true); addScript(`${cdn}/js/d3/d3.min.js?v6.7.0`, "protyleD3Script")
let width: number = undefined; .then(() => addScript(`${cdn}/js/markmap/markmap-lib.min.js?v0.14.4`, "protyleMarkmapLibScript"))
if (wysiswgElement && wysiswgElement.clientWidth > 0 && mindmapElements[0].firstElementChild.clientWidth === 0 && wysiswgElement.firstElementChild) { .then(() => addScript(`${cdn}/js/markmap/markmap-view.min.js?v0.14.4`, "protyleMarkmapScript"))
width = wysiswgElement.firstElementChild.clientWidth; .then(() => {
} const wysiswgElement = hasClosestByClassName(element, "protyle-wysiwyg", true);
mindmapElements.forEach((e: HTMLDivElement) => { let width: number = undefined;
if (e.getAttribute("data-render") === "true") { if (wysiswgElement && wysiswgElement.clientWidth > 0 && mindmapElements[0].firstElementChild.clientWidth === 0 && wysiswgElement.firstElementChild) {
return; width = wysiswgElement.firstElementChild.clientWidth;
} }
if (!e.firstElementChild.classList.contains("protyle-icons")) { mindmapElements.forEach((e: HTMLDivElement) => {
e.insertAdjacentHTML("afterbegin", genIconHTML(wysiswgElement)); if (e.getAttribute("data-render") === "true") {
} return;
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");
} }
window.echarts.init(renderElement.lastElementChild, window.siyuan.config.appearance.mode === 1 ? "dark" : undefined, { if (!e.firstElementChild.classList.contains("protyle-icons")) {
width, // Add home icon for mindmap (reset view), edit and more
}).setOption({ e.insertAdjacentHTML("afterbegin", genIconHTML(wysiswgElement, ["home", "edit", "more"]));
series: [ }
{ const renderElement = e.firstElementChild.nextElementSibling as HTMLElement;
data: [JSON.parse(Lute.EChartsMindmapStr(Lute.UnEscapeHTMLStr(e.getAttribute("data-content"))))], if (!e.getAttribute("data-content")) {
initialTreeDepth: -1, renderElement.innerHTML = `<span style="position: absolute;left:0;top:0;width: 1px;">${Constants.ZWSP}</span>`;
itemStyle: { return;
borderWidth: 0, }
color: "#4285f4", let transformer: any = null;
}, try {
label: { // create or reuse container for markmap
backgroundColor: "#f6f8fa", if (!renderElement.lastElementChild || renderElement.childElementCount === 1) {
borderColor: "#d1d5da", 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>`;
borderRadius: 6, } else {
borderWidth: 0.5, renderElement.lastElementChild.classList.remove("ft__error");
color: "#586069", }
lineHeight: 20,
offset: [-5, 0], // Convert stored content to markdown using Lute (prefer existing instance), then transform/render with markmap
padding: [0, 5], const raw = Lute.UnEscapeHTMLStr(e.getAttribute("data-content"));
position: "insideRight", let md: string = raw;
}, // prefer protyle's lute instance if available
lineStyle: { if ((window as any).protyle && (window as any).protyle.lute && typeof (window as any).protyle.lute.BlockDOM2Md === "function") {
color: "#d1d5da", md = (window as any).protyle.lute.BlockDOM2Md(raw);
width: 1, } else if (typeof Lute === "function" && typeof Lute.New === "function") {
}, try {
roam: true, const luteInst = Lute.New();
symbol: (value: number, params: { data?: { children?: string } }) => { if (luteInst && typeof luteInst.BlockDOM2Md === "function") {
if (params?.data?.children) { md = luteInst.BlockDOM2Md(raw);
return "circle"; } else if (luteInst && typeof luteInst.BlockDOM2HTML === "function") {
} else { md = luteInst.BlockDOM2HTML(raw);
return "path://"; }
} 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 */ }
} }
}, if (scripts && loadJS) {
type: "tree", try { loadJS(scripts, { getMarkmap: () => (window as any).markmap || mm }); } catch (err) { /* ignore */ }
}, }
], }
tooltip: { } else {
trigger: "item", // fallback: try older transform functions (may return root-like object)
triggerOn: "mousemove", const transformFn = mm.transform || (window as any).markmap && (window as any).markmap.transform;
}, if (typeof transformFn === "function") {
backgroundColor: "transparent", try {
}); const tx = transformFn(md) || {};
} catch (error) { rootData = tx.root || tx || null;
window.echarts.dispose(renderElement.lastElementChild); } catch (err) {
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>`; rootData = null;
} }
e.setAttribute("data-render", "true"); }
}
}
// 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 { isInEmbedBlock } from "../util/hasClosest";
import {Constants} from "../../constants"; import { Constants } from "../../constants";
export const genIconHTML = (element?: false | HTMLElement, actions = ["edit", "more"]) => { export const genIconHTML = (element?: false | HTMLElement, actions = ["edit", "more"]) => {
let enable = true; let enable = true;
@ -11,18 +11,51 @@ export const genIconHTML = (element?: false | HTMLElement, actions = ["edit", "m
return '<div class="protyle-icons"></div>'; return '<div class="protyle-icons"></div>';
} }
} }
if (actions.length === 3) { const mapActionToHTML = (action: string, isFirst: boolean, isLast: boolean) => {
return `<div class="protyle-icons"> const classList = ["ariaLabel", "protyle-icon"];
<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> if (isFirst) classList.push("protyle-icon--first");
<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> if (isLast) classList.push("protyle-icon--last");
<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> let aria = "";
</div>`; let className = "";
} else { let icon = "";
return `<div class="protyle-icons"> switch (action) {
<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> case "reload":
<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> case "refresh":
</div>`; 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) => { export const genRenderFrame = (renderElement: Element) => {

View file

@ -1,6 +1,6 @@
import {Divider} from "./Divider"; import { Divider } from "./Divider";
import {Font, hasSameTextStyle, setFontStyle} from "./Font"; import { Font, hasSameTextStyle, setFontStyle } from "./Font";
import {ToolbarItem} from "./ToolbarItem"; import { ToolbarItem } from "./ToolbarItem";
import { import {
fixTableRange, fixTableRange,
focusBlock, focusBlock,
@ -12,40 +12,40 @@ import {
setFirstNodeRange, setFirstNodeRange,
setLastNodeRange setLastNodeRange
} from "../util/selection"; } from "../util/selection";
import {hasClosestBlock, hasClosestByAttribute, hasClosestByClassName} from "../util/hasClosest"; import { hasClosestBlock, hasClosestByAttribute, hasClosestByClassName } from "../util/hasClosest";
import {Link} from "./Link"; import { Link } from "./Link";
import {setPosition} from "../../util/setPosition"; import { setPosition } from "../../util/setPosition";
import {transaction, updateTransaction} from "../wysiwyg/transaction"; import { transaction, updateTransaction } from "../wysiwyg/transaction";
import {Constants} from "../../constants"; import { Constants } from "../../constants";
import {copyPlainText, openByMobile, readClipboard, setStorageVal} from "../util/compatibility"; import { copyPlainText, openByMobile, readClipboard, setStorageVal } from "../util/compatibility";
import {upDownHint} from "../../util/upDownHint"; import { upDownHint } from "../../util/upDownHint";
import {highlightRender} from "../render/highlightRender"; import { highlightRender } from "../render/highlightRender";
import {getContenteditableElement, hasNextSibling, hasPreviousSibling} from "../wysiwyg/getBlock"; import { getContenteditableElement, hasNextSibling, hasPreviousSibling } from "../wysiwyg/getBlock";
import {processRender} from "../util/processCode"; import { processRender } from "../util/processCode";
import {BlockRef} from "./BlockRef"; import { BlockRef } from "./BlockRef";
import {hintRenderTemplate, hintRenderWidget} from "../hint/extend"; import { hintRenderTemplate, hintRenderWidget } from "../hint/extend";
import {blockRender} from "../render/blockRender"; import { blockRender } from "../render/blockRender";
/// #if !BROWSER /// #if !BROWSER
import {openBy} from "../../editor/util"; import { openBy } from "../../editor/util";
/// #endif /// #endif
import {fetchPost} from "../../util/fetch"; import { fetchPost } from "../../util/fetch";
import {isArrayEqual, isMobile} from "../../util/functions"; import { isArrayEqual, isMobile } from "../../util/functions";
import * as dayjs from "dayjs"; import * as dayjs from "dayjs";
import {insertEmptyBlock} from "../../block/util"; import { insertEmptyBlock } from "../../block/util";
import {matchHotKey} from "../util/hotKey"; import { matchHotKey } from "../util/hotKey";
import {hideElements} from "../ui/hideElements"; import { hideElements } from "../ui/hideElements";
import {electronUndo} from "../undo"; import { electronUndo } from "../undo";
import {previewTemplate, toolbarKeyToMenu} from "./util"; import { previewTemplate, toolbarKeyToMenu } from "./util";
import {hideMessage, showMessage} from "../../dialog/message"; import { hideMessage, showMessage } from "../../dialog/message";
import {InlineMath} from "./InlineMath"; import { InlineMath } from "./InlineMath";
import {InlineMemo} from "./InlineMemo"; import { InlineMemo } from "./InlineMemo";
import {mathRender} from "../render/mathRender"; import { mathRender } from "../render/mathRender";
import {linkMenu} from "../../menus/protyle"; import { linkMenu } from "../../menus/protyle";
import {addScript} from "../util/addScript"; import { addScript } from "../util/addScript";
import {confirmDialog} from "../../dialog/confirmDialog"; import { confirmDialog } from "../../dialog/confirmDialog";
import {paste, pasteAsPlainText, pasteEscaped} from "../util/paste"; import { paste, pasteAsPlainText, pasteEscaped } from "../util/paste";
import {escapeHtml} from "../../util/escape"; import { escapeHtml } from "../../util/escape";
import {resizeSide} from "../../history/resizeSide"; import { resizeSide } from "../../history/resizeSide";
export class Toolbar { export class Toolbar {
public element: HTMLElement; public element: HTMLElement;
@ -309,12 +309,12 @@ export class Toolbar {
const startPreviousSibling = hasPreviousSibling(this.range.startContainer); const startPreviousSibling = hasPreviousSibling(this.range.startContainer);
const endNextSibling = hasNextSibling(this.range.endContainer); const endNextSibling = hasNextSibling(this.range.endContainer);
if (( if ((
this.range.startOffset !== 0 || this.range.startOffset !== 0 ||
// https://github.com/siyuan-note/siyuan/issues/14869 // https://github.com/siyuan-note/siyuan/issues/14869
(this.range.startOffset === 0 && startPreviousSibling && (this.range.startOffset === 0 && startPreviousSibling &&
(startPreviousSibling.nodeType === 3 || (startPreviousSibling as HTMLElement).tagName === "BR") && (startPreviousSibling.nodeType === 3 || (startPreviousSibling as HTMLElement).tagName === "BR") &&
this.range.startContainer.previousSibling.parentElement === this.range.startContainer.parentElement) this.range.startContainer.previousSibling.parentElement === this.range.startContainer.parentElement)
) && ( ) && (
this.range.endOffset !== this.range.endContainer.textContent.length || this.range.endOffset !== this.range.endContainer.textContent.length ||
// https://github.com/siyuan-note/siyuan/issues/14869#issuecomment-2911553387 // https://github.com/siyuan-note/siyuan/issues/14869#issuecomment-2911553387
( (
@ -987,6 +987,17 @@ export class Toolbar {
break; break;
case "refresh": case "refresh":
btnElement.classList.toggle("block__icon--active"); 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; break;
case "before": case "before":
insertEmptyBlock(protyle, "beforebegin", id); insertEmptyBlock(protyle, "beforebegin", id);
@ -1017,6 +1028,55 @@ export class Toolbar {
}); });
return; 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(() => { setTimeout(() => {
addScript("/stage/protyle/js/html-to-image.min.js?v=1.11.13", "protyleHtml2image").then(() => { addScript("/stage/protyle/js/html-to-image.min.js?v=1.11.13", "protyleHtml2image").then(() => {
(renderElement as HTMLHtmlElement).style.display = "inline-block"; (renderElement as HTMLHtmlElement).style.display = "inline-block";
@ -1067,11 +1127,32 @@ export class Toolbar {
} }
}); });
} else { } else {
renderElement.setAttribute("data-content", Lute.EscapeHTMLStr(textElement.value)); // mindmap: try incremental update via stored markmap instance
renderElement.removeAttribute("data-render"); 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) { // If mindmap was updated via markmap.setData above, no need to run full processRender.
processRender(renderElement); 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(); event.stopPropagation();
}); });
@ -1158,11 +1239,30 @@ export class Toolbar {
} else { } else {
renderElement.setAttribute("data-content", Lute.EscapeHTMLStr(textElement.value)); renderElement.setAttribute("data-content", Lute.EscapeHTMLStr(textElement.value));
renderElement.removeAttribute("data-render"); renderElement.removeAttribute("data-render");
if (types.includes("NodeBlockQueryEmbed")) { // If this is a mindmap and we have a stored markmap instance, prefer incremental update
blockRender(protyle, renderElement); const isMindmap = renderElement.getAttribute("data-subtype") === "mindmap";
(renderElement as HTMLElement).style.height = ""; 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 { } 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 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(); 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) { if (protyle.app && protyle.app.plugins) {
protyle.app.plugins.forEach((plugin: any) => { protyle.app.plugins.forEach((plugin: any) => {
plugin.eventBus.emit("code-language-update", eventDetail); 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) { if (protyle.app && protyle.app.plugins) {
protyle.app.plugins.forEach((plugin: any) => { protyle.app.plugins.forEach((plugin: any) => {
plugin.eventBus.emit("code-language-update", eventDetail); plugin.eventBus.emit("code-language-update", eventDetail);
@ -1489,7 +1589,7 @@ export class Toolbar {
/// #endif /// #endif
if (iconElement && iconElement.getAttribute("data-type") === "remove") { if (iconElement && iconElement.getAttribute("data-type") === "remove") {
confirmDialog(window.siyuan.languages.remove, window.siyuan.languages.confirmDelete + "?", () => { 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) { if (iconElement.parentElement.parentElement.childElementCount === 1) {
iconElement.parentElement.parentElement.innerHTML = `<li class="b3-list--empty">${window.siyuan.languages.emptyContent}</li>`; iconElement.parentElement.parentElement.innerHTML = `<li class="b3-list--empty">${window.siyuan.languages.emptyContent}</li>`;
previewTemplate("", previewElement, protyle.block.parentID); previewTemplate("", previewElement, protyle.block.parentID);
@ -1513,13 +1613,13 @@ export class Toolbar {
} }
const previousElement = hasClosestByAttribute(target, "data-type", "previous"); const previousElement = hasClosestByAttribute(target, "data-type", "previous");
if (previousElement) { if (previousElement) {
inputElement.dispatchEvent(new KeyboardEvent("keydown", {key: "ArrowUp"})); inputElement.dispatchEvent(new KeyboardEvent("keydown", { key: "ArrowUp" }));
event.stopPropagation(); event.stopPropagation();
return; return;
} }
const nextElement = hasClosestByAttribute(target, "data-type", "next"); const nextElement = hasClosestByAttribute(target, "data-type", "next");
if (nextElement) { if (nextElement) {
inputElement.dispatchEvent(new KeyboardEvent("keydown", {key: "ArrowDown"})); inputElement.dispatchEvent(new KeyboardEvent("keydown", { key: "ArrowDown" }));
event.stopPropagation(); event.stopPropagation();
return; return;
} }
@ -1691,7 +1791,7 @@ ${item.name}
} else { } else {
try { try {
const text = await readClipboard(); const text = await readClipboard();
paste(protyle, Object.assign(text, {target: nodeElement as HTMLElement})); paste(protyle, Object.assign(text, { target: nodeElement as HTMLElement }));
} catch (e) { } catch (e) {
console.log(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 { import {
hasClosestBlock, hasClosestBlock,
hasClosestByAttribute, hasClosestByAttribute,
@ -18,9 +18,9 @@ import {
setInsertWbrHTML, setInsertWbrHTML,
setLastNodeRange, setLastNodeRange,
} from "../util/selection"; } from "../util/selection";
import {Constants} from "../../constants"; import { Constants } from "../../constants";
import {isMobile} from "../../util/functions"; import { isMobile } from "../../util/functions";
import {previewDocImage} from "../preview/image"; import { previewDocImage } from "../preview/image";
import { import {
contentMenu, contentMenu,
enterBack, enterBack,
@ -34,8 +34,8 @@ import {
zoomOut zoomOut
} from "../../menus/protyle"; } from "../../menus/protyle";
import * as dayjs from "dayjs"; import * as dayjs from "dayjs";
import {dropEvent} from "../util/editorCommonEvent"; import { dropEvent } from "../util/editorCommonEvent";
import {input} from "./input"; import { input } from "./input";
import { import {
getContenteditableElement, getContenteditableElement,
getNextBlock, getNextBlock,
@ -45,43 +45,43 @@ import {
isEndOfBlock, isEndOfBlock,
isNotEditBlock isNotEditBlock
} from "./getBlock"; } from "./getBlock";
import {transaction, updateTransaction} from "./transaction"; import { transaction, updateTransaction } from "./transaction";
import {hideElements} from "../ui/hideElements"; import { hideElements } from "../ui/hideElements";
/// #if !BROWSER /// #if !BROWSER
import {ipcRenderer} from "electron"; import { ipcRenderer } from "electron";
/// #endif /// #endif
import {getEnableHTML, removeEmbed} from "./removeEmbed"; import { getEnableHTML, removeEmbed } from "./removeEmbed";
import {keydown} from "./keydown"; import { keydown } from "./keydown";
import {openMobileFileById} from "../../mobile/editor"; import { openMobileFileById } from "../../mobile/editor";
import {removeBlock} from "./remove"; import { removeBlock } from "./remove";
import {highlightRender} from "../render/highlightRender"; import { highlightRender } from "../render/highlightRender";
import {openAttr} from "../../menus/commonMenuItem"; import { openAttr } from "../../menus/commonMenuItem";
import {blockRender} from "../render/blockRender"; import { blockRender } from "../render/blockRender";
/// #if !MOBILE /// #if !MOBILE
import {getAllModels} from "../../layout/getAll"; import { getAllModels } from "../../layout/getAll";
import {pushBack} from "../../util/backForward"; import { pushBack } from "../../util/backForward";
import {openFileById} from "../../editor/util"; import { openFileById } from "../../editor/util";
import {openGlobalSearch} from "../../search/util"; import { openGlobalSearch } from "../../search/util";
/// #else /// #else
import {popSearch} from "../../mobile/menu/search"; import { popSearch } from "../../mobile/menu/search";
/// #endif /// #endif
import {BlockPanel} from "../../block/Panel"; import { BlockPanel } from "../../block/Panel";
import {copyPlainText, isInIOS, isMac, isOnlyMeta, readClipboard, encodeBase64} from "../util/compatibility"; import { copyPlainText, isInIOS, isMac, isOnlyMeta, readClipboard, encodeBase64 } from "../util/compatibility";
import {MenuItem} from "../../menus/Menu"; import { MenuItem } from "../../menus/Menu";
import {fetchPost, fetchSyncPost} from "../../util/fetch"; import { fetchPost, fetchSyncPost } from "../../util/fetch";
import {onGet} from "../util/onGet"; import { onGet } from "../util/onGet";
import {clearTableCell, isIncludeCell, setTableAlign} from "../util/table"; import { clearTableCell, isIncludeCell, setTableAlign } from "../util/table";
import {countBlockWord, countSelectWord} from "../../layout/status"; import { countBlockWord, countSelectWord } from "../../layout/status";
import {showMessage} from "../../dialog/message"; import { showMessage } from "../../dialog/message";
import {getBacklinkHeadingMore, loadBreadcrumb} from "./renderBacklink"; import { getBacklinkHeadingMore, loadBreadcrumb } from "./renderBacklink";
import {removeSearchMark} from "../toolbar/util"; import { removeSearchMark } from "../toolbar/util";
import {activeBlur} from "../../mobile/util/keyboardToolbar"; import { activeBlur } from "../../mobile/util/keyboardToolbar";
import {commonClick} from "./commonClick"; import { commonClick } from "./commonClick";
import {avClick, avContextmenu, updateAVName} from "../render/av/action"; import { avClick, avContextmenu, updateAVName } from "../render/av/action";
import {selectRow, stickyRow} from "../render/av/row"; import { selectRow, stickyRow } from "../render/av/row";
import {showColMenu} from "../render/av/col"; import { showColMenu } from "../render/av/col";
import {openViewMenu} from "../render/av/view"; import { openViewMenu } from "../render/av/view";
import {checkFold} from "../../util/noRelyPCFunction"; import { checkFold } from "../../util/noRelyPCFunction";
import { import {
addDragFill, addDragFill,
dragFillCellsValue, dragFillCellsValue,
@ -91,6 +91,16 @@ import {
getTypeByCellElement, getTypeByCellElement,
updateCellsValue updateCellsValue
} from "../render/av/cell"; } 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 {openEmojiPanel, unicode2Emoji} from "../../emoji";
import {openLink} from "../../editor/openLink"; import {openLink} from "../../editor/openLink";
import {mathRender} from "../render/mathRender"; import {mathRender} from "../render/mathRender";
@ -1554,11 +1564,11 @@ export class WYSIWYG {
const scrollTop = tableBlockElement.querySelector("table").scrollTop; const scrollTop = tableBlockElement.querySelector("table").scrollTop;
tableBlockElement.querySelectorAll("th, td").forEach((item: HTMLTableCellElement) => { tableBlockElement.querySelectorAll("th, td").forEach((item: HTMLTableCellElement) => {
if (!item.classList.contains("fn__none") && isIncludeCell({ if (!item.classList.contains("fn__none") && isIncludeCell({
tableSelectElement, tableSelectElement,
scrollLeft, scrollLeft,
scrollTop, scrollTop,
item, item,
}) && }) &&
(selectCellElements.length === 0 || (selectCellElements.length > 0 && item.offsetTop === selectCellElements[0].offsetTop))) { (selectCellElements.length === 0 || (selectCellElements.length > 0 && item.offsetTop === selectCellElements[0].offsetTop))) {
selectCellElements.push(item); selectCellElements.push(item);
} }
@ -1604,11 +1614,11 @@ export class WYSIWYG {
const scrollTop = tableBlockElement.querySelector("table").scrollTop; const scrollTop = tableBlockElement.querySelector("table").scrollTop;
tableBlockElement.querySelectorAll("th, td").forEach((item: HTMLTableCellElement) => { tableBlockElement.querySelectorAll("th, td").forEach((item: HTMLTableCellElement) => {
if (!item.classList.contains("fn__none") && isIncludeCell({ if (!item.classList.contains("fn__none") && isIncludeCell({
tableSelectElement, tableSelectElement,
scrollLeft, scrollLeft,
scrollTop, scrollTop,
item, item,
}) && }) &&
(selectCellElements.length === 0 || (selectCellElements.length > 0 && item.offsetTop === selectCellElements[0].offsetTop))) { (selectCellElements.length === 0 || (selectCellElements.length > 0 && item.offsetTop === selectCellElements[0].offsetTop))) {
selectCellElements.push(item); selectCellElements.push(item);
} }
@ -1700,7 +1710,7 @@ export class WYSIWYG {
} else if (tableBlockElement) { } else if (tableBlockElement) {
try { try {
const text = await readClipboard(); const text = await readClipboard();
paste(protyle, Object.assign(text, {target: tableBlockElement as HTMLElement})); paste(protyle, Object.assign(text, { target: tableBlockElement as HTMLElement }));
} catch (e) { } catch (e) {
console.log(e); console.log(e);
} }
@ -1708,7 +1718,7 @@ export class WYSIWYG {
} }
}).element); }).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); hideElements(["util"], protyle);
protyle.gutter.renderMenu(protyle, selectElements[0]); protyle.gutter.renderMenu(protyle, selectElements[0]);
window.siyuan.menus.menu.popup({x, y}); window.siyuan.menus.menu.popup({ x, y });
return; return;
} }
const target = event.detail.target || event.target as HTMLElement; const target = event.detail.target || event.target as HTMLElement;
@ -2117,7 +2127,7 @@ export class WYSIWYG {
/// #if MOBILE /// #if MOBILE
window.siyuan.menus.menu.fullscreen(); window.siyuan.menus.menu.fullscreen();
/// #else /// #else
window.siyuan.menus.menu.popup({x, y}); window.siyuan.menus.menu.popup({ x, y });
/// #endif /// #endif
return false; return false;
} }
@ -2191,7 +2201,7 @@ export class WYSIWYG {
const avTabHeaderElement = hasClosestByClassName(target, "item"); const avTabHeaderElement = hasClosestByClassName(target, "item");
if (nodeElement.classList.contains("av") && avTabHeaderElement) { if (nodeElement.classList.contains("av") && avTabHeaderElement) {
if (avTabHeaderElement.classList.contains("item--focus")) { if (avTabHeaderElement.classList.contains("item--focus")) {
openViewMenu({protyle, blockElement: nodeElement, element: avTabHeaderElement}); openViewMenu({ protyle, blockElement: nodeElement, element: avTabHeaderElement });
} else { } else {
transaction(protyle, [{ transaction(protyle, [{
action: "setAttrViewBlockView", action: "setAttrViewBlockView",
@ -2275,7 +2285,7 @@ export class WYSIWYG {
) { ) {
if ((!isMobile() || protyle.toolbar?.element.classList.contains("fn__none")) && !nodeElement.classList.contains("av")) { if ((!isMobile() || protyle.toolbar?.element.classList.contains("fn__none")) && !nodeElement.classList.contains("av")) {
contentMenu(protyle, nodeElement); 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"); protyle.toolbar?.element.classList.add("fn__none");
if (nodeElement.classList.contains("table")) { if (nodeElement.classList.contains("table")) {
nodeElement.querySelector(".table__select").removeAttribute("style"); nodeElement.querySelector(".table__select").removeAttribute("style");
@ -2289,7 +2299,7 @@ export class WYSIWYG {
/// #if MOBILE /// #if MOBILE
window.siyuan.menus.menu.fullscreen(); window.siyuan.menus.menu.fullscreen();
/// #else /// #else
window.siyuan.menus.menu.popup({x, y}); window.siyuan.menus.menu.popup({ x, y });
/// #endif /// #endif
protyle.toolbar?.element.classList.add("fn__none"); protyle.toolbar?.element.classList.add("fn__none");
} }
@ -2360,7 +2370,7 @@ export class WYSIWYG {
window.siyuan.menus.menu.remove(); window.siyuan.menus.menu.remove();
} }
} }
}, {passive: true}); }, { passive: true });
this.element.addEventListener("paste", (event: ClipboardEvent & { target: HTMLElement }) => { this.element.addEventListener("paste", (event: ClipboardEvent & { target: HTMLElement }) => {
// https://github.com/siyuan-note/siyuan/issues/11241 // https://github.com/siyuan-note/siyuan/issues/11241
@ -2696,7 +2706,7 @@ export class WYSIWYG {
zoomIn, zoomIn,
scrollPosition: "start" scrollPosition: "start"
}); });
window.dispatchEvent(new KeyboardEvent("keydown", {key: "Escape"})); window.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape" }));
} else if (event.altKey) { } else if (event.altKey) {
openFileById({ openFileById({
app: protyle.app, app: protyle.app,
@ -2809,7 +2819,7 @@ export class WYSIWYG {
const tagElement = hasClosestByAttribute(event.target, "data-type", "tag"); const tagElement = hasClosestByAttribute(event.target, "data-type", "tag");
if (tagElement && !event.altKey && !event.shiftKey && range.toString() === "") { if (tagElement && !event.altKey && !event.shiftKey && range.toString() === "") {
/// #if !MOBILE /// #if !MOBILE
openGlobalSearch(protyle.app, `#${tagElement.textContent}#`, !ctrlIsPressed, {method: 0}); openGlobalSearch(protyle.app, `#${tagElement.textContent}#`, !ctrlIsPressed, { method: 0 });
hideElements(["dialog"]); hideElements(["dialog"]);
/// #else /// #else
popSearch(protyle.app, { popSearch(protyle.app, {
@ -2863,7 +2873,7 @@ export class WYSIWYG {
app: protyle.app, app: protyle.app,
targetElement: embedItemElement, targetElement: embedItemElement,
isBacklink: false, isBacklink: false,
refDefs: [{refID: embedId}] refDefs: [{ refID: embedId }]
})); }));
} }
/// #endif /// #endif
@ -2927,6 +2937,24 @@ export class WYSIWYG {
return; 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"); const languageElement = hasClosestByClassName(event.target, "protyle-action__language");
if (languageElement && !protyle.disabled && !ctrlIsPressed) { if (languageElement && !protyle.disabled && !ctrlIsPressed) {
protyle.toolbar.showCodeLanguage(protyle, [languageElement]); protyle.toolbar.showCodeLanguage(protyle, [languageElement]);
@ -2986,7 +3014,7 @@ export class WYSIWYG {
} else if (event.shiftKey && !protyle.disabled) { } else if (event.shiftKey && !protyle.disabled) {
openAttr(actionElement.parentElement, "bookmark", protyle); openAttr(actionElement.parentElement, "bookmark", protyle);
} else if (ctrlIsPressed) { } else if (ctrlIsPressed) {
zoomOut({protyle, id: actionId}); zoomOut({ protyle, id: actionId });
} else { } else {
if (actionElement.classList.contains("protyle-action--task")) { if (actionElement.classList.contains("protyle-action--task")) {
if (!protyle.disabled) { if (!protyle.disabled) {
@ -3005,7 +3033,7 @@ export class WYSIWYG {
if (protyle.block.showAll && protyle.block.id === actionId) { if (protyle.block.showAll && protyle.block.id === actionId) {
enterBack(protyle, actionId); enterBack(protyle, actionId);
} else { } 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