import { hasClosestBlock, hasClosestByClassName, hasClosestByMatchTag, hasClosestByTag, hasTopClosestByClassName, isInEmbedBlock } from "../util/hasClosest"; import {getIconByType} from "../../editor/getIcon"; import {enterBack, iframeMenu, setFold, tableMenu, videoMenu, zoomOut} from "../../menus/protyle"; import {MenuItem} from "../../menus/Menu"; import {copySubMenu, openAttr, openFileAttr, openWechatNotify} from "../../menus/commonMenuItem"; import { copyPlainText, isInAndroid, isInHarmony, isMac, isOnlyMeta, openByMobile, updateHotkeyTip, writeText } from "../util/compatibility"; import { transaction, turnsIntoOneTransaction, turnsIntoTransaction, turnsOneInto, updateBatchTransaction, updateTransaction } from "../wysiwyg/transaction"; import {removeBlock} from "../wysiwyg/remove"; import {focusBlock, focusByRange, getEditorRange} from "../util/selection"; import {hideElements} from "../ui/hideElements"; import {highlightRender} from "../render/highlightRender"; import {blockRender} from "../render/blockRender"; import {getContenteditableElement, getTopAloneElement, isNotEditBlock} from "../wysiwyg/getBlock"; import * as dayjs from "dayjs"; import {fetchPost} from "../../util/fetch"; import {cancelSB, genEmptyElement, getLangByType, insertEmptyBlock, jumpToParent,} from "../../block/util"; import {countBlockWord} from "../../layout/status"; import {Constants} from "../../constants"; import {mathRender} from "../render/mathRender"; import {duplicateBlock} from "../wysiwyg/commonHotkey"; import {movePathTo} from "../../util/pathName"; import {hintMoveBlock} from "../hint/extend"; import {makeCard, quickMakeCard} from "../../card/makeCard"; import {transferBlockRef} from "../../menus/block"; import {isMobile} from "../../util/functions"; import {AIActions} from "../../ai/actions"; import {activeBlur, renderTextMenu, showKeyboardToolbarUtil} from "../../mobile/util/keyboardToolbar"; import {hideTooltip} from "../../dialog/tooltip"; import {appearanceMenu} from "../toolbar/Font"; import {setPosition} from "../../util/setPosition"; import {emitOpenMenu} from "../../plugin/EventBus"; import {insertAttrViewBlockAnimation, updateHeader} from "../render/av/row"; import {avContextmenu, duplicateCompletely} from "../render/av/action"; import {getPlainText} from "../util/paste"; import {addEditorToDatabase} from "../render/av/addToDatabase"; import {processClonePHElement} from "../render/util"; /// #if !MOBILE import {openFileById} from "../../editor/util"; /// #endif import {checkFold} from "../../util/noRelyPCFunction"; import {copyTextByType} from "../toolbar/util"; export class Gutter { public element: HTMLElement; private gutterTip: string; constructor(protyle: IProtyle) { if (isMac()) { this.gutterTip = window.siyuan.languages.gutterTip.replace("⌥→", updateHotkeyTip(window.siyuan.config.keymap.general.enter.custom)); } else { this.gutterTip = window.siyuan.languages.gutterTip.replace("⌥→", updateHotkeyTip(window.siyuan.config.keymap.general.enter.custom)) .replace("⌘↑", updateHotkeyTip(window.siyuan.config.keymap.editor.general.collapse.custom)) .replace("⌥⌘A", updateHotkeyTip(window.siyuan.config.keymap.editor.general.attr.custom)) .replace(/⌘/g, "Ctrl+").replace(/⌥/g, "Alt+").replace(/⇧/g, "Shift+").replace(/⌃/g, "Ctrl+"); } if (protyle.options.backlinkData) { this.gutterTip = this.gutterTip.replace(window.siyuan.languages.enter, window.siyuan.languages.openBy); } this.element = document.createElement("div"); this.element.className = "protyle-gutters"; this.element.addEventListener("dragstart", (event: DragEvent & { target: HTMLElement }) => { hideTooltip(); window.siyuan.menus.menu.remove(); const buttonElement = event.target.parentElement; let selectIds: string[] = []; let selectElements: Element[] = []; let avElement: Element; if (buttonElement.dataset.rowId) { avElement = Array.from(protyle.wysiwyg.element.querySelectorAll(`.av[data-node-id="${buttonElement.dataset.nodeId}"]`)).find((item: HTMLElement) => { if (!isInEmbedBlock(item)) { return true; } }); const rowElement = avElement.querySelector(`.av__row[data-id="${buttonElement.dataset.rowId}"]`); rowElement.classList.add("av__row--select"); rowElement.querySelector(".av__firstcol use").setAttribute("xlink:href", "#iconCheck"); updateHeader(rowElement as HTMLElement); avElement.querySelectorAll(".av__row--select:not(.av__row--header)").forEach(item => { selectIds.push(item.getAttribute("data-id")); selectElements.push(item); }); } else { const gutterId = buttonElement.getAttribute("data-node-id"); selectElements = Array.from(protyle.wysiwyg.element.querySelectorAll(".protyle-wysiwyg--select")); let selectedIncludeGutter = false; selectElements.forEach((item => { const itemId = item.getAttribute("data-node-id"); if (itemId === gutterId) { selectedIncludeGutter = true; } selectIds.push(itemId); })); if (!selectedIncludeGutter) { const gutterNodeElement = protyle.wysiwyg.element.querySelector(`[data-node-id="${gutterId}"]`); if (gutterNodeElement) { selectElements.forEach((item => { item.classList.remove("protyle-wysiwyg--select"); })); gutterNodeElement.classList.add("protyle-wysiwyg--select"); selectElements = [gutterNodeElement]; selectIds = [gutterId]; } } } const ghostElement = document.createElement("div"); ghostElement.className = protyle.wysiwyg.element.className; selectElements.forEach(item => { const type = item.getAttribute("data-type"); if (item.querySelector("iframe")) { const embedElement = genEmptyElement(); embedElement.classList.add("protyle-wysiwyg--select"); getContenteditableElement(embedElement).innerHTML = ` ${getLangByType(type)}`; ghostElement.append(embedElement); } else { ghostElement.append(processClonePHElement(item.cloneNode(true) as Element)); } }); ghostElement.setAttribute("style", `position:fixed;opacity:.1;width:${selectElements[0].clientWidth}px;padding:0;`); document.body.append(ghostElement); event.dataTransfer.setDragImage(ghostElement, 0, 0); setTimeout(() => { ghostElement.remove(); }); buttonElement.style.opacity = "0.1"; window.siyuan.dragElement = avElement as HTMLElement || protyle.wysiwyg.element; event.dataTransfer.setData(`${Constants.SIYUAN_DROP_GUTTER}${buttonElement.getAttribute("data-type")}${Constants.ZWSP}${buttonElement.getAttribute("data-subtype")}${Constants.ZWSP}${selectIds}`, protyle.wysiwyg.element.innerHTML); }); this.element.addEventListener("dragend", () => { this.element.querySelectorAll("button").forEach((item) => { item.style.opacity = ""; }); window.siyuan.dragElement = undefined; }); this.element.addEventListener("click", (event: MouseEvent & { target: HTMLInputElement }) => { const buttonElement = hasClosestByTag(event.target, "BUTTON"); if (!buttonElement) { return; } event.preventDefault(); event.stopPropagation(); hideTooltip(); const id = buttonElement.getAttribute("data-node-id"); if (!id) { if (buttonElement.getAttribute("disabled")) { return; } buttonElement.setAttribute("disabled", "disabled"); let foldElement: Element; Array.from(protyle.wysiwyg.element.querySelectorAll(`[data-node-id="${(buttonElement.previousElementSibling || buttonElement.nextElementSibling).getAttribute("data-node-id")}"]`)).find(item => { if (!isInEmbedBlock(item) && this.isMatchNode(item)) { foldElement = item; return true; } }); if (!foldElement) { return; } if (event.altKey) { // 折叠所有子集 let hasFold = true; Array.from(foldElement.children).find((ulElement) => { if (ulElement.classList.contains("list")) { const foldElement = Array.from(ulElement.children).find((listItemElement) => { if (listItemElement.classList.contains("li")) { if (listItemElement.getAttribute("fold") !== "1" && listItemElement.childElementCount > 3) { hasFold = false; return true; } } }); if (foldElement) { return true; } } }); const doOperations: IOperation[] = []; const undoOperations: IOperation[] = []; Array.from(foldElement.children).forEach((ulElement) => { if (ulElement.classList.contains("list")) { Array.from(ulElement.children).forEach((listItemElement) => { if (listItemElement.classList.contains("li")) { if (hasFold) { listItemElement.removeAttribute("fold"); } else if (listItemElement.childElementCount > 3) { listItemElement.setAttribute("fold", "1"); } const listId = listItemElement.getAttribute("data-node-id"); doOperations.push({ action: "setAttrs", id: listId, data: JSON.stringify({fold: hasFold ? "" : "1"}) }); undoOperations.push({ action: "setAttrs", id: listId, data: JSON.stringify({fold: hasFold ? "1" : ""}) }); } }); } }); transaction(protyle, doOperations, undoOperations); buttonElement.removeAttribute("disabled"); } else { const foldStatus = setFold(protyle, foldElement); if (foldStatus === 1) { (buttonElement.firstElementChild as HTMLElement).style.transform = ""; } else if (foldStatus === 0) { (buttonElement.firstElementChild as HTMLElement).style.transform = "rotate(90deg)"; } } hideElements(["select"], protyle); window.siyuan.menus.menu.remove(); return; } const gutterRect = buttonElement.getBoundingClientRect(); if (buttonElement.dataset.type === "NodeAttributeViewRowMenu" || buttonElement.dataset.type === "NodeAttributeViewRow") { const rowElement = Array.from(protyle.wysiwyg.element.querySelectorAll(`.av[data-node-id="${buttonElement.dataset.nodeId}"] .av__row[data-id="${buttonElement.dataset.rowId}"]`)).find((item: HTMLElement) => { if (!isInEmbedBlock(item)) { return true; } }); if (!rowElement) { return; } const blockElement = hasClosestBlock(rowElement); if (!blockElement) { return; } if (buttonElement.dataset.type === "NodeAttributeViewRow") { blockElement.querySelectorAll(".av__cell--select, .av__cell--active").forEach((cellElement: HTMLElement) => { cellElement.classList.remove("av__cell--select", "av__cell--active"); cellElement.querySelector(".av__drag-fill")?.remove(); }); const avID = blockElement.getAttribute("data-av-id"); const srcIDs = [Lute.NewNodeID()]; const previousID = event.altKey ? (rowElement.previousElementSibling.getAttribute("data-id") || "") : buttonElement.dataset.rowId; const newUpdated = dayjs().format("YYYYMMDDHHmmss"); transaction(protyle, [{ action: "insertAttrViewBlock", avID, previousID, srcs: [{ id: srcIDs[0], isDetached: true, content: "" }], blockID: id, }, { action: "doUpdateUpdated", id, data: newUpdated, }], [{ action: "removeAttrViewBlock", srcIDs, avID, }, { action: "doUpdateUpdated", id, data: blockElement.getAttribute("updated") }]); insertAttrViewBlockAnimation(protyle, blockElement, srcIDs, previousID, avID); if (event.altKey) { this.element.querySelectorAll("button").forEach(item => { item.dataset.rowId = srcIDs[0]; }); } blockElement.setAttribute("updated", newUpdated); } else { if (!protyle.disabled && event.shiftKey) { const blockId = rowElement.querySelector('[data-dtype="block"] .av__celltext--ref')?.getAttribute("data-id"); if (blockId) { fetchPost("/api/attr/getBlockAttrs", {id: blockId}, (response) => { openFileAttr(response.data, "av", protyle); }); return; } } avContextmenu(protyle, rowElement as HTMLElement, { x: gutterRect.left, y: gutterRect.bottom, w: gutterRect.width, h: gutterRect.height, isLeft: true }); } return; } if (isOnlyMeta(event)) { if (protyle.options.backlinkData) { checkFold(id, (zoomIn, action) => { openFileById({ app: protyle.app, id, action, zoomIn }); }); } else { zoomOut({protyle, id}); } } else if (event.altKey) { let foldElement: Element; Array.from(protyle.wysiwyg.element.querySelectorAll(`[data-node-id="${id}"]`)).find(item => { if (!isInEmbedBlock(item) && this.isMatchNode(item)) { foldElement = item; return true; } }); if (!foldElement) { return; } if (buttonElement.getAttribute("data-type") === "NodeListItem" && foldElement.parentElement.getAttribute("data-node-id")) { // 折叠同级 let hasFold = true; Array.from(foldElement.parentElement.children).find((listItemElement) => { if (listItemElement.classList.contains("li")) { if (listItemElement.getAttribute("fold") !== "1" && listItemElement.childElementCount > 3) { hasFold = false; return true; } } }); const doOperations: IOperation[] = []; const undoOperations: IOperation[] = []; Array.from(foldElement.parentElement.children).find((listItemElement) => { if (listItemElement.classList.contains("li")) { if (hasFold) { listItemElement.removeAttribute("fold"); } else if (listItemElement.childElementCount > 3) { listItemElement.setAttribute("fold", "1"); } const listId = listItemElement.getAttribute("data-node-id"); doOperations.push({ action: "setAttrs", id: listId, data: JSON.stringify({fold: hasFold ? "" : "1"}) }); undoOperations.push({ action: "setAttrs", id: listId, data: JSON.stringify({fold: hasFold ? "1" : ""}) }); } }); transaction(protyle, doOperations, undoOperations); } else { setFold(protyle, foldElement); } foldElement.classList.remove("protyle-wysiwyg--hl"); } else if (window.siyuan.shiftIsPressed && !protyle.disabled) { openAttr(protyle.wysiwyg.element.querySelector(`[data-node-id="${id}"]`), "bookmark", protyle); } else if (!window.siyuan.ctrlIsPressed && !window.siyuan.altIsPressed && !window.siyuan.shiftIsPressed) { this.renderMenu(protyle, buttonElement); // https://ld246.com/article/1648433751993 if (!protyle.toolbar.range) { protyle.toolbar.range = getEditorRange(protyle.wysiwyg.element.firstElementChild); } if (isMobile()) { window.siyuan.menus.menu.fullscreen(); } else { window.siyuan.menus.menu.popup({x: gutterRect.left, y: gutterRect.bottom, isLeft: true}); const popoverElement = hasTopClosestByClassName(protyle.element, "block__popover", true); window.siyuan.menus.menu.element.setAttribute("data-from", popoverElement ? popoverElement.dataset.level + "popover" : "app"); focusByRange(protyle.toolbar.range); } } }); this.element.addEventListener("contextmenu", (event: MouseEvent & { target: HTMLInputElement }) => { const buttonElement = hasClosestByTag(event.target, "BUTTON"); if (!buttonElement || buttonElement.getAttribute("data-type") === "fold") { return; } if (!window.siyuan.ctrlIsPressed && !window.siyuan.altIsPressed && !window.siyuan.shiftIsPressed) { hideTooltip(); const gutterRect = buttonElement.getBoundingClientRect(); if (buttonElement.dataset.type === "NodeAttributeViewRowMenu") { const rowElement = Array.from(protyle.wysiwyg.element.querySelectorAll(`.av[data-node-id="${buttonElement.dataset.nodeId}"] .av__row[data-id="${buttonElement.dataset.rowId}"]`)).find((item: HTMLElement) => { if (!isInEmbedBlock(item)) { return true; } }); if (rowElement) { avContextmenu(protyle, rowElement as HTMLElement, { x: gutterRect.left, y: gutterRect.bottom, w: gutterRect.width, h: gutterRect.height, isLeft: true }); } } else if (buttonElement.dataset.type !== "NodeAttributeViewRow") { this.renderMenu(protyle, buttonElement); window.siyuan.menus.menu.popup({x: gutterRect.left, y: gutterRect.bottom, isLeft: true}); const popoverElement = hasTopClosestByClassName(protyle.element, "block__popover", true); window.siyuan.menus.menu.element.setAttribute("data-from", popoverElement ? popoverElement.dataset.level + "popover" : "app"); } } event.preventDefault(); event.stopPropagation(); }); this.element.addEventListener("mouseover", (event: MouseEvent & { target: HTMLInputElement }) => { const buttonElement = hasClosestByTag(event.target, "BUTTON"); if (!buttonElement) { return; } const type = buttonElement.getAttribute("data-type"); if (type === "fold" || type === "NodeAttributeViewRow") { Array.from(protyle.wysiwyg.element.querySelectorAll(".protyle-wysiwyg--hl, .av__row--hl")).forEach(item => { item.classList.remove("protyle-wysiwyg--hl", "av__row--hl"); }); return; } Array.from(protyle.wysiwyg.element.querySelectorAll(`[data-node-id="${buttonElement.getAttribute("data-node-id")}"]`)).find(item => { if (!isInEmbedBlock(item) && this.isMatchNode(item)) { const rowItem = item.querySelector(`.av__row[data-id="${buttonElement.dataset.rowId}"]`); Array.from(protyle.wysiwyg.element.querySelectorAll(".protyle-wysiwyg--hl, .av__row--hl")).forEach(hlItem => { if (!item.isSameNode(hlItem)) { hlItem.classList.remove("protyle-wysiwyg--hl"); } if (rowItem && !rowItem.isSameNode(hlItem)) { rowItem.classList.remove("av__row--hl"); } }); if (type === "NodeAttributeViewRowMenu") { rowItem.classList.add("av__row--hl"); } else { item.classList.add("protyle-wysiwyg--hl"); } return true; } }); event.preventDefault(); }); this.element.addEventListener("mouseleave", (event: MouseEvent & { target: HTMLInputElement }) => { Array.from(protyle.wysiwyg.element.querySelectorAll(".protyle-wysiwyg--hl, .av__row--hl")).forEach(item => { item.classList.remove("protyle-wysiwyg--hl", "av__row--hl"); }); event.preventDefault(); event.stopPropagation(); }); // https://github.com/siyuan-note/siyuan/issues/12751 this.element.addEventListener("mousewheel", (event) => { hideElements(["gutter"], protyle); event.stopPropagation(); event.preventDefault(); }); } private isMatchNode(item: Element) { const itemRect = item.getBoundingClientRect(); // 原本为4,由于 https://github.com/siyuan-note/siyuan/issues/12166 改为 6 let gutterTop = this.element.getBoundingClientRect().top + 6; if (itemRect.height < Math.floor(window.siyuan.config.editor.fontSize * 1.625) + 8) { gutterTop = gutterTop - (itemRect.height - this.element.clientHeight) / 2; } return itemRect.top <= gutterTop && itemRect.bottom >= gutterTop; } private turnsOneInto(options: { menuId?: string, id: string, icon: string, label: string, protyle: IProtyle, nodeElement: Element, accelerator?: string type: string, level?: number }) { return { id: options.menuId, icon: options.icon, label: options.label, accelerator: options.accelerator, click() { turnsOneInto(options); } }; } private turnsIntoOne(options: { menuId?: string, accelerator?: string, icon?: string, label: string, protyle: IProtyle, selectsElement: Element[], type: TTurnIntoOne, level?: TTurnIntoOneSub, }) { return { id: options.menuId, icon: options.icon, label: options.label, accelerator: options.accelerator, click() { turnsIntoOneTransaction(options); } }; } private turnsInto(options: { menuId?: string, icon?: string, label: string, protyle: IProtyle, selectsElement: Element[], type: TTurnInto, level?: number, isContinue?: boolean, accelerator?: string, }) { return { id: options.menuId, icon: options.icon, label: options.label, accelerator: options.accelerator, click() { turnsIntoTransaction(options); } }; } private showMobileAppearance(protyle: IProtyle) { const toolbarElement = document.getElementById("keyboardToolbar"); const dynamicElements = toolbarElement.querySelectorAll("#keyboardToolbar .keyboard__dynamic"); dynamicElements[0].classList.add("fn__none"); dynamicElements[1].classList.remove("fn__none"); toolbarElement.querySelector('.keyboard__action[data-type="text"]').classList.add("protyle-toolbar__item--current"); toolbarElement.querySelector('.keyboard__action[data-type="done"] use').setAttribute("xlink:href", "#iconCloseRound"); toolbarElement.classList.remove("fn__none"); const oldScrollTop = protyle.contentElement.scrollTop + 333.5; // toolbarElement.clientHeight renderTextMenu(protyle, toolbarElement); showKeyboardToolbarUtil(oldScrollTop); } public renderMultipleMenu(protyle: IProtyle, selectsElement: Element[]) { let isList = false; let isContinue = false; let hasEmbedBlock = false; selectsElement.find((item, index) => { if (item.classList.contains("li")) { isList = true; return true; } if (item.classList.contains("sb") || item.classList.contains("p")) { hasEmbedBlock = true; } if (item.nextElementSibling && selectsElement[index + 1] && item.nextElementSibling.isSameNode(selectsElement[index + 1])) { isContinue = true; } else if (index !== selectsElement.length - 1) { isContinue = false; return true; } }); if (!isList && !protyle.disabled) { const turnIntoSubmenu: IMenu[] = []; if (isContinue) { turnIntoSubmenu.push(this.turnsIntoOne({ menuId: "list", icon: "iconList", label: window.siyuan.languages.list, protyle, accelerator: window.siyuan.config.keymap.editor.insert.list.custom, selectsElement, type: "Blocks2ULs" })); turnIntoSubmenu.push(this.turnsIntoOne({ menuId: "orderedList", icon: "iconOrderedList", label: window.siyuan.languages["ordered-list"], accelerator: window.siyuan.config.keymap.editor.insert["ordered-list"].custom, protyle, selectsElement, type: "Blocks2OLs" })); turnIntoSubmenu.push(this.turnsIntoOne({ menuId: "check", icon: "iconCheck", label: window.siyuan.languages.check, accelerator: window.siyuan.config.keymap.editor.insert.check.custom, protyle, selectsElement, type: "Blocks2TLs" })); turnIntoSubmenu.push(this.turnsIntoOne({ menuId: "quote", icon: "iconQuote", label: window.siyuan.languages.quote, accelerator: window.siyuan.config.keymap.editor.insert.quote.custom, protyle, selectsElement, type: "Blocks2Blockquote" })); } // 多选引用转换为块的时候 id 不一致 if (!hasEmbedBlock) { turnIntoSubmenu.push(this.turnsInto({ menuId: "paragraph", icon: "iconParagraph", label: window.siyuan.languages.paragraph, accelerator: window.siyuan.config.keymap.editor.heading.paragraph.custom, protyle, selectsElement, type: "Blocks2Ps", isContinue })); } turnIntoSubmenu.push(this.turnsInto({ menuId: "heading1", icon: "iconH1", label: window.siyuan.languages.heading1, accelerator: window.siyuan.config.keymap.editor.heading.heading1.custom, protyle, selectsElement, level: 1, type: "Blocks2Hs", isContinue })); turnIntoSubmenu.push(this.turnsInto({ menuId: "heading2", icon: "iconH2", label: window.siyuan.languages.heading2, accelerator: window.siyuan.config.keymap.editor.heading.heading2.custom, protyle, selectsElement, level: 2, type: "Blocks2Hs", isContinue })); turnIntoSubmenu.push(this.turnsInto({ menuId: "heading3", icon: "iconH3", label: window.siyuan.languages.heading3, accelerator: window.siyuan.config.keymap.editor.heading.heading3.custom, protyle, selectsElement, level: 3, type: "Blocks2Hs", isContinue })); turnIntoSubmenu.push(this.turnsInto({ menuId: "heading4", icon: "iconH4", label: window.siyuan.languages.heading4, accelerator: window.siyuan.config.keymap.editor.heading.heading4.custom, protyle, selectsElement, level: 4, type: "Blocks2Hs", isContinue })); turnIntoSubmenu.push(this.turnsInto({ menuId: "heading5", icon: "iconH5", label: window.siyuan.languages.heading5, accelerator: window.siyuan.config.keymap.editor.heading.heading5.custom, protyle, selectsElement, level: 5, type: "Blocks2Hs", isContinue })); turnIntoSubmenu.push(this.turnsInto({ menuId: "heading6", icon: "iconH6", label: window.siyuan.languages.heading6, accelerator: window.siyuan.config.keymap.editor.heading.heading6.custom, protyle, selectsElement, level: 6, type: "Blocks2Hs", isContinue })); window.siyuan.menus.menu.append(new MenuItem({ id: "turnInto", icon: "iconRefresh", label: window.siyuan.languages.turnInto, type: "submenu", submenu: turnIntoSubmenu }).element); if (isContinue) { window.siyuan.menus.menu.append(new MenuItem({ id: "mergeSuperBlock", icon: "iconSuper", label: window.siyuan.languages.merge + " " + window.siyuan.languages.superBlock, type: "submenu", submenu: [this.turnsIntoOne({ menuId: "hLayout", label: window.siyuan.languages.hLayout, accelerator: window.siyuan.config.keymap.editor.general.hLayout.custom, icon: "iconSplitLR", protyle, selectsElement, type: "BlocksMergeSuperBlock", level: "col" }), this.turnsIntoOne({ menuId: "vLayout", label: window.siyuan.languages.vLayout, accelerator: window.siyuan.config.keymap.editor.general.vLayout.custom, icon: "iconSplitTB", protyle, selectsElement, type: "BlocksMergeSuperBlock", level: "row" })] }).element); } } if (!protyle.disabled) { window.siyuan.menus.menu.append(new MenuItem({ id: "ai", icon: "iconSparkles", label: window.siyuan.languages.ai, accelerator: window.siyuan.config.keymap.editor.general.ai.custom, click() { AIActions(selectsElement, protyle); } }).element); } const copyMenu: IMenu[] = (copySubMenu(Array.from(selectsElement).map(item => item.getAttribute("data-node-id")), true, selectsElement[0]) as IMenu[]).concat([{ id: "copyPlainText", iconHTML: "", label: window.siyuan.languages.copyPlainText, accelerator: window.siyuan.config.keymap.editor.general.copyPlainText.custom, click() { let html = ""; selectsElement.forEach((item: HTMLElement) => { html += getPlainText(item) + "\n"; }); copyPlainText(html.trimEnd()); focusBlock(selectsElement[0]); } }, { id: "copy", iconHTML: "", label: window.siyuan.languages.copy, accelerator: "⌘C", click() { if (isNotEditBlock(selectsElement[0])) { focusBlock(selectsElement[0]); } else { focusByRange(getEditorRange(selectsElement[0])); } document.execCommand("copy"); } }, { id: "duplicate", iconHTML: "", label: window.siyuan.languages.duplicate, accelerator: window.siyuan.config.keymap.editor.general.duplicate.custom, disabled: protyle.disabled, click() { duplicateBlock(selectsElement, protyle); } }]); copyMenu.splice(4, 1, { id: "copyHPath", iconHTML: "", label: window.siyuan.languages.copyHPath, accelerator: window.siyuan.config.keymap.editor.general.copyHPath.custom, click: () => { copyTextByType([selectsElement[0].getAttribute("data-node-id")], "hPath"); focusBlock(selectsElement[0]); } }); const copyTextRefMenu = this.genCopyTextRef(selectsElement); if (copyTextRefMenu) { copyMenu.splice(7, 0, copyTextRefMenu); } window.siyuan.menus.menu.append(new MenuItem({ id: "copy", label: window.siyuan.languages.copy, icon: "iconCopy", type: "submenu", submenu: copyMenu, }).element); if (protyle.disabled) { return; } window.siyuan.menus.menu.append(new MenuItem({ id: "cut", label: window.siyuan.languages.cut, accelerator: "⌘X", icon: "iconCut", click: () => { focusBlock(selectsElement[0]); document.execCommand("cut"); } }).element); window.siyuan.menus.menu.append(new MenuItem({ id: "move", label: window.siyuan.languages.move, accelerator: window.siyuan.config.keymap.general.move.custom, icon: "iconMove", click: () => { movePathTo((toPath) => { hintMoveBlock(toPath[0], selectsElement, protyle); }); } }).element); const range = getSelection().rangeCount > 0 ? getSelection().getRangeAt(0) : undefined; window.siyuan.menus.menu.append(new MenuItem({ id: "addToDatabase", label: window.siyuan.languages.addToDatabase, accelerator: window.siyuan.config.keymap.general.addToDatabase.custom, icon: "iconDatabase", click: () => { addEditorToDatabase(protyle, range); } }).element); window.siyuan.menus.menu.append(new MenuItem({ id: "delete", label: window.siyuan.languages.delete, icon: "iconTrashcan", accelerator: "⌫", click: () => { protyle.breadcrumb?.hide(); removeBlock(protyle, selectsElement[0], getEditorRange(selectsElement[0]), "Backspace"); } }).element); window.siyuan.menus.menu.append(new MenuItem({id: "separator_appearance", type: "separator"}).element); const appearanceElement = new MenuItem({ id: "appearance", label: window.siyuan.languages.appearance, icon: "iconFont", accelerator: window.siyuan.config.keymap.editor.insert.appearance.custom, click: () => { /// #if MOBILE this.showMobileAppearance(protyle); /// #else protyle.toolbar.element.classList.add("fn__none"); protyle.toolbar.subElement.innerHTML = ""; protyle.toolbar.subElement.style.width = ""; protyle.toolbar.subElement.style.padding = ""; protyle.toolbar.subElement.append(appearanceMenu(protyle, selectsElement)); protyle.toolbar.subElement.style.zIndex = (++window.siyuan.zIndex).toString(); protyle.toolbar.subElement.classList.remove("fn__none"); protyle.toolbar.subElementCloseCB = undefined; const position = selectsElement[0].getBoundingClientRect(); setPosition(protyle.toolbar.subElement, position.left, position.top); /// #endif } }).element; window.siyuan.menus.menu.append(appearanceElement); if (!isMobile()) { appearanceElement.lastElementChild.classList.add("b3-menu__submenu--row"); } this.genAlign(selectsElement, protyle); this.genWidths(selectsElement, protyle); // this.genHeights(selectsElement, protyle); if (!window.siyuan.config.readonly) { window.siyuan.menus.menu.append(new MenuItem({id: "separator_quickMakeCard", type: "separator"}).element); window.siyuan.menus.menu.append(new MenuItem({ id: "quickMakeCard", label: window.siyuan.languages.quickMakeCard, accelerator: window.siyuan.config.keymap.editor.general.quickMakeCard.custom, iconHTML: '', icon: "iconRiffCard", click() { quickMakeCard(protyle, selectsElement); } }).element); window.siyuan.menus.menu.append(new MenuItem({ id: "addToDeck", label: window.siyuan.languages.addToDeck, icon: "iconRiffCard", ignore: !window.siyuan.config.flashcard.deck, click() { const ids: string[] = []; selectsElement.forEach(item => { if (item.getAttribute("data-type") === "NodeThematicBreak") { return; } ids.push(item.getAttribute("data-node-id")); }); makeCard(protyle.app, ids); } }).element); } if (protyle?.app?.plugins) { emitOpenMenu({ plugins: protyle.app.plugins, type: "click-blockicon", detail: { protyle, blockElements: selectsElement, }, separatorPosition: "top", }); } return window.siyuan.menus.menu; } public renderMenu(protyle: IProtyle, buttonElement: Element) { if (!buttonElement) { return; } hideElements(["util", "toolbar", "hint"], protyle); window.siyuan.menus.menu.remove(); if (isMobile()) { activeBlur(); } const id = buttonElement.getAttribute("data-node-id"); const selectsElement = protyle.wysiwyg.element.querySelectorAll(".protyle-wysiwyg--select"); if (selectsElement.length > 1) { const match = Array.from(selectsElement).find(item => { if (id === item.getAttribute("data-node-id")) { return true; } }); if (match) { return this.renderMultipleMenu(protyle, Array.from(selectsElement)); } } let nodeElement: Element; if (buttonElement.tagName === "BUTTON") { Array.from(protyle.wysiwyg.element.querySelectorAll(`[data-node-id="${id}"]`)).find(item => { if (!isInEmbedBlock(item) && this.isMatchNode(item)) { nodeElement = item; return true; } }); } else { nodeElement = buttonElement; } if (!nodeElement) { return; } const type = nodeElement.getAttribute("data-type"); const subType = nodeElement.getAttribute("data-subtype"); const turnIntoSubmenu: IMenu[] = []; hideElements(["select"], protyle); nodeElement.classList.add("protyle-wysiwyg--select"); countBlockWord([id], protyle.block.rootID); // "heading1-6", "list", "ordered-list", "check", "quote", "code", "table", "line", "math", "paragraph" if (type === "NodeParagraph" && !protyle.disabled) { turnIntoSubmenu.push(this.turnsIntoOne({ menuId: "list", icon: "iconList", label: window.siyuan.languages.list, accelerator: window.siyuan.config.keymap.editor.insert.list.custom, protyle, selectsElement: [nodeElement], type: "Blocks2ULs" })); turnIntoSubmenu.push(this.turnsIntoOne({ menuId: "orderedList", icon: "iconOrderedList", label: window.siyuan.languages["ordered-list"], accelerator: window.siyuan.config.keymap.editor.insert["ordered-list"].custom, protyle, selectsElement: [nodeElement], type: "Blocks2OLs" })); turnIntoSubmenu.push(this.turnsIntoOne({ menuId: "check", icon: "iconCheck", label: window.siyuan.languages.check, accelerator: window.siyuan.config.keymap.editor.insert.check.custom, protyle, selectsElement: [nodeElement], type: "Blocks2TLs" })); turnIntoSubmenu.push(this.turnsIntoOne({ menuId: "quote", icon: "iconQuote", label: window.siyuan.languages.quote, accelerator: window.siyuan.config.keymap.editor.insert.quote.custom, protyle, selectsElement: [nodeElement], type: "Blocks2Blockquote" })); turnIntoSubmenu.push(this.turnsInto({ menuId: "heading1", icon: "iconH1", label: window.siyuan.languages.heading1, accelerator: window.siyuan.config.keymap.editor.heading.heading1.custom, protyle, selectsElement: [nodeElement], level: 1, type: "Blocks2Hs", })); turnIntoSubmenu.push(this.turnsInto({ menuId: "heading2", icon: "iconH2", label: window.siyuan.languages.heading2, accelerator: window.siyuan.config.keymap.editor.heading.heading2.custom, protyle, selectsElement: [nodeElement], level: 2, type: "Blocks2Hs", })); turnIntoSubmenu.push(this.turnsInto({ menuId: "heading3", icon: "iconH3", label: window.siyuan.languages.heading3, accelerator: window.siyuan.config.keymap.editor.heading.heading3.custom, protyle, selectsElement: [nodeElement], level: 3, type: "Blocks2Hs", })); turnIntoSubmenu.push(this.turnsInto({ menuId: "heading4", icon: "iconH4", label: window.siyuan.languages.heading4, accelerator: window.siyuan.config.keymap.editor.heading.heading4.custom, protyle, selectsElement: [nodeElement], level: 4, type: "Blocks2Hs", })); turnIntoSubmenu.push(this.turnsInto({ menuId: "heading5", icon: "iconH5", label: window.siyuan.languages.heading5, accelerator: window.siyuan.config.keymap.editor.heading.heading5.custom, protyle, selectsElement: [nodeElement], level: 5, type: "Blocks2Hs", })); turnIntoSubmenu.push(this.turnsInto({ menuId: "heading6", icon: "iconH6", label: window.siyuan.languages.heading6, accelerator: window.siyuan.config.keymap.editor.heading.heading6.custom, protyle, selectsElement: [nodeElement], level: 6, type: "Blocks2Hs", })); } else if (type === "NodeHeading" && !protyle.disabled) { turnIntoSubmenu.push(this.turnsInto({ menuId: "paragraph", icon: "iconParagraph", label: window.siyuan.languages.paragraph, accelerator: window.siyuan.config.keymap.editor.heading.paragraph.custom, protyle, selectsElement: [nodeElement], type: "Blocks2Ps", })); turnIntoSubmenu.push(this.turnsIntoOne({ menuId: "quote", icon: "iconQuote", label: window.siyuan.languages.quote, accelerator: window.siyuan.config.keymap.editor.insert.quote.custom, protyle, selectsElement: [nodeElement], type: "Blocks2Blockquote" })); if (subType !== "h1") { turnIntoSubmenu.push(this.turnsInto({ menuId: "heading1", icon: "iconH1", label: window.siyuan.languages.heading1, accelerator: window.siyuan.config.keymap.editor.heading.heading1.custom, protyle, selectsElement: [nodeElement], level: 1, type: "Blocks2Hs", })); } if (subType !== "h2") { turnIntoSubmenu.push(this.turnsInto({ menuId: "heading2", icon: "iconH2", label: window.siyuan.languages.heading2, accelerator: window.siyuan.config.keymap.editor.heading.heading2.custom, protyle, selectsElement: [nodeElement], level: 2, type: "Blocks2Hs", })); } if (subType !== "h3") { turnIntoSubmenu.push(this.turnsInto({ menuId: "heading3", icon: "iconH3", label: window.siyuan.languages.heading3, accelerator: window.siyuan.config.keymap.editor.heading.heading3.custom, protyle, selectsElement: [nodeElement], level: 3, type: "Blocks2Hs", })); } if (subType !== "h4") { turnIntoSubmenu.push(this.turnsInto({ menuId: "heading4", icon: "iconH4", label: window.siyuan.languages.heading4, accelerator: window.siyuan.config.keymap.editor.heading.heading4.custom, protyle, selectsElement: [nodeElement], level: 4, type: "Blocks2Hs", })); } if (subType !== "h5") { turnIntoSubmenu.push(this.turnsInto({ menuId: "heading5", icon: "iconH5", label: window.siyuan.languages.heading5, accelerator: window.siyuan.config.keymap.editor.heading.heading5.custom, protyle, selectsElement: [nodeElement], level: 5, type: "Blocks2Hs", })); } if (subType !== "h6") { turnIntoSubmenu.push(this.turnsInto({ menuId: "heading6", icon: "iconH6", label: window.siyuan.languages.heading6, accelerator: window.siyuan.config.keymap.editor.heading.heading6.custom, protyle, selectsElement: [nodeElement], level: 6, type: "Blocks2Hs", })); } } else if (type === "NodeList" && !protyle.disabled) { turnIntoSubmenu.push(this.turnsOneInto({ menuId: "paragraph", id, icon: "iconParagraph", label: window.siyuan.languages.paragraph, accelerator: window.siyuan.config.keymap.editor.heading.paragraph.custom, protyle, nodeElement, type: "CancelList" })); turnIntoSubmenu.push(this.turnsIntoOne({ menuId: "quote", icon: "iconQuote", label: window.siyuan.languages.quote, accelerator: window.siyuan.config.keymap.editor.insert.quote.custom, protyle, selectsElement: [nodeElement], type: "Blocks2Blockquote" })); if (nodeElement.getAttribute("data-subtype") === "o") { turnIntoSubmenu.push(this.turnsOneInto({ menuId: "list", id, icon: "iconList", label: window.siyuan.languages.list, accelerator: window.siyuan.config.keymap.editor.insert.list.custom, protyle, nodeElement, type: "OL2UL" })); turnIntoSubmenu.push(this.turnsOneInto({ menuId: "check", id, icon: "iconCheck", label: window.siyuan.languages.check, accelerator: window.siyuan.config.keymap.editor.insert.check.custom, protyle, nodeElement, type: "UL2TL" })); } else if (nodeElement.getAttribute("data-subtype") === "t") { turnIntoSubmenu.push(this.turnsOneInto({ menuId: "list", id, icon: "iconList", label: window.siyuan.languages.list, accelerator: window.siyuan.config.keymap.editor.insert.list.custom, protyle, nodeElement, type: "TL2UL" })); turnIntoSubmenu.push(this.turnsOneInto({ menuId: "orderedList", id, icon: "iconOrderedList", label: window.siyuan.languages["ordered-list"], accelerator: window.siyuan.config.keymap.editor.insert["ordered-list"].custom, protyle, nodeElement, type: "TL2OL" })); } else { turnIntoSubmenu.push(this.turnsOneInto({ menuId: "orderedList", id, icon: "iconOrderedList", label: window.siyuan.languages["ordered-list"], accelerator: window.siyuan.config.keymap.editor.insert["ordered-list"].custom, protyle, nodeElement, type: "UL2OL" })); turnIntoSubmenu.push(this.turnsOneInto({ menuId: "check", id, icon: "iconCheck", label: window.siyuan.languages.check, accelerator: window.siyuan.config.keymap.editor.insert.check.custom, protyle, nodeElement, type: "OL2TL" })); } } else if (type === "NodeBlockquote" && !protyle.disabled) { turnIntoSubmenu.push(this.turnsOneInto({ menuId: "paragraph", id, icon: "iconParagraph", label: window.siyuan.languages.paragraph, accelerator: window.siyuan.config.keymap.editor.heading.paragraph.custom, protyle, nodeElement, type: "CancelBlockquote" })); } if (turnIntoSubmenu.length > 0 && !protyle.disabled) { window.siyuan.menus.menu.append(new MenuItem({ id: "turnInto", icon: "iconRefresh", label: window.siyuan.languages.turnInto, type: "submenu", submenu: turnIntoSubmenu }).element); } if (!protyle.disabled && !nodeElement.classList.contains("hr")) { window.siyuan.menus.menu.append(new MenuItem({ id: "ai", icon: "iconSparkles", label: window.siyuan.languages.ai, accelerator: window.siyuan.config.keymap.editor.general.ai.custom, click() { AIActions([nodeElement], protyle); } }).element); } const copyMenu = (copySubMenu([id], true, nodeElement) as IMenu[]).concat([{ id: "copyPlainText", iconHTML: "", label: window.siyuan.languages.copyPlainText, accelerator: window.siyuan.config.keymap.editor.general.copyPlainText.custom, click() { copyPlainText(getPlainText(nodeElement as HTMLElement).trimEnd()); focusBlock(nodeElement); } }, { id: type === "NodeAttributeView" ? "copyMirror" : "copy", iconHTML: "", label: type === "NodeAttributeView" ? window.siyuan.languages.copyMirror : window.siyuan.languages.copy, accelerator: "⌘C", click() { if (isNotEditBlock(nodeElement)) { focusBlock(nodeElement); } else { focusByRange(getEditorRange(nodeElement)); } document.execCommand("copy"); } }, { id: type === "NodeAttributeView" ? "duplicateMirror" : "duplicate", iconHTML: "", label: type === "NodeAttributeView" ? window.siyuan.languages.duplicateMirror : window.siyuan.languages.duplicate, accelerator: window.siyuan.config.keymap.editor.general.duplicate.custom, disabled: protyle.disabled, click() { duplicateBlock([nodeElement], protyle); } }]); if (type === "NodeAttributeView") { copyMenu.push({ id: "duplicateCompletely", iconHTML: "", label: window.siyuan.languages.duplicateCompletely, accelerator: window.siyuan.config.keymap.editor.general.duplicateCompletely.custom, disabled: protyle.disabled, click() { duplicateCompletely(protyle, nodeElement as HTMLElement); } }); } const copyTextRefMenu = this.genCopyTextRef([nodeElement]); if (copyTextRefMenu) { copyMenu.splice(7, 0, copyTextRefMenu); } window.siyuan.menus.menu.append(new MenuItem({ id: "copy", icon: "iconCopy", label: window.siyuan.languages.copy, type: "submenu", submenu: copyMenu }).element); if (!protyle.disabled) { window.siyuan.menus.menu.append(new MenuItem({ id: "cut", icon: "iconCut", label: window.siyuan.languages.cut, accelerator: "⌘X", click: () => { focusBlock(nodeElement); document.execCommand("cut"); } }).element); window.siyuan.menus.menu.append(new MenuItem({ id: "move", icon: "iconMove", label: window.siyuan.languages.move, accelerator: window.siyuan.config.keymap.general.move.custom, click: () => { movePathTo((toPath) => { hintMoveBlock(toPath[0], [nodeElement], protyle); }); } }).element); const range = getSelection().rangeCount > 0 ? getSelection().getRangeAt(0) : undefined; window.siyuan.menus.menu.append(new MenuItem({ id: "addToDatabase", icon: "iconDatabase", label: window.siyuan.languages.addToDatabase, accelerator: window.siyuan.config.keymap.general.addToDatabase.custom, click: () => { addEditorToDatabase(protyle, range); } }).element); window.siyuan.menus.menu.append(new MenuItem({ id: "delete", icon: "iconTrashcan", label: window.siyuan.languages.delete, accelerator: "⌫", click: () => { protyle.breadcrumb?.hide(); removeBlock(protyle, nodeElement, getEditorRange(nodeElement), "Backspace"); } }).element); } if (type === "NodeSuperBlock" && !protyle.disabled) { window.siyuan.menus.menu.append(new MenuItem({ id: "separator_cancelSuperBlock", type: "separator" }).element); window.siyuan.menus.menu.append(new MenuItem({ id: "cancelSuperBlock", label: window.siyuan.languages.cancel + " " + window.siyuan.languages.superBlock, click() { const sbData = cancelSB(protyle, nodeElement); transaction(protyle, sbData.doOperations, sbData.undoOperations); focusBlock(protyle.wysiwyg.element.querySelector(`[data-node-id="${sbData.previousId}"]`)); hideElements(["gutter"], protyle); } }).element); } else if (type === "NodeCodeBlock" && !protyle.disabled && !nodeElement.getAttribute("data-subtype")) { window.siyuan.menus.menu.append(new MenuItem({id: "separator_code", type: "separator"}).element); const linewrap = nodeElement.getAttribute("linewrap"); const ligatures = nodeElement.getAttribute("ligatures"); const linenumber = nodeElement.getAttribute("linenumber"); window.siyuan.menus.menu.append(new MenuItem({ id: "code", type: "submenu", icon: "iconCode", label: window.siyuan.languages.code, submenu: [{ id: "md31", iconHTML: "", label: `