import {paste} from "../util/paste"; import { hasClosestBlock, hasClosestByAttribute, hasClosestByClassName, hasClosestByMatchTag, hasTopClosestByClassName, } from "../util/hasClosest"; import { focusBlock, focusByRange, focusByWbr, focusSideBlock, getEditorRange, getSelectionOffset, setLastNodeRange, } from "../util/selection"; import {Constants} from "../../constants"; import {getSearch, isMobile} from "../../util/functions"; import {isLocalPath, pathPosix} from "../../util/pathName"; import {genEmptyElement} from "../../block/util"; import {previewDocImage} from "../preview/image"; import { contentMenu, enterBack, fileAnnotationRefMenu, imgMenu, linkMenu, refMenu, setFold, tagMenu, zoomOut } from "../../menus/protyle"; import * as dayjs from "dayjs"; import {dropEvent} from "../util/editorCommonEvent"; import {input} from "./input"; import { getContenteditableElement, getLastBlock, getNextBlock, getTopAloneElement, hasNextSibling, hasPreviousSibling, isNotEditBlock } from "./getBlock"; import {transaction, updateTransaction} from "./transaction"; import {hideElements} from "../ui/hideElements"; /// #if !BROWSER import {ipcRenderer, shell} from "electron"; /// #endif import {getEnableHTML, removeEmbed} from "./removeEmbed"; import {keydown} from "./keydown"; import {openMobileFileById} from "../../mobile/editor"; import {removeBlock} from "./remove"; import {highlightRender} from "../render/highlightRender"; import {openAttr} from "../../menus/commonMenuItem"; import {blockRender} from "../render/blockRender"; /// #if !MOBILE import {getAllModels} from "../../layout/getAll"; import {pushBack} from "../../util/backForward"; import {openAsset, openBy, openFileById} from "../../editor/util"; import {openGlobalSearch} from "../../search/util"; /// #else import {popSearch} from "../../mobile/menu/search"; /// #endif import {BlockPanel} from "../../block/Panel"; import {isInIOS, isOnlyMeta, openByMobile} from "../util/compatibility"; import {MenuItem} from "../../menus/Menu"; import {fetchPost} from "../../util/fetch"; import {onGet} from "../util/onGet"; import {setTableAlign} from "../util/table"; import {countBlockWord, countSelectWord} from "../../layout/status"; import {showMessage} from "../../dialog/message"; import {getBacklinkHeadingMore, loadBreadcrumb} from "./renderBacklink"; import {removeSearchMark} from "../toolbar/util"; import {activeBlur, hideKeyboardToolbar} from "../../mobile/util/keyboardToolbar"; import {commonClick} from "./commonClick"; import {avClick, avContextmenu, updateAVName} from "../render/av/action"; import {stickyRow, updateHeader} from "../render/av/row"; export class WYSIWYG { public lastHTMLs: { [key: string]: string } = {}; public element: HTMLDivElement; public preventKeyup: boolean; private shiftStartElement: HTMLElement; constructor(protyle: IProtyle) { this.element = document.createElement("div"); this.element.className = "protyle-wysiwyg"; this.element.setAttribute("spellcheck", "false"); if (isMobile()) { // iPhone,iPad 端输入 contenteditable 为 true 时会在块中间插入 span // Android 端空块输入法弹出会收起 https://ld246.com/article/1689713888289 this.element.setAttribute("contenteditable", "false"); } else { this.element.setAttribute("contenteditable", "true"); } if (window.siyuan.config.editor.displayBookmarkIcon) { this.element.classList.add("protyle-wysiwyg--attr"); } this.bindCommonEvent(protyle); if (protyle.options.action.includes(Constants.CB_GET_HISTORY)) { return; } this.bindEvent(protyle); keydown(protyle, this.element); dropEvent(protyle, this.element); } public renderCustom(ial: IObject) { let isFullWidth = ial[Constants.CUSTOM_SY_FULLWIDTH]; if (!isFullWidth) { isFullWidth = window.siyuan.config.editor.fullWidth ? "true" : "false"; } if (isFullWidth === "true") { this.element.parentElement.setAttribute("data-fullwidth", "true"); } else { this.element.parentElement.removeAttribute("data-fullwidth"); } const ialKeys = Object.keys(ial); for (let i = 0; i < this.element.attributes.length; i++) { const oldKey = this.element.attributes[i].nodeName; if (!["type", "class", "spellcheck", "contenteditable", "data-doc-type", "style", "scroll", "data-realwidth"].includes(oldKey) && !ialKeys.includes(oldKey)) { this.element.removeAttribute(oldKey); i--; } } ialKeys.forEach((key: string) => { if (!["title-img", "title", "updated", "icon", "id", "type", "class", "spellcheck", "contenteditable", "data-doc-type", "style", "data-realwidth"].includes(key)) { this.element.setAttribute(key, ial[key]); } }); } // text block-ref file-annotation-ref a 结尾处打字应为普通文本 private escapeInline(protyle: IProtyle, range: Range, event: InputEvent) { if (!event.data) { return; } const inputData = event.data; protyle.toolbar.range = range; const inlineElement = range.startContainer.parentElement; const currentTypes = protyle.toolbar.getCurrentType(); let dataLength = inputData.length; if (inputData === "<" || inputData === ">") { // 使用 inlineElement.innerHTML 会出现 https://ld246.com/article/1627185027423 中的第2个问题 dataLength = 4; } // https://github.com/siyuan-note/siyuan/issues/5924 if (currentTypes.length > 0 && range.toString() === "" && range.startOffset === inputData.length && inlineElement.tagName === "SPAN" && inlineElement.textContent.replace(Constants.ZWSP, "") !== inputData && inlineElement.textContent.replace(Constants.ZWSP, "").length >= inputData.length && !hasPreviousSibling(range.startContainer) && !hasPreviousSibling(inlineElement)) { const html = inlineElement.innerHTML.replace(Constants.ZWSP, ""); inlineElement.innerHTML = html.substr(dataLength); const textNode = document.createTextNode(inputData); inlineElement.before(textNode); range.selectNodeContents(textNode); range.collapse(false); return; } if (// 表格行内公式之前无法插入文字 https://github.com/siyuan-note/siyuan/issues/3908 inlineElement.tagName === "SPAN" && inlineElement.textContent !== inputData && !currentTypes.includes("search-mark") && // https://github.com/siyuan-note/siyuan/issues/7586 range.toString() === "" && range.startContainer.nodeType === 3 && (currentTypes.includes("inline-memo") || currentTypes.includes("text") || currentTypes.includes("block-ref") || currentTypes.includes("file-annotation-ref") || currentTypes.includes("a")) && !hasNextSibling(range.startContainer) && range.startContainer.textContent.length === range.startOffset && inlineElement.textContent.length > inputData.length ) { const position = getSelectionOffset(inlineElement, protyle.wysiwyg.element, range); const html = inlineElement.innerHTML; if (position.start === inlineElement.textContent.length) { // 使用 inlineElement.textContent **$a$b** 中数学公式消失 inlineElement.innerHTML = html.substr(0, html.length - dataLength); const textNode = document.createTextNode(inputData); inlineElement.after(textNode); range.selectNodeContents(textNode); range.collapse(false); } } } private setEmptyOutline(protyle: IProtyle, element: HTMLElement) { // 图片移除选择状态应放在前面,否则 https://github.com/siyuan-note/siyuan/issues/4173 protyle.wysiwyg.element.querySelectorAll(".img--select, .av__cell--select, .av__row--select").forEach((item: HTMLElement) => { if (item.classList.contains("av__row--select") && !hasClosestByClassName(element, "av")) { item.classList.remove("av__row--select"); item.querySelector(".av__firstcol use").setAttribute("xlink:href", "#iconUncheck"); updateHeader(item); } else { item.classList.remove("img--select", "av__cell--select"); } }); let nodeElement = element; if (!element.getAttribute("data-node-id")) { const tempElement = hasClosestBlock(element); if (!tempElement) { return; } nodeElement = tempElement; } /// #if !MOBILE if (protyle.model) { getAllModels().outline.forEach(item => { if (item.blockId === protyle.block.rootID) { item.setCurrent(nodeElement); } }); } /// #endif } private emojiToMd(element: HTMLElement) { element.querySelectorAll(".emoji").forEach((item: HTMLElement) => { item.outerHTML = `:${item.getAttribute("alt")}:`; }); } private bindCommonEvent(protyle: IProtyle) { this.element.addEventListener("copy", (event: ClipboardEvent & { target: HTMLElement }) => { window.siyuan.ctrlIsPressed = false; // https://github.com/siyuan-note/siyuan/issues/6373 // https://github.com/siyuan-note/siyuan/issues/4600 if (event.target.tagName === "PROTYLE-HTML") { event.stopPropagation(); return; } event.stopPropagation(); event.preventDefault(); const range = getEditorRange(protyle.wysiwyg.element); const nodeElement = hasClosestBlock(range.startContainer); if (!nodeElement) { return; } const selectImgElement = nodeElement.querySelector(".img--select"); let selectElements = Array.from(protyle.wysiwyg.element.querySelectorAll(".protyle-wysiwyg--select")); if (selectElements.length === 0 && range.toString() === "" && !range.cloneContents().querySelector("img") && !selectImgElement) { nodeElement.classList.add("protyle-wysiwyg--select"); countBlockWord([nodeElement.getAttribute("data-node-id")]); selectElements = [nodeElement]; } let html = ""; let textPlain = ""; if (selectElements.length > 0) { const isRefText = selectElements[0].getAttribute("data-reftext") === "true"; if (selectElements[0].getAttribute("data-type") === "NodeListItem" && selectElements[0].parentElement.classList.contains("list") && // 反链复制列表项 https://github.com/siyuan-note/siyuan/issues/6555 selectElements[0].parentElement.childElementCount - 1 === selectElements.length) { if (isRefText) { const cloneElement = selectElements[0].parentElement.cloneNode(true) as HTMLElement; const cloneEditElement = getContenteditableElement(cloneElement); if (cloneEditElement) { cloneEditElement.insertAdjacentHTML("beforeend", ` *`); } html = cloneElement.outerHTML; selectElements[0].removeAttribute("data-reftext"); } else { html = selectElements[0].parentElement.outerHTML; } } else { selectElements.forEach((item, index) => { // 复制列表项中的块会变为复制列表项,因此不能使用 getTopAloneElement https://github.com/siyuan-note/siyuan/issues/8925 if (isRefText && index === 0) { const cloneElement = item.cloneNode(true) as HTMLElement; const cloneEditElement = getContenteditableElement(cloneElement); if (cloneEditElement) { cloneEditElement.insertAdjacentHTML("beforeend", ` *`); } html += removeEmbed(cloneElement); selectElements[0].removeAttribute("data-reftext"); } else { html += removeEmbed(item); } }); } } else { const tempElement = document.createElement("div"); // https://github.com/siyuan-note/siyuan/issues/5540 const selectTypes = protyle.toolbar.getCurrentType(range); if ((selectTypes.length > 0 || range.startContainer.parentElement.parentElement.getAttribute("data-type") === "NodeHeading") && ( (range.startContainer.nodeType === 3 && range.startContainer.parentElement.textContent === range.toString()) || (range.startContainer.nodeType !== 3 && range.startContainer.textContent === range.toString()) )) { if (range.startContainer.parentElement.parentElement.getAttribute("data-type") === "NodeHeading") { // 复制标题 https://github.com/siyuan-note/insider/issues/297 tempElement.append(range.startContainer.parentElement.parentElement.cloneNode(true)); } else if (!["DIV", "TD", "TH", "TR"].includes(range.startContainer.parentElement.tagName)) { // 复制行内元素 https://github.com/siyuan-note/insider/issues/191 tempElement.append(range.startContainer.parentElement.cloneNode(true)); this.emojiToMd(tempElement); } else { // 直接复制块 https://github.com/siyuan-note/insider/issues/318 tempElement.append(range.cloneContents()); this.emojiToMd(tempElement); } html = tempElement.innerHTML; } else if (selectImgElement) { html = selectImgElement.outerHTML; } else if (selectTypes.length > 0 && range.startContainer.nodeType === 3 && range.startContainer.parentElement.tagName === "SPAN" && range.startContainer.parentElement.isSameNode(range.endContainer.parentElement)) { // 复制粗体等字体中的一部分 const attributes = range.startContainer.parentElement.attributes; const spanElement = document.createElement("span"); for (let i = 0; i < attributes.length; i++) { spanElement.setAttribute(attributes[i].name, attributes[i].value); } if (spanElement.getAttribute("data-type").indexOf("block-ref") > -1 && spanElement.getAttribute("data-subtype") === "d") { // 需变为静态锚文本 spanElement.setAttribute("data-subtype", "s"); } spanElement.textContent = range.toString(); html = spanElement.outerHTML; } else { tempElement.append(range.cloneContents()); this.emojiToMd(tempElement); const inlineMathElement = hasClosestByAttribute(range.commonAncestorContainer, "data-type", "inline-math"); if (inlineMathElement) { // 表格内复制数学公式 https://ld246.com/article/1631708573504 html = inlineMathElement.outerHTML; } else { html = tempElement.innerHTML; } // 不能使用 commonAncestorContainer https://ld246.com/article/1643282894693 if (hasClosestByAttribute(range.startContainer, "data-type", "NodeCodeBlock") || hasClosestByMatchTag(range.startContainer, "CODE")) { textPlain = tempElement.textContent.replace(Constants.ZWSP, ""); } } } if (protyle.disabled) { html = getEnableHTML(html); } textPlain = textPlain || protyle.lute.BlockDOM2StdMd(html).trimEnd(); textPlain = textPlain.replace(/\u00A0/g, " "); // Replace non-breaking spaces with normal spaces when copying https://github.com/siyuan-note/siyuan/issues/9382 event.clipboardData.setData("text/plain", textPlain); event.clipboardData.setData("text/html", protyle.lute.BlockDOM2HTML(html)); event.clipboardData.setData("text/siyuan", html); }); this.element.addEventListener("mousedown", (event: MouseEvent) => { if (event.button === 2 || window.siyuan.ctrlIsPressed) { // 右键 return; } // Electron 更新后 shift 向上点击获取的 range 不为上一个位置的 https://github.com/siyuan-note/siyuan/issues/9334 if (window.siyuan.shiftIsPressed && getSelection().rangeCount > 0) { this.shiftStartElement = hasClosestBlock(getSelection().getRangeAt(0).startContainer) as HTMLElement; } if (!window.siyuan.shiftIsPressed) { // https://github.com/siyuan-note/siyuan/issues/3026 hideElements(["select"], protyle); } const target = event.target as HTMLElement; if (hasClosestByClassName(target, "protyle-action") || hasClosestByClassName(target, "av__gutters") || hasClosestByClassName(target, "av__cellheader")) { return; } const documentSelf = document; const rect = protyle.element.getBoundingClientRect(); const mostLeft = rect.left + (parseInt(protyle.wysiwyg.element.style.paddingLeft) || 24) + 1; // 不能用 firstElement,否则 https://ld246.com/article/1668758661338 const mostRight = mostLeft + (protyle.wysiwyg.element.clientWidth - (parseInt(protyle.wysiwyg.element.style.paddingLeft) || 24) - (parseInt(protyle.wysiwyg.element.style.paddingRight) || 16)) - 2; const mostBottom = rect.bottom; const y = event.clientY; // av col resize if (!protyle.disabled && target.classList.contains("av__widthdrag")) { const nodeElement = hasClosestBlock(target); if (!nodeElement) { return; } const avId = nodeElement.getAttribute("data-av-id"); const dragElement = target.parentElement; const oldWidth = dragElement.clientWidth; const dragColId = dragElement.getAttribute("data-col-id"); let newWidth: string; const scrollElement = nodeElement.querySelector(".av__scroll"); const contentRect = protyle.contentElement.getBoundingClientRect(); documentSelf.onmousemove = (moveEvent: MouseEvent) => { newWidth = Math.max(oldWidth + (moveEvent.clientX - event.clientX), 25) + "px"; scrollElement.querySelectorAll(".av__row, .av__row--footer").forEach(item => { (item.querySelector(`[data-col-id="${dragColId}"]`) as HTMLElement).style.width = newWidth; }); stickyRow(nodeElement, contentRect, "bottom"); }; documentSelf.onmouseup = () => { documentSelf.onmousemove = null; documentSelf.onmouseup = null; documentSelf.ondragstart = null; documentSelf.onselectstart = null; documentSelf.onselect = null; if (!newWidth || newWidth === oldWidth + "px") { return; } transaction(protyle, [{ action: "setAttrViewColWidth", id: dragColId, avID: avId, data: newWidth }], [{ action: "setAttrViewColWidth", id: dragColId, avID: avId, data: oldWidth + "px" }]); }; event.stopPropagation(); event.preventDefault(); return; } // 图片、iframe、video 缩放 if (!protyle.disabled && target.classList.contains("protyle-action__drag")) { const nodeElement = hasClosestBlock(target); if (!nodeElement) { return; } let isCenter = true; if (["NodeIFrame", "NodeWidget", "NodeVideo"].includes(nodeElement.getAttribute("data-type"))) { nodeElement.classList.add("iframe--drag"); if (nodeElement.style.textAlign === "left" || nodeElement.style.textAlign === "right") { isCenter = false; } } else if (target.parentElement.parentElement.getAttribute("data-type") === "img") { target.parentElement.parentElement.classList.add("img--drag"); } const id = nodeElement.getAttribute("data-node-id"); const html = nodeElement.outerHTML; const x = event.clientX; const dragElement = target.previousElementSibling as HTMLElement; const dragWidth = dragElement.clientWidth; const dragHeight = dragElement.clientHeight; documentSelf.onmousemove = (moveEvent: MouseEvent) => { if (dragElement.tagName === "IMG") { dragElement.parentElement.parentElement.style.width = ""; } if (moveEvent.clientX > x - dragWidth + 8 && moveEvent.clientX < mostRight) { if ((dragElement.tagName === "IMG" && dragElement.parentElement.parentElement.style.display !== "block") || !isCenter) { dragElement.style.width = Math.max(17, dragWidth + (moveEvent.clientX - x)) + "px"; } else { dragElement.style.width = Math.max(17, dragWidth + (moveEvent.clientX - x) * 2) + "px"; } } if (dragElement.tagName !== "IMG") { if (moveEvent.clientY > y - dragHeight + 8 && moveEvent.clientY < mostBottom) { dragElement.style.height = (dragHeight + (moveEvent.clientY - y)) + "px"; } } else { dragElement.parentElement.parentElement.style.width = (parseInt(dragElement.style.width) + 10) + "px"; } }; documentSelf.onmouseup = () => { documentSelf.onmousemove = null; documentSelf.onmouseup = null; documentSelf.ondragstart = null; documentSelf.onselectstart = null; documentSelf.onselect = null; if (target.classList.contains("protyle-action__drag") && nodeElement) { updateTransaction(protyle, id, nodeElement.outerHTML, html); } nodeElement.classList.remove("iframe--drag"); target.parentElement.parentElement.classList.remove("img--drag"); }; return; } // table cell select let tableBlockElement: HTMLElement | false; if (target.tagName === "TH" || target.tagName === "TD" || target.firstElementChild?.tagName === "TABLE" || target.classList.contains("table__resize") || target.classList.contains("table__select")) { tableBlockElement = hasClosestBlock(target); if (tableBlockElement) { tableBlockElement.querySelector(".table__select").removeAttribute("style"); window.siyuan.menus.menu.remove(); event.stopPropagation(); } // 后续拖拽操作写在多选节点中 } // table col resize if (!protyle.disabled && target.classList.contains("table__resize")) { const nodeElement = hasClosestBlock(target); if (!nodeElement) { return; } const html = nodeElement.outerHTML; // https://github.com/siyuan-note/siyuan/issues/4455 if (getSelection().rangeCount > 0) { getSelection().getRangeAt(0).collapse(false); } // @ts-ignore nodeElement.firstElementChild.style.webkitUserModify = "read-only"; nodeElement.style.cursor = "col-resize"; target.removeAttribute("style"); const id = nodeElement.getAttribute("data-node-id"); const x = event.clientX; const colIndex = parseInt(target.getAttribute("data-col-index")); const colElement = nodeElement.querySelectorAll("table col")[colIndex] as HTMLElement; // 清空初始化 table 时的最小宽度 if (colElement.style.minWidth) { colElement.style.width = (nodeElement.querySelectorAll("table td, table th")[colIndex] as HTMLElement).offsetWidth + "px"; colElement.style.minWidth = ""; } // 移除 cell 上的宽度限制 https://github.com/siyuan-note/siyuan/issues/7795 nodeElement.querySelectorAll("tr").forEach((trItem: HTMLTableRowElement) => { trItem.cells[colIndex].style.width = ""; }); const oldWidth = colElement.clientWidth; const hasScroll = nodeElement.firstElementChild.clientWidth < nodeElement.firstElementChild.scrollWidth; documentSelf.onmousemove = (moveEvent: MouseEvent) => { if (nodeElement.style.textAlign === "center" && !hasScroll) { colElement.style.width = (oldWidth + (moveEvent.clientX - x) * 2) + "px"; } else { colElement.style.width = (oldWidth + (moveEvent.clientX - x)) + "px"; } }; documentSelf.onmouseup = () => { // @ts-ignore nodeElement.firstElementChild.style.webkitUserModify = ""; nodeElement.style.cursor = ""; documentSelf.onmousemove = null; documentSelf.onmouseup = null; documentSelf.ondragstart = null; documentSelf.onselectstart = null; documentSelf.onselect = null; if (nodeElement) { updateTransaction(protyle, id, nodeElement.outerHTML, html); } }; return; } // https://ld246.com/article/1681778773806 if (["IMG", "VIDEO", "AUDIO"].includes(target.tagName)) { return; } // 多选节点 let x = event.clientX; if (event.clientX > mostRight) { x = mostRight; } else if (event.clientX < mostLeft) { x = mostLeft; } const mostTop = rect.top + (protyle.options.render.breadcrumb ? protyle.breadcrumb.element.parentElement.clientHeight : 0); let mouseElement: Element; let moveCellElement: HTMLElement; let startFirstElement: Element; let endLastElement: Element; documentSelf.onmousemove = (moveEvent: MouseEvent) => { const moveTarget = moveEvent.target as HTMLElement; // table cell select if (!protyle.disabled && tableBlockElement && tableBlockElement.contains(moveTarget) && !hasClosestByClassName(tableBlockElement, "protyle-wysiwyg__embed")) { if ((moveTarget.tagName === "TH" || moveTarget.tagName === "TD") && !moveTarget.isSameNode(target) && (!moveCellElement || !moveCellElement.isSameNode(moveTarget))) { // @ts-ignore tableBlockElement.firstElementChild.style.webkitUserModify = "read-only"; let width = target.offsetLeft + target.clientWidth - moveTarget.offsetLeft; let left = moveTarget.offsetLeft; if (target.offsetLeft === moveTarget.offsetLeft) { width = Math.max(target.clientWidth, moveTarget.clientWidth); } else if (target.offsetLeft < moveTarget.offsetLeft) { width = moveTarget.offsetLeft + moveTarget.clientWidth - target.offsetLeft; left = target.offsetLeft; } let height = target.offsetTop + target.clientHeight - moveTarget.offsetTop; let top = moveTarget.offsetTop; if (target.offsetTop === moveTarget.offsetTop) { height = Math.max(target.clientHeight, moveTarget.clientHeight); } else if (target.offsetTop < moveTarget.offsetTop) { height = moveTarget.offsetTop + moveTarget.clientHeight - target.offsetTop; top = target.offsetTop; } // https://github.com/siyuan-note/insider/issues/1015 Array.from(tableBlockElement.querySelectorAll("th, td")).find((item: HTMLElement) => { const updateWidth = item.offsetLeft < left + width && item.offsetLeft + item.clientWidth > left + width; const updateWidth2 = item.offsetLeft < left && item.offsetLeft + item.clientWidth > left; if (item.offsetTop < top && item.offsetTop + item.clientHeight > top) { if ((item.offsetLeft + 6 > left && item.offsetLeft + item.clientWidth - 6 < left + width) || updateWidth || updateWidth2) { height = top + height - item.offsetTop; top = item.offsetTop; } if (updateWidth) { width = item.offsetLeft + item.clientWidth - left; } if (updateWidth2) { width = left + width - item.offsetLeft; left = item.offsetLeft; } } else if (item.offsetTop < top + height && item.offsetTop + item.clientHeight > top + height) { if ((item.offsetLeft + 6 > left && item.offsetLeft + item.clientWidth - 6 < left + width) || updateWidth || updateWidth2) { height = item.clientHeight + item.offsetTop - top; } if (updateWidth) { width = item.offsetLeft + item.clientWidth - left; } if (updateWidth2) { width = left + width - item.offsetLeft; left = item.offsetLeft; } } else if (updateWidth2 && item.offsetTop + 6 > top && item.offsetTop + item.clientHeight - 6 < top + height) { width = left + width - item.offsetLeft; left = item.offsetLeft; } else if (updateWidth && item.offsetTop + 6 > top && item.offsetTop + item.clientHeight - 6 < top + height) { width = item.offsetLeft + item.clientWidth - left; } }); tableBlockElement.querySelector(".table__select").setAttribute("style", `left:${left - tableBlockElement.firstElementChild.scrollLeft}px;top:${top}px;height:${height}px;width:${width + 1}px;`); moveCellElement = moveTarget; } return; } protyle.selectElement.classList.remove("fn__none"); // 向左选择,遇到 gutter 就不会弹出 toolbar hideElements(["gutter"], protyle); let newTop = 0; let newLeft = 0; let newWidth = 0; let newHeight = 0; if (moveEvent.clientX < x) { if (moveEvent.clientX < mostLeft) { // 向左越界 newLeft = mostLeft; } else { // 向左 newLeft = moveEvent.clientX; } newWidth = x - newLeft; } else { if (moveEvent.clientX > mostRight) { // 向右越界 newLeft = x; newWidth = mostRight - newLeft; } else { // 向右 newLeft = x; newWidth = moveEvent.clientX - x; } } if (moveEvent.clientY > y) { if (moveEvent.clientY > mostBottom) { // 向下越界 newTop = y; newHeight = mostBottom - y; } else { // 向下 newTop = y; newHeight = moveEvent.clientY - y; } } else { if (moveEvent.clientY < mostTop) { // 向上越界 newTop = mostTop; } else { // 向上 newTop = moveEvent.clientY; } newHeight = y - newTop; } if (newHeight < 4) { return; } protyle.selectElement.setAttribute("style", `background-color: ${protyle.selectElement.style.backgroundColor};top:${newTop}px;height:${newHeight}px;left:${newLeft + 2}px;width:${newWidth - 2}px;`); const newMouseElement = document.elementFromPoint(moveEvent.clientX, moveEvent.clientY); if (mouseElement && mouseElement.isSameNode(newMouseElement) && !mouseElement.classList.contains("protyle-wysiwyg") && !mouseElement.classList.contains("list") && !mouseElement.classList.contains("bq") && !mouseElement.classList.contains("sb")) { // 性能优化,同一个p元素不进行选中计算 return; } else { mouseElement = newMouseElement; } hideElements(["select"], protyle); let firstElement; if (moveEvent.clientY > y) { firstElement = startFirstElement || document.elementFromPoint(newLeft, newTop); endLastElement = undefined; } else { firstElement = document.elementFromPoint(newLeft, newTop); startFirstElement = undefined; } if (!firstElement) { return; } if (firstElement.classList.contains("protyle-wysiwyg") || firstElement.classList.contains("list") || firstElement.classList.contains("sb") || firstElement.classList.contains("bq")) { firstElement = document.elementFromPoint(newLeft, newTop + 16); } if (!firstElement) { return; } let firstBlockElement = hasClosestBlock(firstElement); if (moveEvent.clientY > y) { if (!startFirstElement) { startFirstElement = firstElement; } } else if (!firstBlockElement && // https://github.com/siyuan-note/siyuan/issues/7580 moveEvent.clientY < protyle.wysiwyg.element.lastElementChild.getBoundingClientRect().bottom) { firstBlockElement = protyle.wysiwyg.element.firstElementChild as HTMLElement; } let selectElements: Element[] = []; let currentElement: Element | boolean = firstBlockElement; let hasJump = false; const selectBottom = endLastElement ? endLastElement.getBoundingClientRect().bottom : (newTop + newHeight); while (currentElement) { if (currentElement && !currentElement.classList.contains("protyle-attr")) { const currentRect = currentElement.getBoundingClientRect(); if (currentRect.height > 0 && currentRect.top < selectBottom && currentRect.left < newLeft + newWidth) { if (hasJump) { // 父节点的下个节点在选中范围内才可使用父节点作为选中节点 if (currentElement.nextElementSibling && !currentElement.nextElementSibling.classList.contains("protyle-attr")) { const nextRect = currentElement.nextElementSibling.getBoundingClientRect(); if (nextRect.top < selectBottom && nextRect.left < newLeft + newWidth) { selectElements = [currentElement]; currentElement = currentElement.nextElementSibling; hasJump = false; } else if (currentElement.parentElement.classList.contains("sb")) { currentElement = hasClosestBlock(currentElement.parentElement); hasJump = true; } else { break; } } else { currentElement = hasClosestBlock(currentElement.parentElement); hasJump = true; } } else { selectElements.push(currentElement); currentElement = currentElement.nextElementSibling; } } else if (currentElement.parentElement.classList.contains("sb")) { // 跳出超级块横向排版中的未选中元素 currentElement = hasClosestBlock(currentElement.parentElement); hasJump = true; } else if (currentRect.height === 0 && currentRect.width === 0 && currentElement.parentElement.getAttribute("fold") === "1") { currentElement = currentElement.parentElement; selectElements = []; } else { break; } } else { currentElement = hasClosestBlock(currentElement.parentElement); hasJump = true; } } if (moveEvent.clientY <= y && !endLastElement) { endLastElement = selectElements[selectElements.length - 1]; } if (selectElements.length === 1 && !selectElements[0].classList.contains("list") && !selectElements[0].classList.contains("bq") && !selectElements[0].classList.contains("sb")) { // 只有一个 p 时不选中 protyle.selectElement.style.backgroundColor = "transparent"; } else { selectElements.forEach(item => { if (!hasClosestByClassName(item, "protyle-wysiwyg__embed")) { item.classList.add("protyle-wysiwyg--select"); } }); protyle.selectElement.style.backgroundColor = ""; } }; documentSelf.onmouseup = (mouseUpEvent) => { documentSelf.onmousemove = null; documentSelf.onmouseup = null; documentSelf.ondragstart = null; documentSelf.onselectstart = null; documentSelf.onselect = null; startFirstElement = undefined; endLastElement = undefined; protyle.selectElement.classList.add("fn__none"); protyle.selectElement.removeAttribute("style"); if (!protyle.disabled && tableBlockElement) { // @ts-ignore tableBlockElement.firstElementChild.style.webkitUserModify = ""; const tableSelectElement = tableBlockElement.querySelector(".table__select") as HTMLElement; if (tableSelectElement.getAttribute("style")) { if (getSelection().rangeCount > 0) { getSelection().getRangeAt(0).collapse(false); } window.siyuan.menus.menu.remove(); window.siyuan.menus.menu.append(new MenuItem({ label: window.siyuan.languages.mergeCell, click: () => { if (tableBlockElement) { const selectCellElements: HTMLTableCellElement[] = []; const colIndexList: number[] = []; const colCount = tableBlockElement.querySelectorAll("th").length; let fnNoneMax = 0; const scrollLeft = tableBlockElement.firstElementChild.scrollLeft; let isTHead = false; let isTBody = false; tableBlockElement.querySelectorAll("th, td").forEach((item: HTMLTableCellElement, index: number) => { if (item.classList.contains("fn__none")) { // 合并的元素中间有 fn__none 的元素 if (item.previousElementSibling && item.previousElementSibling.isSameNode(selectCellElements[selectCellElements.length - 1])) { selectCellElements.push(item); if (!isTHead && item.parentElement.parentElement.tagName === "THEAD") { isTHead = true; } else if (!isTBody && item.parentElement.parentElement.tagName === "TBODY") { isTBody = true; } } else { if (index < fnNoneMax && colIndexList.includes((index + 1) % colCount)) { selectCellElements.push(item); if (!isTHead && item.parentElement.parentElement.tagName === "THEAD") { isTHead = true; } else if (!isTBody && item.parentElement.parentElement.tagName === "TBODY") { isTBody = true; } } } } else { if (item.offsetLeft + 6 > tableSelectElement.offsetLeft + scrollLeft && item.offsetLeft + item.clientWidth - 6 < tableSelectElement.offsetLeft + scrollLeft + tableSelectElement.clientWidth && item.offsetTop + 6 > tableSelectElement.offsetTop && item.offsetTop + item.clientHeight - 6 < tableSelectElement.offsetTop + tableSelectElement.clientHeight) { selectCellElements.push(item); if (!isTHead && item.parentElement.parentElement.tagName === "THEAD") { isTHead = true; } else if (!isTBody && item.parentElement.parentElement.tagName === "TBODY") { isTBody = true; } colIndexList.push((index + 1) % colCount); // https://github.com/siyuan-note/insider/issues/1014 fnNoneMax = Math.max((item.rowSpan - 1) * colCount + index + 1, fnNoneMax); } } }); tableSelectElement.removeAttribute("style"); const oldHTML = tableBlockElement.outerHTML; let cellElement = selectCellElements[0]; let colSpan = cellElement.colSpan; let index = 1; while (cellElement.nextElementSibling && cellElement.nextElementSibling.isSameNode(selectCellElements[index])) { cellElement = cellElement.nextElementSibling as HTMLTableCellElement; if (!cellElement.classList.contains("fn__none")) { // https://github.com/siyuan-note/insider/issues/1007#issuecomment-1046195608 colSpan += cellElement.colSpan; } index++; } let html = ""; let rowElement: Element = selectCellElements[0].parentElement; let rowSpan = selectCellElements[0].rowSpan; selectCellElements.forEach((item, index) => { let cellHTML = item.innerHTML.trim(); if (cellHTML.endsWith("
")) { cellHTML = cellHTML.substr(0, cellHTML.length - 4); } html += cellHTML + ((!cellHTML || index === selectCellElements.length - 1) ? "" : "
"); if (index !== 0) { if (!rowElement.isSameNode(item.parentElement)) { if (!item.classList.contains("fn__none")) { // https://github.com/siyuan-note/insider/issues/1011 rowSpan += item.rowSpan; } rowElement = item.parentElement; if (selectCellElements[0].parentElement.parentElement.tagName === "THEAD" && item.parentElement.parentElement.tagName !== "THEAD") { selectCellElements[0].parentElement.parentElement.insertAdjacentElement("beforeend", item.parentElement); } } item.classList.add("fn__none"); item.innerHTML = ""; } }); // https://github.com/siyuan-note/insider/issues/1017 if (isTHead && isTBody) { rowElement = rowElement.parentElement.nextElementSibling.firstElementChild; while (rowElement && rowElement.parentElement.tagName !== "THEAD") { let colSpanCount = 0; let noneCount = 0; Array.from(rowElement.children).forEach((item: HTMLTableCellElement) => { colSpanCount += item.colSpan - 1; if (item.classList.contains("fn__none")) { noneCount++; } }); if (colSpanCount !== noneCount) { selectCellElements[0].parentElement.parentElement.insertAdjacentElement("beforeend", rowElement); rowElement = rowElement.parentElement.nextElementSibling.firstElementChild; } else { break; } } } // 合并背景色不会修改,需要等计算完毕 setTimeout(() => { if (tableBlockElement) { selectCellElements[0].innerHTML = html + ""; selectCellElements[0].colSpan = colSpan; selectCellElements[0].rowSpan = rowSpan; focusByWbr(selectCellElements[0], document.createRange()); updateTransaction(protyle, tableBlockElement.getAttribute("data-node-id"), tableBlockElement.outerHTML, oldHTML); } }); } } }).element); window.siyuan.menus.menu.append(new MenuItem({type: "separator"}).element); window.siyuan.menus.menu.append(new MenuItem({ icon: "iconAlignLeft", accelerator: window.siyuan.config.keymap.editor.general.alignLeft.custom, label: window.siyuan.languages.alignLeft, click: () => { if (tableBlockElement) { const selectCellElements: HTMLTableCellElement[] = []; const scrollLeft = tableBlockElement.firstElementChild.scrollLeft; tableBlockElement.querySelectorAll("th, td").forEach((item: HTMLTableCellElement) => { if (!item.classList.contains("fn__none") && item.offsetLeft + 6 > tableSelectElement.offsetLeft + scrollLeft && item.offsetLeft + item.clientWidth - 6 < tableSelectElement.offsetLeft + scrollLeft + tableSelectElement.clientWidth && item.offsetTop + 6 > tableSelectElement.offsetTop && item.offsetTop + item.clientHeight - 6 < tableSelectElement.offsetTop + tableSelectElement.clientHeight && (selectCellElements.length === 0 || (selectCellElements.length > 0 && item.offsetTop === selectCellElements[0].offsetTop))) { selectCellElements.push(item); } }); tableSelectElement.removeAttribute("style"); setTableAlign(protyle, selectCellElements, tableBlockElement, "left", getEditorRange(tableBlockElement)); } } }).element); window.siyuan.menus.menu.append(new MenuItem({ icon: "iconAlignCenter", accelerator: window.siyuan.config.keymap.editor.general.alignCenter.custom, label: window.siyuan.languages.alignCenter, click: () => { if (tableBlockElement) { const selectCellElements: HTMLTableCellElement[] = []; const scrollLeft = tableBlockElement.firstElementChild.scrollLeft; tableBlockElement.querySelectorAll("th, td").forEach((item: HTMLTableCellElement) => { if (!item.classList.contains("fn__none") && item.offsetLeft + 6 > tableSelectElement.offsetLeft + scrollLeft && item.offsetLeft + item.clientWidth - 6 < tableSelectElement.offsetLeft + scrollLeft + tableSelectElement.clientWidth && item.offsetTop + 6 > tableSelectElement.offsetTop && item.offsetTop + item.clientHeight - 6 < tableSelectElement.offsetTop + tableSelectElement.clientHeight && (selectCellElements.length === 0 || (selectCellElements.length > 0 && item.offsetTop === selectCellElements[0].offsetTop))) { selectCellElements.push(item); } }); tableSelectElement.removeAttribute("style"); setTableAlign(protyle, selectCellElements, tableBlockElement, "center", getEditorRange(tableBlockElement)); } } }).element); window.siyuan.menus.menu.append(new MenuItem({ icon: "iconAlignRight", accelerator: window.siyuan.config.keymap.editor.general.alignRight.custom, label: window.siyuan.languages.alignRight, click: () => { if (tableBlockElement) { const selectCellElements: HTMLTableCellElement[] = []; const scrollLeft = tableBlockElement.firstElementChild.scrollLeft; tableBlockElement.querySelectorAll("th, td").forEach((item: HTMLTableCellElement) => { if (!item.classList.contains("fn__none") && item.offsetLeft + 6 > tableSelectElement.offsetLeft + scrollLeft && item.offsetLeft + item.clientWidth - 6 < tableSelectElement.offsetLeft + scrollLeft + tableSelectElement.clientWidth && item.offsetTop + 6 > tableSelectElement.offsetTop && item.offsetTop + item.clientHeight - 6 < tableSelectElement.offsetTop + tableSelectElement.clientHeight && (selectCellElements.length === 0 || (selectCellElements.length > 0 && item.offsetTop === selectCellElements[0].offsetTop))) { selectCellElements.push(item); } }); tableSelectElement.removeAttribute("style"); setTableAlign(protyle, selectCellElements, tableBlockElement, "right", getEditorRange(tableBlockElement)); } } }).element); window.siyuan.menus.menu.append(new MenuItem({type: "separator"}).element); window.siyuan.menus.menu.append(new MenuItem({ label: window.siyuan.languages.clear, icon: "iconTrashcan", click() { if (tableBlockElement) { const selectCellElements: HTMLTableCellElement[] = []; const scrollLeft = tableBlockElement.firstElementChild.scrollLeft; tableBlockElement.querySelectorAll("th, td").forEach((item: HTMLTableCellElement) => { if (!item.classList.contains("fn__none") && item.offsetLeft + 6 > tableSelectElement.offsetLeft + scrollLeft && item.offsetLeft + item.clientWidth - 6 < tableSelectElement.offsetLeft + scrollLeft + tableSelectElement.clientWidth && item.offsetTop + 6 > tableSelectElement.offsetTop && item.offsetTop + item.clientHeight - 6 < tableSelectElement.offsetTop + tableSelectElement.clientHeight) { selectCellElements.push(item); } }); tableSelectElement.removeAttribute("style"); const oldHTML = tableBlockElement.outerHTML; selectCellElements.forEach(item => { item.innerHTML = ""; }); updateTransaction(protyle, tableBlockElement.getAttribute("data-node-id"), tableBlockElement.outerHTML, oldHTML); } } }).element); window.siyuan.menus.menu.popup({x: mouseUpEvent.clientX - 8, y: mouseUpEvent.clientY - 16}); } } const ids: string[] = []; const selectElement = protyle.wysiwyg.element.querySelectorAll(".protyle-wysiwyg--select"); selectElement.forEach(item => { ids.push(item.getAttribute("data-node-id")); }); countBlockWord(ids); // 划选后不能存在跨块的 range https://github.com/siyuan-note/siyuan/issues/4473 if (getSelection().rangeCount > 0) { const range = getSelection().getRangeAt(0); if (range.toString() === "" || window.siyuan.shiftIsPressed // https://ld246.com/article/1650096678723 ) { if (event.detail === 3) { // table 前或最后一个 cell 三击状态不对 let cursorElement = hasClosestBlock(range.startContainer) as Element; if (cursorElement) { if (cursorElement.nextElementSibling?.classList.contains("table")) { setLastNodeRange(getContenteditableElement(cursorElement), range, false); } else if (cursorElement.classList.contains("table")) { const cellElements = cursorElement.querySelectorAll("th, td"); cursorElement = cellElements[cellElements.length - 1]; if (cursorElement.contains(range.startContainer)) { setLastNodeRange(cursorElement, range, false); } } } return; } } if (selectElement.length > 0) { range.collapse(true); return; } const startBlockElement = hasClosestBlock(range.startContainer); let endBlockElement: false | HTMLElement; if (mouseUpEvent.detail === 3 && range.endContainer.nodeType !== 3 && (range.endContainer as HTMLElement).tagName === "DIV" && range.endOffset === 0) { // 三击选中段落块时,rangeEnd 会在下一个块 if ((range.endContainer as HTMLElement).classList.contains("protyle-attr") && startBlockElement) { // 三击在悬浮层中会选择到 attr https://github.com/siyuan-note/siyuan/issues/4636 // 需要获取可编辑元素,使用 previousElementSibling 的话会 https://github.com/siyuan-note/siyuan/issues/9714 setLastNodeRange(getContenteditableElement(startBlockElement), range, false); } } else { endBlockElement = hasClosestBlock(range.endContainer); } if (startBlockElement && endBlockElement && !endBlockElement.isSameNode(startBlockElement)) { range.collapse(true); } } }; }); } private bindEvent(protyle: IProtyle) { this.element.addEventListener("focusout", () => { if (getSelection().rangeCount === 0) { return; } const range = getSelection().getRangeAt(0); if (this.element.isSameNode(range.startContainer) || this.element.contains(range.startContainer)) { protyle.toolbar.range = range; } }); this.element.addEventListener("cut", (event: ClipboardEvent & { target: HTMLElement }) => { window.siyuan.ctrlIsPressed = false; // https://github.com/siyuan-note/siyuan/issues/6373 if (protyle.disabled) { return; } if (event.target.tagName === "PROTYLE-HTML") { event.stopPropagation(); return; } if (protyle.options.render.breadcrumb) { protyle.breadcrumb.hide(); } const range = getEditorRange(protyle.wysiwyg.element); let nodeElement = hasClosestBlock(range.startContainer); if (!nodeElement) { event.stopPropagation(); event.preventDefault(); return; } if (nodeElement.classList.contains("av")) { updateAVName(protyle, nodeElement); event.stopPropagation(); return; } event.stopPropagation(); event.preventDefault(); const selectImgElement = nodeElement.querySelector(".img--select"); let selectElements = Array.from(protyle.wysiwyg.element.querySelectorAll(".protyle-wysiwyg--select")); if (selectElements.length === 0 && range.toString() === "" && !range.cloneContents().querySelector("img") && !selectImgElement) { nodeElement.classList.add("protyle-wysiwyg--select"); selectElements = [nodeElement]; } let html = ""; if (selectElements.length > 0) { if (selectElements[0].getAttribute("data-type") === "NodeListItem" && selectElements[0].parentElement.classList.contains("list") && // 反链复制列表项 https://github.com/siyuan-note/siyuan/issues/6555 selectElements[0].parentElement.childElementCount - 1 === selectElements.length) { html = selectElements[0].parentElement.outerHTML; } else { selectElements.forEach(item => { const topElement = getTopAloneElement(item); if (item.getAttribute("data-type") === "NodeHeading" && item.getAttribute("fold") === "1") { html += removeEmbed(topElement).replace('fold="1"', ""); } else { html += removeEmbed(topElement); } }); if (selectElements[0].getAttribute("data-type") === "NodeListItem") { html = `
${html}
${Constants.ZWSP}
`; } } const nextElement = getNextBlock(selectElements[selectElements.length - 1]); removeBlock(protyle, nodeElement, range); if (nextElement) { // Ctrl+X 剪切后光标应跳到下一行行首 https://github.com/siyuan-note/siyuan/issues/5485 focusBlock(nextElement); } } else { const id = nodeElement.getAttribute("data-node-id"); const oldHTML = nodeElement.outerHTML; const tempElement = document.createElement("div"); // 首次选中标题时,range.startContainer 会为空 let startContainer = range.startContainer; if (startContainer.nodeType === 3 && startContainer.textContent === "") { const nextSibling = hasNextSibling(range.startContainer); if (nextSibling) { startContainer = nextSibling; } } // 选中整个标题 https://github.com/siyuan-note/siyuan/issues/4329 const headElement = hasClosestByAttribute(startContainer, "data-type", "NodeHeading"); let isFoldHeading = false; if (headElement && range.toString() === headElement.firstElementChild.textContent) { const doOperations: IOperation[] = [{ action: "delete", id: headElement.getAttribute("data-node-id") }]; const undoOperations: IOperation[] = [{ action: "insert", id: headElement.getAttribute("data-node-id"), data: headElement.outerHTML, previousID: headElement.previousElementSibling?.getAttribute("data-node-id"), parentID: headElement.parentElement?.getAttribute("data-node-id") || protyle.block.parentID }]; if (headElement.getAttribute("fold") === "1") { isFoldHeading = true; const headCloneElement = headElement.cloneNode(true) as HTMLElement; headCloneElement.removeAttribute("fold"); tempElement.append(headCloneElement); undoOperations[0].data = headCloneElement.outerHTML; setFold(protyle, headElement, undefined, true); } else { if ((headElement.parentElement.childElementCount === 3 && headElement.parentElement.classList.contains("li")) || (headElement.parentElement.childElementCount === 2 && (headElement.parentElement.classList.contains("bq") || headElement.parentElement.classList.contains("sb"))) || (headElement.parentElement.childElementCount === 1 && headElement.parentElement.classList.contains("protyle-wysiwyg")) // 全选剪切标题 ) { // https://github.com/siyuan-note/siyuan/issues/4040 const emptyId = Lute.NewNodeID(); const emptyElement = genEmptyElement(false, false, emptyId); doOperations.push({ id: emptyId, data: emptyElement.outerHTML, action: "insert", parentID: headElement.parentElement.getAttribute("data-node-id") || protyle.block.parentID }); undoOperations.push({ id: emptyId, action: "delete", }); headElement.before(emptyElement); } focusSideBlock(headElement); tempElement.append(headElement); } transaction(protyle, doOperations, undoOperations); } else if (range.toString() !== "" && startContainer.isSameNode(range.endContainer) && range.startContainer.nodeType === 3 && range.endOffset === range.endContainer.textContent.length && range.startOffset === 0 && !["DIV", "TD", "TH", "TR"].includes(range.startContainer.parentElement.tagName)) { // 选中整个内联元素 tempElement.append(range.startContainer.parentElement); } else if (selectImgElement) { tempElement.append(selectImgElement); } else if (range.startContainer.nodeType === 3 && range.startContainer.parentElement.tagName === "SPAN" && range.startContainer.parentElement.getAttribute("data-type") && range.startContainer.parentElement.isSameNode(range.endContainer.parentElement)) { // 剪切粗体等字体中的一部分 const spanElement = range.startContainer.parentElement; const attributes = spanElement.attributes; const newSpanElement = document.createElement("span"); for (let i = 0; i < attributes.length; i++) { newSpanElement.setAttribute(attributes[i].name, attributes[i].value); } if (spanElement.getAttribute("data-type").indexOf("block-ref") > -1 && spanElement.getAttribute("data-subtype") === "d") { // 引用被剪切后需变为静态锚文本 newSpanElement.setAttribute("data-subtype", "s"); spanElement.setAttribute("data-subtype", "s"); } newSpanElement.textContent = range.toString(); range.deleteContents(); tempElement.append(newSpanElement); } else { if (range.cloneContents().querySelectorAll("td, th").length > 0) { // 表格内多格子 cut https://github.com/siyuan-note/insider/issues/564 const wbrElement = document.createElement("wbr"); range.insertNode(wbrElement); range.setStartAfter(wbrElement); tempElement.append(range.extractContents()); nodeElement.outerHTML = protyle.lute.SpinBlockDOM(nodeElement.outerHTML); nodeElement = protyle.wysiwyg.element.querySelector(`[data-node-id="${id}"]`) as HTMLElement; focusByWbr(nodeElement, range); } else { const inlineMathElement = hasClosestByAttribute(range.commonAncestorContainer, "data-type", "inline-math"); if (inlineMathElement) { // 表格内剪切数学公式 https://ld246.com/article/1631708573504 tempElement.append(inlineMathElement); } else { tempElement.append(range.extractContents()); let parentElement: Element | false; // https://ld246.com/article/1647689760545 if (nodeElement.classList.contains("table")) { parentElement = hasClosestByMatchTag(range.startContainer, "TD") || hasClosestByMatchTag(range.startContainer, "TH"); } else { parentElement = getContenteditableElement(nodeElement); } if (parentElement) { // 引用文本剪切 https://ld246.com/article/1647689760545 // 表格多行剪切 https://ld246.com/article/1652603836350 // 自定义表情的段落剪切后表情丢失 https://ld246.com/article/1668781478724 Array.from(parentElement.children).forEach(item => { if (item.textContent === "" && (item.nodeType === 1 && !["BR", "IMG"].includes(item.tagName))) { item.remove(); } }); } } } } this.emojiToMd(tempElement); html = tempElement.innerHTML; // https://github.com/siyuan-note/siyuan/issues/4321 if (!nodeElement.classList.contains("table")) { const editableElement = getContenteditableElement(nodeElement); if (editableElement && editableElement.textContent === "") { editableElement.innerHTML = ""; } } nodeElement.setAttribute("updated", dayjs().format("YYYYMMDDHHmmss")); if (nodeElement.getAttribute("data-type") === "NodeCodeBlock") { range.insertNode(document.createElement("wbr")); getContenteditableElement(nodeElement).removeAttribute("data-render"); highlightRender(nodeElement); } if (nodeElement.parentElement.parentElement && !isFoldHeading) { // 选中 heading 时,使用删除的 transaction updateTransaction(protyle, id, nodeElement.outerHTML, oldHTML); } } protyle.hint.render(protyle); let textPlain = protyle.lute.BlockDOM2StdMd(html).trimEnd(); // 需要 trimEnd,否则 \n 会导致 https://github.com/siyuan-note/siyuan/issues/6218 textPlain = textPlain.replace(/\u00A0/g, " "); // Replace non-breaking spaces with normal spaces when copying https://github.com/siyuan-note/siyuan/issues/9382 event.clipboardData.setData("text/plain", textPlain); event.clipboardData.setData("text/html", protyle.lute.BlockDOM2HTML(html)); event.clipboardData.setData("text/siyuan", html); }); let beforeContextmenuRange: Range; this.element.addEventListener("contextmenu", (event: MouseEvent & { detail: any }) => { if (event.shiftKey) { return; } event.stopPropagation(); event.preventDefault(); const x = event.clientX || event.detail.x; const y = event.clientY || event.detail.y; const selectElements = protyle.wysiwyg.element.querySelectorAll(".protyle-wysiwyg--select"); if (selectElements.length > 1) { // 多选块 hideElements(["util"], protyle); protyle.gutter.renderMenu(protyle, selectElements[0]); window.siyuan.menus.menu.popup({x, y}); return; } const target = event.detail.target || event.target as HTMLElement; const embedElement = hasClosestByAttribute(target, "data-type", "NodeBlockQueryEmbed"); if (embedElement) { if (getSelection().rangeCount === 0) { focusSideBlock(embedElement); } protyle.gutter.renderMenu(protyle, embedElement); /// #if MOBILE window.siyuan.menus.menu.fullscreen(); /// #else window.siyuan.menus.menu.popup({x, y}); /// #endif return false; } protyle.toolbar.range = getEditorRange(protyle.element); if (target.tagName === "SPAN") { // https://ld246.com/article/1665141518103 let types = protyle.toolbar.getCurrentType(protyle.toolbar.range); if (types.length === 0) { // https://github.com/siyuan-note/siyuan/issues/8960 types = (target.dataset.type || "").split(" "); } if (types.length > 0) { removeSearchMark(target); } if (types.includes("block-ref")) { refMenu(protyle, target); // 阻止 popover target.setAttribute("prevent-popover", "true"); setTimeout(() => { target.removeAttribute("prevent-popover"); }, 620); return false; } else if (types.includes("file-annotation-ref") && !protyle.disabled) { fileAnnotationRefMenu(protyle, target); return false; } else if (types.includes("tag") && !protyle.disabled) { tagMenu(protyle, target); return false; } else if (types.includes("inline-memo")) { protyle.toolbar.showRender(protyle, target); return false; } else if (types.includes("a") && !protyle.disabled) { linkMenu(protyle, target); if (window.siyuan.config.editor.floatWindowMode === 0 && target.getAttribute("data-href")?.startsWith("siyuan://blocks")) { // 阻止 popover target.setAttribute("prevent-popover", "true"); setTimeout(() => { target.removeAttribute("prevent-popover"); }, 620); } return false; } } if (target.tagName === "IMG" && hasClosestByClassName(target, "img")) { imgMenu(protyle, protyle.toolbar.range, target.parentElement.parentElement, { clientX: x + 4, clientY: y }); return false; } const nodeElement = hasClosestBlock(target); const avRowElement = hasClosestByClassName(target, "av__row"); if (avRowElement && avContextmenu(protyle, avRowElement, { x: event.clientX, y: avRowElement.getBoundingClientRect().bottom, h: avRowElement.clientHeight })) { event.stopPropagation(); event.preventDefault(); return; } if (!nodeElement) { return false; } if (!isNotEditBlock(nodeElement) && !nodeElement.classList.contains("protyle-wysiwyg--select") && !hasClosestByClassName(target, "protyle-action") && // https://github.com/siyuan-note/siyuan/issues/8983 (isMobile() || event.detail.target || (beforeContextmenuRange && nodeElement.contains(beforeContextmenuRange.startContainer))) ) { if ((!isMobile() || protyle.toolbar?.element.classList.contains("fn__none")) && !nodeElement.classList.contains("av")) { contentMenu(protyle, nodeElement); window.siyuan.menus.menu.popup({x, y: y + 13, h: 26}); protyle.toolbar?.element.classList.add("fn__none"); if (nodeElement.classList.contains("table")) { nodeElement.querySelector(".table__select").removeAttribute("style"); } } } else if (protyle.toolbar.range.toString() === "") { hideElements(["util"], protyle); if (protyle.gutter) { protyle.gutter.renderMenu(protyle, nodeElement); } /// #if MOBILE window.siyuan.menus.menu.fullscreen(); /// #else window.siyuan.menus.menu.popup({x, y}); /// #endif protyle.toolbar?.element.classList.add("fn__none"); } }); this.element.addEventListener("pointerdown", () => { if (getSelection().rangeCount > 0) { beforeContextmenuRange = getSelection().getRangeAt(0); } else { beforeContextmenuRange = undefined; } }); let preventGetTopHTML = false; this.element.addEventListener("mousewheel", (event: WheelEvent) => { // https://ld246.com/article/1648865235549 // 不能使用上一版本的 timeStamp,否则一直滚动将导致间隔不够 https://ld246.com/article/1662852664926 if (!preventGetTopHTML && event.deltaY < 0 && !protyle.scroll.element.classList.contains("fn__none") && protyle.contentElement.clientHeight === protyle.contentElement.scrollHeight && protyle.wysiwyg.element.firstElementChild.getAttribute("data-eof") !== "1") { fetchPost("/api/filetree/getDoc", { id: protyle.wysiwyg.element.firstElementChild.getAttribute("data-node-id"), mode: 1, size: window.siyuan.config.editor.dynamicLoadBlocks, }, getResponse => { preventGetTopHTML = false; onGet({ data: getResponse, protyle, action: [Constants.CB_GET_BEFORE, Constants.CB_GET_UNCHANGEID], }); }); preventGetTopHTML = true; } if (event.deltaX === 0) { return; } // https://github.com/siyuan-note/siyuan/issues/4099 const tableElement = hasClosestByClassName(event.target as HTMLElement, "table"); if (tableElement) { const tableSelectElement = tableElement.querySelector(".table__select") as HTMLElement; if (tableSelectElement?.style.width) { tableSelectElement.removeAttribute("style"); window.siyuan.menus.menu.remove(); } } }, {passive: true}); let overAttr = false; this.element.addEventListener("mouseover", (event: MouseEvent & { target: Element }) => { const attrElement = hasClosestByClassName(event.target, "protyle-attr"); if (attrElement) { overAttr = true; attrElement.parentElement.classList.add("protyle-wysiwyg--hl"); return; } else if (overAttr) { const hlElement = protyle.wysiwyg.element.querySelector(".protyle-wysiwyg--hl"); if (hlElement) { hlElement.classList.remove("protyle-wysiwyg--hl"); } overAttr = false; } if (hasClosestByClassName(event.target, "protyle-action") || !protyle.options.render.gutter) { return; } const nodeElement = hasClosestBlock(event.target); if (nodeElement && (nodeElement.classList.contains("list") || nodeElement.classList.contains("li"))) { // 光标在列表下部应显示右侧的元素,而不是列表本身。放在 windowEvent 中的 mousemove 下处理 return; } if (nodeElement) { const embedElement = hasClosestByAttribute(nodeElement, "data-type", "NodeBlockQueryEmbed"); if (embedElement) { protyle.gutter.render(protyle, embedElement, this.element); } else { // database 行块标 const rowElement = hasClosestByClassName(event.target, "av__row"); if (rowElement && rowElement.dataset.id) { const guttersElement = rowElement.querySelector(".av__gutters"); guttersElement.setAttribute("style", `left:${rowElement.parentElement.parentElement.getBoundingClientRect().left - guttersElement.clientWidth}px;top:${rowElement.getBoundingClientRect().top}px`); } protyle.gutter.render(protyle, nodeElement, this.element); } } }); this.element.addEventListener("paste", (event: ClipboardEvent & { target: HTMLElement }) => { if (protyle.disabled) { event.stopPropagation(); event.preventDefault(); return; } window.siyuan.ctrlIsPressed = false; // https://github.com/siyuan-note/siyuan/issues/6373 // https://github.com/siyuan-note/siyuan/issues/4600 if (event.target.tagName === "PROTYLE-HTML") { event.stopPropagation(); return; } const blockElement = hasClosestBlock(event.target); if (blockElement && !getContenteditableElement(blockElement)) { event.stopPropagation(); event.preventDefault(); return; } paste(protyle, event); }); // 输入法测试点 https://github.com/siyuan-note/siyuan/issues/3027 let isComposition = false; // for iPhone this.element.addEventListener("compositionstart", (event) => { // 搜狗输入法划选输入后无 data https://github.com/siyuan-note/siyuan/issues/4672 const range = getEditorRange(protyle.wysiwyg.element); const nodeElement = hasClosestBlock(range.startContainer); if (nodeElement && typeof protyle.wysiwyg.lastHTMLs[nodeElement.getAttribute("data-node-id")] === "undefined") { range.insertNode(document.createElement("wbr")); protyle.wysiwyg.lastHTMLs[nodeElement.getAttribute("data-node-id")] = nodeElement.outerHTML; nodeElement.querySelector("wbr").remove(); } isComposition = true; event.stopPropagation(); }); this.element.addEventListener("compositionend", (event: InputEvent) => { event.stopPropagation(); isComposition = false; const range = getEditorRange(this.element); const blockElement = hasClosestBlock(range.startContainer); if (blockElement && blockElement.getAttribute("data-type") === "NodeHTMLBlock") { return; } if (!blockElement) { return; } if ("" !== event.data) { this.escapeInline(protyle, range, event); // 小鹤音形 ;k 不能使用 setTimeout; // wysiwyg.element contenteditable 为 false 时,连拼 needRender 必须为 false // hr 渲染;任务列表、粗体、数学公示结尾 needRender 必须为 true input(protyle, blockElement, range, true); } else { const id = blockElement.getAttribute("data-node-id"); if (protyle.wysiwyg.lastHTMLs[id]) { updateTransaction(protyle, id, blockElement.outerHTML, protyle.wysiwyg.lastHTMLs[id]); } } }); let timeout: number; this.element.addEventListener("input", (event: InputEvent) => { const target = event.target as HTMLElement; if (target.tagName === "VIDEO" || target.tagName === "AUDIO" || event.inputType === "historyRedo") { return; } if (event.inputType === "historyUndo") { /// #if !BROWSER ipcRenderer.send(Constants.SIYUAN_CMD, "redo"); /// #endif window.siyuan.menus.menu.remove(); return; } const range = getEditorRange(this.element); const blockElement = hasClosestBlock(range.startContainer); if (!blockElement) { return; } if (blockElement && blockElement.getAttribute("data-type") === "NodeHTMLBlock") { event.stopPropagation(); return; } if ([":", "(", "【", "(", "[", "{", "「", "『", "#", "/", "、"].includes(event.data)) { protyle.hint.enableExtend = true; } if (event.isComposing || isComposition || // https://github.com/siyuan-note/siyuan/issues/337 编辑器内容拖拽问题 event.inputType === "deleteByDrag" || event.inputType === "insertFromDrop" ) { return; } this.escapeInline(protyle, range, event); if ((/^\d{1}$/.test(event.data) || event.data === "‘" || event.data === "“" || // 百度输入法中文反双引号 https://github.com/siyuan-note/siyuan/issues/9686 event.data === "”" || event.data === "「")) { clearTimeout(timeout); // https://github.com/siyuan-note/siyuan/issues/9179 timeout = window.setTimeout(() => { input(protyle, blockElement, range, true); // 搜狗拼音数字后面句号变为点;Mac 反向双引号无法输入 }); } else { input(protyle, blockElement, range, true); } event.stopPropagation(); }); this.element.addEventListener("keyup", (event) => { const range = getEditorRange(this.element).cloneRange(); const nodeElement = hasClosestBlock(range.startContainer); if (event.key !== "PageUp" && event.key !== "PageDown" && event.key !== "Home" && event.key !== "End" && event.key.indexOf("Arrow") === -1 && event.key !== "Alt" && event.key !== "Shift" && event.key !== "CapsLock" && event.key !== "Escape" && event.key !== "Meta" && !/^F\d{1,2}$/.test(event.key) && (!event.isComposing || (event.isComposing && range.toString() !== "")) // https://github.com/siyuan-note/siyuan/issues/4341 ) { // 搜狗输入法不走 keydown,需重新记录历史状态 if (range.toString() === "" && // windows 下回车新建块输入abc,选中 bc ctrl+m 后光标错误 nodeElement && typeof protyle.wysiwyg.lastHTMLs[nodeElement.getAttribute("data-node-id")] === "undefined") { range.insertNode(document.createElement("wbr")); protyle.wysiwyg.lastHTMLs[nodeElement.getAttribute("data-node-id")] = nodeElement.outerHTML; nodeElement.querySelector("wbr").remove(); } return; } // 需放在 lastHTMLs 后,否则 https://github.com/siyuan-note/siyuan/issues/4388 if (this.preventKeyup) { return; } if ((event.shiftKey || isOnlyMeta(event)) && !event.isComposing && range.toString() !== "") { // 工具栏 protyle.toolbar.render(protyle, range, event); countSelectWord(range); } if (event.eventPhase !== 3 && !event.shiftKey && (event.key.indexOf("Arrow") > -1 || event.key === "Home" || event.key === "End" || event.key === "PageUp" || event.key === "PageDown") && !event.isComposing) { if (nodeElement) { this.setEmptyOutline(protyle, nodeElement); if (range.toString() === "") { countSelectWord(range, protyle.block.rootID); } } event.stopPropagation(); } // https://github.com/siyuan-note/siyuan/issues/8918 if ((event.key === "ArrowLeft" || event.key === "ArrowRight" || event.key === "Alt" || event.key === "Shift") && // 选中后 alt+shift+arrowRight 会导致光标和选中块不一致 nodeElement && !nodeElement.classList.contains("protyle-wysiwyg--select")) { const selectElements = Array.from(protyle.wysiwyg.element.querySelectorAll(".protyle-wysiwyg--select")); let containRange = false; selectElements.find(item => { if (item.contains(range.startContainer)) { containRange = true; return true; } }); if (!containRange && selectElements.length > 0) { selectElements.forEach(item => { item.classList.remove("protyle-wysiwyg--select"); }); nodeElement.classList.add("protyle-wysiwyg--select"); } } }); this.element.addEventListener("dblclick", (event: MouseEvent & { target: HTMLElement }) => { if (event.target.tagName === "IMG" && !event.target.classList.contains("emoji")) { previewDocImage((event.target as HTMLImageElement).src, protyle.block.rootID); return; } }); this.element.addEventListener("click", (event: MouseEvent & { target: HTMLElement }) => { protyle.app.plugins.forEach(item => { item.eventBus.emit("click-editorcontent", { protyle, event }); }); hideElements(["hint", "util"], protyle); const ctrlIsPressed = isOnlyMeta(event); /// #if !MOBILE const backlinkBreadcrumbItemElement = hasClosestByClassName(event.target, "protyle-breadcrumb__item"); if (backlinkBreadcrumbItemElement) { const breadcrumbId = backlinkBreadcrumbItemElement.getAttribute("data-id"); if (breadcrumbId) { if (ctrlIsPressed) { fetchPost("/api/block/checkBlockFold", {id: breadcrumbId}, (foldResponse) => { openFileById({ app: protyle.app, id: breadcrumbId, action: foldResponse.data ? [Constants.CB_GET_FOCUS, Constants.CB_GET_ALL] : [Constants.CB_GET_FOCUS, Constants.CB_GET_CONTEXT], zoomIn: foldResponse.data }); }); } else { loadBreadcrumb(protyle, backlinkBreadcrumbItemElement); } } else { // 引用标题时的更多加载 getBacklinkHeadingMore(backlinkBreadcrumbItemElement); } event.stopPropagation(); return; } /// #endif if (!event.shiftKey) { this.shiftStartElement = undefined; } this.setEmptyOutline(protyle, event.target); const tableElement = hasClosestByClassName(event.target, "table"); this.element.querySelectorAll(".table").forEach(item => { if (!tableElement || !item.isSameNode(tableElement)) { item.querySelector(".table__select").removeAttribute("style"); } if (tableElement && tableElement.isSameNode(item) && item.querySelector(".table__select").getAttribute("style")) { // 防止合并单元格的菜单消失 event.stopPropagation(); } }); // 面包屑定位,需至于前,否则 return 的元素就无法进行面包屑定位 if (protyle.options.render.breadcrumb) { protyle.breadcrumb.render(protyle); } const range = getEditorRange(this.element); // 需放在嵌入块之前,否则嵌入块内的引用、链接、pdf 双链无法点击打开 https://ld246.com/article/1630479789513 const blockRefElement = hasClosestByAttribute(event.target, "data-type", "block-ref"); const aElement = hasClosestByAttribute(event.target, "data-type", "a") || hasClosestByAttribute(event.target, "data-type", "url"); let aLink = ""; if (aElement) { if (aElement.classList.contains("av__celltext")) { aLink = aElement.textContent.trim(); } else { aLink = aElement.getAttribute("data-href"); } } if (blockRefElement || aLink.startsWith("siyuan://blocks/")) { event.stopPropagation(); event.preventDefault(); hideElements(["dialog", "toolbar"], protyle); if (range.toString() !== "" && !event.shiftKey) { // 选择不打开引用 return; } let refBlockId: string; if (blockRefElement) { refBlockId = blockRefElement.getAttribute("data-id"); } else if (aElement) { refBlockId = aLink.substring(16, 38); } fetchPost("/api/block/checkBlockFold", {id: refBlockId}, (foldResponse) => { /// #if MOBILE openMobileFileById(protyle.app, refBlockId, foldResponse.data ? [Constants.CB_GET_ALL, Constants.CB_GET_HL] : [Constants.CB_GET_HL, Constants.CB_GET_CONTEXT]); activeBlur(); hideKeyboardToolbar(); /// #else if (event.shiftKey) { openFileById({ app: protyle.app, id: refBlockId, position: "bottom", action: foldResponse.data ? [Constants.CB_GET_FOCUS, Constants.CB_GET_ALL] : [Constants.CB_GET_FOCUS, Constants.CB_GET_CONTEXT, Constants.CB_GET_ROOTSCROLL], zoomIn: foldResponse.data }); } else if (event.altKey) { openFileById({ app: protyle.app, id: refBlockId, position: "right", action: foldResponse.data ? [Constants.CB_GET_FOCUS, Constants.CB_GET_ALL] : [Constants.CB_GET_FOCUS, Constants.CB_GET_CONTEXT, Constants.CB_GET_ROOTSCROLL], zoomIn: foldResponse.data }); } else if (ctrlIsPressed) { openFileById({ app: protyle.app, id: refBlockId, action: foldResponse.data ? [Constants.CB_GET_FOCUS, Constants.CB_GET_ALL] : [Constants.CB_GET_HL, Constants.CB_GET_CONTEXT, Constants.CB_GET_ROOTSCROLL], keepCursor: true, zoomIn: foldResponse.data }); } else { openFileById({ app: protyle.app, id: refBlockId, action: foldResponse.data ? [Constants.CB_GET_FOCUS, Constants.CB_GET_ALL] : [Constants.CB_GET_FOCUS, Constants.CB_GET_CONTEXT, Constants.CB_GET_ROOTSCROLL], zoomIn: foldResponse.data }); } /// #endif }); /// #if !MOBILE if (protyle.model) { // 打开双链需记录到后退中 https://github.com/siyuan-note/insider/issues/801 let blockElement: HTMLElement | false; if (blockRefElement) { blockElement = hasClosestBlock(blockRefElement); } else if (aElement) { blockElement = hasClosestBlock(aElement); } if (blockElement) { pushBack(protyle, getEditorRange(this.element), blockElement); } } /// #endif return; } const fileElement = hasClosestByAttribute(event.target, "data-type", "file-annotation-ref"); if (fileElement && range.toString() === "") { event.stopPropagation(); event.preventDefault(); const fileIds = fileElement.getAttribute("data-id").split("/"); const linkAddress = `assets/${fileIds[1]}`; /// #if MOBILE openByMobile(linkAddress); /// #else if (ctrlIsPressed) { openBy(linkAddress, "folder"); } else if (event.shiftKey) { openBy(linkAddress, "app"); } else { openAsset(protyle.app, linkAddress, fileIds[2], "right"); } /// #endif return; } if (aElement && !event.altKey) { event.stopPropagation(); event.preventDefault(); const linkAddress = Lute.UnEscapeHTMLStr(aLink); /// #if MOBILE openByMobile(linkAddress); /// #else if (isLocalPath(linkAddress)) { const linkPathname = linkAddress.split("?page")[0]; if (Constants.SIYUAN_ASSETS_EXTS.includes(pathPosix().extname(linkPathname)) && (!linkPathname.endsWith(".pdf") || (linkPathname.endsWith(".pdf") && !linkAddress.startsWith("file://"))) ) { if (ctrlIsPressed) { openBy(linkAddress, "folder"); } else if (event.shiftKey) { openBy(linkAddress, "app"); } else { openAsset(protyle.app, linkPathname, parseInt(getSearch("page", linkAddress)), "right"); } } else { /// #if !BROWSER if (ctrlIsPressed) { openBy(linkAddress, "folder"); } else { openBy(linkAddress, "app"); } /// #else openByMobile(linkAddress); /// #endif } } else if (linkAddress) { /// #if !BROWSER shell.openExternal(linkAddress).catch((e) => { showMessage(e); }); /// #else openByMobile(linkAddress); /// #endif } /// #endif return; } const tagElement = hasClosestByAttribute(event.target, "data-type", "tag"); if (tagElement && !event.altKey) { /// #if !MOBILE openGlobalSearch(protyle.app, `#${tagElement.textContent}#`, !ctrlIsPressed); hideElements(["dialog"]); /// #else const searchOption = window.siyuan.storage[Constants.LOCAL_SEARCHDATA]; popSearch(protyle.app, { removed: searchOption.removed, sort: searchOption.sort, group: searchOption.group, hasReplace: false, method: 0, hPath: "", idPath: [], k: `#${tagElement.textContent}#`, r: "", page: 1, types: Object.assign({}, searchOption.types) }); /// #endif return; } const embedItemElement = hasClosestByClassName(event.target, "protyle-wysiwyg__embed"); if (embedItemElement) { const embedId = embedItemElement.getAttribute("data-id"); fetchPost("/api/block/checkBlockFold", {id: embedId}, (foldResponse) => { /// #if MOBILE openMobileFileById(protyle.app, embedId, foldResponse.data ? [Constants.CB_GET_ALL, Constants.CB_GET_HL] : [Constants.CB_GET_HL, Constants.CB_GET_CONTEXT]); activeBlur(); hideKeyboardToolbar(); /// #else if (event.shiftKey) { openFileById({ app: protyle.app, id: embedId, position: "bottom", action: foldResponse.data ? [Constants.CB_GET_FOCUS, Constants.CB_GET_ALL] : [Constants.CB_GET_FOCUS, Constants.CB_GET_CONTEXT, Constants.CB_GET_ROOTSCROLL], zoomIn: foldResponse.data }); } else if (event.altKey) { openFileById({ app: protyle.app, id: embedId, position: "right", action: foldResponse.data ? [Constants.CB_GET_FOCUS, Constants.CB_GET_ALL] : [Constants.CB_GET_FOCUS, Constants.CB_GET_CONTEXT, Constants.CB_GET_ROOTSCROLL], zoomIn: foldResponse.data }); } else if (ctrlIsPressed) { openFileById({ app: protyle.app, id: embedId, action: foldResponse.data ? [Constants.CB_GET_FOCUS, Constants.CB_GET_ALL] : [Constants.CB_GET_FOCUS, Constants.CB_GET_CONTEXT, Constants.CB_GET_ROOTSCROLL], zoomIn: foldResponse.data, keepCursor: true, }); } else if (!protyle.disabled) { window.siyuan.blockPanels.push(new BlockPanel({ app: protyle.app, targetElement: embedItemElement, isBacklink: false, nodeIds: [embedId], })); } /// #endif }); event.stopPropagation(); return; } if (commonClick(event, protyle)) { return; } if (hasTopClosestByClassName(event.target, "protyle-action__copy")) { return; } const editElement = hasClosestByClassName(event.target, "protyle-action__edit"); if (editElement && !protyle.disabled) { protyle.toolbar.showRender(protyle, editElement.parentElement.parentElement); event.stopPropagation(); event.preventDefault(); return; } const menuElement = hasClosestByClassName(event.target, "protyle-action__menu"); if (menuElement) { protyle.gutter.renderMenu(protyle, menuElement.parentElement.parentElement); /// #if MOBILE window.siyuan.menus.menu.fullscreen(); /// #else const rect = menuElement.getBoundingClientRect(); window.siyuan.menus.menu.popup({ x: rect.left, y: rect.top, isLeft: true }); /// #endif event.stopPropagation(); event.preventDefault(); return; } const reloadElement = hasClosestByClassName(event.target, "protyle-action__reload"); if (reloadElement) { const embedReloadElement = hasClosestByAttribute(reloadElement, "data-type", "NodeBlockQueryEmbed"); if (embedReloadElement) { embedReloadElement.removeAttribute("data-render"); blockRender(protyle, embedReloadElement); } event.stopPropagation(); event.preventDefault(); return; } const languageElement = hasClosestByClassName(event.target, "protyle-action__language"); if (languageElement && !protyle.disabled) { protyle.toolbar.showCodeLanguage(protyle, languageElement); event.stopPropagation(); event.preventDefault(); return; } // 需放在属性后,否则数学公式无法点击属性;需放在 action 后,否则嵌入块的的 action 无法打开;需放在嵌入块后,否则嵌入块中的数学公式会被打开 const mathElement = hasClosestByAttribute(event.target, "data-subtype", "math"); if (!event.shiftKey && !ctrlIsPressed && mathElement && !protyle.disabled) { protyle.toolbar.showRender(protyle, mathElement); event.stopPropagation(); return; } const actionElement = hasClosestByClassName(event.target, "protyle-action"); if (actionElement) { const type = actionElement.parentElement.parentElement.getAttribute("data-type"); if (type === "img" && !protyle.disabled) { imgMenu(protyle, range, actionElement.parentElement.parentElement, { clientX: event.clientX + 4, clientY: event.clientY }); } else if (actionElement.parentElement.classList.contains("li")) { const actionId = actionElement.parentElement.getAttribute("data-node-id"); if (event.altKey && !protyle.disabled) { // 展开/折叠当前层级的所有列表项 if (actionElement.parentElement.parentElement.classList.contains("protyle-wysiwyg")) { // 缩放列表项 https://ld246.com/article/1653123034794 setFold(protyle, actionElement.parentElement); } else { let hasFold = true; const oldHTML = actionElement.parentElement.parentElement.outerHTML; Array.from(actionElement.parentElement.parentElement.children).find((listItemElement) => { if (listItemElement.classList.contains("li")) { if (listItemElement.getAttribute("fold") !== "1" && listItemElement.childElementCount > 3) { hasFold = false; return true; } } }); Array.from(actionElement.parentElement.parentElement.children).find((listItemElement) => { if (listItemElement.classList.contains("li")) { if (hasFold) { listItemElement.removeAttribute("fold"); } else if (listItemElement.childElementCount > 3) { listItemElement.setAttribute("fold", "1"); } } }); updateTransaction(protyle, actionElement.parentElement.parentElement.getAttribute("data-node-id"), actionElement.parentElement.parentElement.outerHTML, oldHTML); } hideElements(["gutter"], protyle); } else if (event.shiftKey && !protyle.disabled) { openAttr(actionElement.parentElement, "bookmark", protyle); } else if (ctrlIsPressed) { zoomOut({protyle, id: actionId}); } else { if (actionElement.classList.contains("protyle-action--task")) { if (!protyle.disabled) { const html = actionElement.parentElement.outerHTML; if (actionElement.parentElement.classList.contains("protyle-task--done")) { actionElement.querySelector("use").setAttribute("xlink:href", "#iconUncheck"); actionElement.parentElement.classList.remove("protyle-task--done"); } else { actionElement.querySelector("use").setAttribute("xlink:href", "#iconCheck"); actionElement.parentElement.classList.add("protyle-task--done"); } actionElement.parentElement.setAttribute("updated", dayjs().format("YYYYMMDDHHmmss")); updateTransaction(protyle, actionId, actionElement.parentElement.outerHTML, html); } } else { if (protyle.block.showAll && protyle.block.id === actionId) { enterBack(protyle, actionId); } else { zoomOut({protyle, id: actionId}); } } } } event.stopPropagation(); return; } const selectElement = hasClosestByClassName(event.target, "hr") || hasClosestByClassName(event.target, "iframe"); if (!event.shiftKey && !ctrlIsPressed && selectElement) { selectElement.classList.add("protyle-wysiwyg--select"); event.stopPropagation(); return; } const imgElement = hasTopClosestByClassName(event.target, "img"); if (!event.shiftKey && !ctrlIsPressed && imgElement) { imgElement.classList.add("img--select"); range.setStartAfter(imgElement); range.collapse(true); focusByRange(range); // 需等待 range 更新再次进行渲染 if (protyle.options.render.breadcrumb) { protyle.breadcrumb.render(protyle); } return; } if (avClick(protyle, event)) { return; } // 点击空白 if (event.target.contains(this.element) && this.element.lastElementChild && !protyle.disabled) { const lastRect = this.element.lastElementChild.getBoundingClientRect(); if (event.y > lastRect.bottom) { const lastEditElement = getContenteditableElement(getLastBlock(this.element.lastElementChild)); if (!lastEditElement || (this.element.lastElementChild.getAttribute("data-type") !== "NodeParagraph" && protyle.wysiwyg.element.getAttribute("data-doc-type") !== "NodeListItem") || (this.element.lastElementChild.getAttribute("data-type") === "NodeParagraph" && getContenteditableElement(lastEditElement).innerHTML !== "")) { const emptyElement = genEmptyElement(false, false); this.element.insertAdjacentElement("beforeend", emptyElement); transaction(protyle, [{ action: "insert", data: emptyElement.outerHTML, id: emptyElement.getAttribute("data-node-id"), previousID: emptyElement.previousElementSibling.getAttribute("data-node-id"), parentID: protyle.block.parentID }], [{ action: "delete", id: emptyElement.getAttribute("data-node-id") }]); const emptyEditElement = getContenteditableElement(emptyElement) as HTMLInputElement; range.selectNodeContents(emptyEditElement); range.collapse(true); focusByRange(range); // 需等待 range 更新再次进行渲染 if (protyle.options.render.breadcrumb) { setTimeout(() => { protyle.breadcrumb.render(protyle); }, Constants.TIMEOUT_TRANSITION); } } else if (lastEditElement) { range.selectNodeContents(lastEditElement); range.collapse(false); focusByRange(range); } } } setTimeout(() => { // 选中后,在选中的文字上点击需等待 range 更新 const newRange = getEditorRange(this.element); /// #if !MOBILE if (newRange.toString().replace(Constants.ZWSP, "") !== "") { protyle.toolbar.render(protyle, newRange); } else { hideElements(["toolbar"], protyle); } /// #endif if (!protyle.wysiwyg.element.querySelector(".protyle-wysiwyg--select")) { countSelectWord(newRange, protyle.block.rootID); } if (getSelection().rangeCount === 0) { // https://github.com/siyuan-note/siyuan/issues/5901 focusByRange(newRange); } /// #if !MOBILE pushBack(protyle, newRange); /// #endif }, (isMobile() || isInIOS()) ? 520 : 0); // Android/iPad 双击慢了出不来 protyle.hint.enableExtend = false; if (event.shiftKey) { event.preventDefault(); event.stopPropagation(); // shift 多选 let startElement = this.shiftStartElement; let endElement = hasClosestBlock(event.target); if (this.shiftStartElement && endElement && !this.shiftStartElement.isSameNode(endElement)) { let toDown = true; range.collapse(true); const startRect = startElement.getBoundingClientRect(); const endRect = endElement.getBoundingClientRect(); let startTop = startRect.top; let endTop = endRect.top; if (startTop === endTop) { // 横排 https://ld246.com/article/1663036247544 startTop = startRect.left; endTop = endRect.left; } if (startTop > endTop) { const tempElement = endElement; endElement = startElement; startElement = tempElement; const tempTop = endTop; endTop = startTop; startTop = tempTop; toDown = false; } let selectElements: Element[] = []; let currentElement: HTMLElement = startElement; let hasJump = false; while (currentElement) { if (currentElement && !currentElement.classList.contains("protyle-attr")) { const currentRect = currentElement.getBoundingClientRect(); if (startRect.top === endRect.top ? (currentRect.left <= endTop) : (currentRect.top <= endTop)) { if (hasJump) { // 父节点的下个节点在选中范围内才可使用父节点作为选中节点 if (currentElement.nextElementSibling && !currentElement.nextElementSibling.classList.contains("protyle-attr")) { const currentNextRect = currentElement.nextElementSibling.getBoundingClientRect(); if (startRect.top === endRect.top ? (currentNextRect.left <= endTop && currentNextRect.bottom <= endRect.bottom) : (currentNextRect.top <= endTop)) { selectElements = [currentElement]; currentElement = currentElement.nextElementSibling as HTMLElement; hasJump = false; } else if (currentElement.parentElement.classList.contains("sb")) { currentElement = hasClosestBlock(currentElement.parentElement) as HTMLElement; hasJump = true; } else { break; } } else { currentElement = hasClosestBlock(currentElement.parentElement) as HTMLElement; hasJump = true; } } else { selectElements.push(currentElement); currentElement = currentElement.nextElementSibling as HTMLElement; } } else if (currentElement.parentElement.classList.contains("sb")) { // 跳出超级块横向排版中的未选中元素 currentElement = hasClosestBlock(currentElement.parentElement) as HTMLElement; hasJump = true; } else { break; } } else { currentElement = hasClosestBlock(currentElement.parentElement) as HTMLElement; hasJump = true; } } if (selectElements.length === 1 && !selectElements[0].classList.contains("list") && !selectElements[0].classList.contains("bq") && !selectElements[0].classList.contains("sb")) { // 单个 p 不选中 this.shiftStartElement = undefined; } else { const ids: string[] = []; if (!protyle.wysiwyg.element.querySelector(".protyle-wysiwyg--select") && protyle.scroll && !protyle.scroll.element.classList.contains("fn__none") && !protyle.scroll.keepLazyLoad && (startElement.getBoundingClientRect().top < -protyle.contentElement.clientHeight * 2 || endElement.getBoundingClientRect().bottom > protyle.contentElement.clientHeight * 2)) { showMessage(window.siyuan.languages.crossKeepLazyLoad); } selectElements.forEach(item => { item.classList.add("protyle-wysiwyg--select"); ids.push(item.getAttribute("data-node-id")); // 清除选中的子块 https://ld246.com/article/1667826582251 item.querySelectorAll(".protyle-wysiwyg--select").forEach(subItem => { subItem.classList.remove("protyle-wysiwyg--select"); }); }); countBlockWord(ids); if (toDown) { focusBlock(selectElements[selectElements.length - 1], protyle.wysiwyg.element, false); } else { focusBlock(selectElements[0], protyle.wysiwyg.element, false); } } } } if (this.element.querySelector(".protyle-wysiwyg--select") && range.toString() !== "") { // 选中块后,文字不能被选中。需在 shift click 之后,防止shift点击单个块出现文字选中 range.collapse(false); focusByRange(range); } if (ctrlIsPressed && range.toString() === "") { let ctrlElement = hasClosestBlock(event.target); if (ctrlElement) { ctrlElement = getTopAloneElement(ctrlElement) as HTMLElement; if (ctrlElement.classList.contains("protyle-wysiwyg--select")) { ctrlElement.classList.remove("protyle-wysiwyg--select"); ctrlElement.removeAttribute("select-start"); ctrlElement.removeAttribute("select-end"); } else { ctrlElement.classList.add("protyle-wysiwyg--select"); } ctrlElement.querySelectorAll(".protyle-wysiwyg--select").forEach(item => { item.classList.remove("protyle-wysiwyg--select"); item.removeAttribute("select-start"); item.removeAttribute("select-end"); }); const ctrlParentElement = hasClosestByClassName(ctrlElement.parentElement, "protyle-wysiwyg--select"); if (ctrlParentElement) { ctrlParentElement.classList.remove("protyle-wysiwyg--select"); ctrlParentElement.removeAttribute("select-start"); ctrlParentElement.removeAttribute("select-end"); } const ids: string[] = []; protyle.wysiwyg.element.querySelectorAll(".protyle-wysiwyg--select").forEach(item => { ids.push(item.getAttribute("data-node-id")); }); countBlockWord(ids); } } }); } }