import {Divider} from "./Divider"; import {Font, hasSameTextStyle, setFontStyle} from "./Font"; import {ToolbarItem} from "./ToolbarItem"; import { fixTableRange, focusBlock, focusByRange, focusByWbr, getEditorRange, getSelectionPosition, selectAll, setFirstNodeRange, setLastNodeRange } from "../util/selection"; import {hasClosestBlock, hasClosestByAttribute, hasClosestByClassName} from "../util/hasClosest"; import {Link} from "./Link"; import {setPosition} from "../../util/setPosition"; import {updateTransaction} from "../wysiwyg/transaction"; import {Constants} from "../../constants"; import {copyPlainText, openByMobile, readClipboard, setStorageVal} from "../util/compatibility"; import {upDownHint} from "../../util/upDownHint"; import {highlightRender} from "../render/highlightRender"; import {getContenteditableElement, hasNextSibling, hasPreviousSibling} from "../wysiwyg/getBlock"; import {processRender} from "../util/processCode"; import {BlockRef} from "./BlockRef"; import {hintRenderTemplate, hintRenderWidget} from "../hint/extend"; import {blockRender} from "../render/blockRender"; /// #if !BROWSER import {openBy} from "../../editor/util"; /// #endif import {fetchPost} from "../../util/fetch"; import {isArrayEqual, isMobile} from "../../util/functions"; import * as dayjs from "dayjs"; import {insertEmptyBlock} from "../../block/util"; import {matchHotKey} from "../util/hotKey"; import {hideElements} from "../ui/hideElements"; import {electronUndo} from "../undo"; import {previewTemplate, toolbarKeyToMenu} from "./util"; import {hideMessage, showMessage} from "../../dialog/message"; import {InlineMath} from "./InlineMath"; import {InlineMemo} from "./InlineMemo"; import {mathRender} from "../render/mathRender"; import {linkMenu} from "../../menus/protyle"; import {addScript} from "../util/addScript"; import {confirmDialog} from "../../dialog/confirmDialog"; import {paste, pasteAsPlainText, pasteEscaped} from "../util/paste"; import {escapeHtml} from "../../util/escape"; import {resizeSide} from "../../history/resizeSide"; export class Toolbar { public element: HTMLElement; public subElement: HTMLElement; public subElementCloseCB: () => void; public range: Range; public toolbarHeight: number; constructor(protyle: IProtyle) { const options = protyle.options; const element = document.createElement("div"); element.className = "protyle-toolbar fn__none"; this.element = element; this.subElement = document.createElement("div"); /// #if MOBILE this.subElement.className = "protyle-util fn__none protyle-util--mobile"; /// #else this.subElement.className = "protyle-util fn__none"; /// #endif this.toolbarHeight = 29; protyle.app.plugins.forEach(item => { const pluginToolbar = item.updateProtyleToolbar(options.toolbar); pluginToolbar.forEach(toolbarItem => { if (typeof toolbarItem === "string" || Constants.INLINE_TYPE.concat("|").includes(toolbarItem.name) || !toolbarItem.hotkey) { return; } if (window.siyuan.config.keymap.plugin && window.siyuan.config.keymap.plugin[item.name] && window.siyuan.config.keymap.plugin[item.name][toolbarItem.name]) { toolbarItem.hotkey = window.siyuan.config.keymap.plugin[item.name][toolbarItem.name].custom; } }); options.toolbar = toolbarKeyToMenu(pluginToolbar); }); options.toolbar.forEach((menuItem: IMenuItem) => { const itemElement = this.genItem(protyle, menuItem); this.element.appendChild(itemElement); }); } public update(protyle: IProtyle) { this.element.innerHTML = ""; protyle.options.toolbar = toolbarKeyToMenu(Constants.PROTYLE_TOOLBAR); protyle.app.plugins.forEach(item => { const pluginToolbar = item.updateProtyleToolbar(protyle.options.toolbar); pluginToolbar.forEach(toolbarItem => { if (typeof toolbarItem === "string" || Constants.INLINE_TYPE.concat("|").includes(toolbarItem.name) || !toolbarItem.hotkey) { return; } if (window.siyuan.config.keymap.plugin && window.siyuan.config.keymap.plugin[item.name] && window.siyuan.config.keymap.plugin[item.name][toolbarItem.name]) { toolbarItem.hotkey = window.siyuan.config.keymap.plugin[item.name][toolbarItem.name].custom; } }); protyle.options.toolbar = toolbarKeyToMenu(pluginToolbar); }); protyle.options.toolbar.forEach((menuItem: IMenuItem) => { const itemElement = this.genItem(protyle, menuItem); this.element.appendChild(itemElement); }); } public render(protyle: IProtyle, range: Range, event?: KeyboardEvent) { this.range = range; let nodeElement = hasClosestBlock(range.startContainer); if (isMobile() || !nodeElement || protyle.disabled || nodeElement.classList.contains("av")) { this.element.classList.add("fn__none"); return; } // https://github.com/siyuan-note/siyuan/issues/5157 let hasText = false; Array.from(range.cloneContents().childNodes).find(item => { // zwsp 不显示工具栏 if (item.textContent.length > 0 && item.textContent !== Constants.ZWSP) { if (item.nodeType === 1 && (item as HTMLElement).classList.contains("img")) { // 图片不显示工具栏 } else { hasText = true; return true; } } }); if (!hasText || // 拖拽图片到最右侧 (range.commonAncestorContainer.nodeType !== 3 && (range.commonAncestorContainer as HTMLElement).classList.contains("img"))) { this.element.classList.add("fn__none"); return; } // shift+方向键或三击选中,不同的块 https://github.com/siyuan-note/siyuan/issues/3891 const startElement = hasClosestBlock(range.startContainer); const endElement = hasClosestBlock(range.endContainer); if (startElement && endElement && !startElement.isSameNode(endElement)) { if (event) { // 在 keyup 中使用 shift+方向键选中 if (event.key === "ArrowLeft") { this.range = setLastNodeRange(getContenteditableElement(startElement), range, false); } else if (event.key === "ArrowRight") { this.range = setFirstNodeRange(getContenteditableElement(endElement), range); this.range.collapse(false); } else if (event.key === "ArrowUp") { this.range = setFirstNodeRange(getContenteditableElement(endElement), range); nodeElement = hasClosestBlock(endElement); if (!nodeElement) { return; } } else if (event.key === "ArrowDown") { this.range = setLastNodeRange(getContenteditableElement(startElement), range, false); } } else { this.range = setLastNodeRange(getContenteditableElement(nodeElement), range, false); } focusByRange(this.range); if (this.range.toString() === "") { this.element.classList.add("fn__none"); return; } } // 需放在 range 修改之后,否则 https://github.com/siyuan-note/siyuan/issues/4726 if (nodeElement.getAttribute("data-type") === "NodeCodeBlock") { this.element.classList.add("fn__none"); return; } const rangePosition = getSelectionPosition(nodeElement, range); this.element.classList.remove("fn__none"); this.toolbarHeight = this.element.clientHeight; const y = rangePosition.top - this.toolbarHeight - 4; this.element.setAttribute("data-inity", y + Constants.ZWSP + protyle.contentElement.scrollTop.toString()); setPosition(this.element, rangePosition.left - 52, Math.max(y, protyle.element.getBoundingClientRect().top + 30)); this.element.querySelectorAll(".protyle-toolbar__item--current").forEach(item => { item.classList.remove("protyle-toolbar__item--current"); }); const types = this.getCurrentType(); types.forEach(item => { if (["search-mark", "a", "block-ref", "virtual-block-ref", "text", "file-annotation-ref", "inline-math", "inline-memo", "", "backslash"].includes(item)) { return; } const itemElement = this.element.querySelector(`[data-type="${item}"]`); if (itemElement) { itemElement.classList.add("protyle-toolbar__item--current"); } }); } public getCurrentType(range = this.range) { let types: string[] = []; let startElement = range.startContainer as HTMLElement; if (startElement.nodeType === 3) { startElement = startElement.parentElement; } else if (startElement.childElementCount > 0 && startElement.childNodes[range.startOffset]?.nodeType !== 3) { startElement = startElement.childNodes[range.startOffset] as HTMLElement; } if (!startElement || startElement.nodeType === 3) { return []; } if (!["DIV", "TD", "TH", "TR"].includes(startElement.tagName)) { types = (startElement.getAttribute("data-type") || "").split(" "); } let endElement = range.endContainer as HTMLElement; if (endElement.nodeType === 3) { endElement = endElement.parentElement; } else if (endElement.childElementCount > 0 && endElement.childNodes[range.endOffset]?.nodeType !== 3) { endElement = endElement.childNodes[range.endOffset] as HTMLElement; } if (!endElement || endElement.nodeType === 3) { return []; } if (!["DIV", "TD", "TH", "TR"].includes(endElement.tagName) && !startElement.isSameNode(endElement)) { types = types.concat((endElement.getAttribute("data-type") || "").split(" ")); } range.cloneContents().childNodes.forEach((item: HTMLElement) => { if (item.nodeType !== 3) { types = types.concat((item.getAttribute("data-type") || "").split(" ")); } }); types = [...new Set(types)]; types.find((item, index) => { if (item === "") { types.splice(index, 1); return true; } }); return types; } public setInlineMark(protyle: IProtyle, type: string, action: "range" | "toolbar", textObj?: ITextOption) { const nodeElement = hasClosestBlock(this.range.startContainer); if (!nodeElement || nodeElement.getAttribute("data-type") === "NodeCodeBlock") { return; } const endElement = hasClosestBlock(this.range.endContainer); if (!endElement) { return; } // 三击后还没有重新纠正 range 时使用快捷键标记会导致异常 https://github.com/siyuan-note/siyuan/issues/7068 if (!nodeElement.isSameNode(endElement)) { this.range = setLastNodeRange(getContenteditableElement(nodeElement), this.range, false); } const rangeTypes = this.getCurrentType(this.range); // https://github.com/siyuan-note/siyuan/issues/6501 // https://github.com/siyuan-note/siyuan/issues/12877 if (rangeTypes.length === 1 && ["block-ref", "file-annotation-ref", "a", "inline-memo", "inline-math", "tag"].includes(rangeTypes[0]) && type === "clear") { return; } const selectText = this.range.toString(); fixTableRange(this.range); let previousElement: HTMLElement; let nextElement: HTMLElement; let previousIndex: number; let nextIndex: number; const previousSibling = hasPreviousSibling(this.range.startContainer); if (!["DIV", "TD", "TH", "TR"].includes(this.range.startContainer.parentElement.tagName)) { if (this.range.startOffset === 0 && !previousSibling) { previousElement = this.range.startContainer.parentElement.previousSibling as HTMLElement; this.range.setStartBefore(this.range.startContainer.parentElement); } else { previousElement = this.range.startContainer.parentElement; } } else if (previousSibling && previousSibling.nodeType !== 3 && this.range.startOffset === 0) { // **aaa**bbb 选中 bbb 加粗 previousElement = previousSibling as HTMLElement; } let isEndSpan = false; const nextSibling = hasNextSibling(this.range.endContainer); if (!["DIV", "TD", "TH", "TR"].includes(this.range.endContainer.parentElement.tagName)) { if (this.range.endOffset === this.range.endContainer.textContent.length && !nextSibling) { nextElement = this.range.endContainer.parentElement.nextSibling as HTMLElement; this.range.setEndAfter(this.range.endContainer.parentElement); if (selectText === "") { isEndSpan = true; this.range.collapse(false); } } else { nextElement = this.range.endContainer.parentElement; } } else if (nextSibling && nextSibling.nodeType !== 3 && this.range.endOffset === this.range.endContainer.textContent.length) { // aaa**bbb** 选中 aaa 加粗 nextElement = nextSibling as HTMLElement; } this.range.insertNode(document.createElement("wbr")); const html = nodeElement.outerHTML; const contents = this.range.extractContents(); this.mergeNode(contents.childNodes); // 选择 span 中的一部分需进行包裹 if (previousElement && nextElement && previousElement.isSameNode(nextElement) && contents.firstChild?.nodeType === 3) { const attributes = previousElement.attributes; contents.childNodes.forEach(item => { const spanElement = document.createElement("span"); for (let i = 0; i < attributes.length; i++) { spanElement.setAttribute(attributes[i].name, attributes[i].value); } spanElement.innerHTML = item.textContent; item.replaceWith(spanElement); }); } const toolbarElement = isMobile() ? document.querySelector("#keyboardToolbar .keyboard__dynamic").nextElementSibling : this.element; const actionBtn = action === "toolbar" ? toolbarElement.querySelector(`[data-type="${type}"]`) : undefined; const newNodes: Node[] = []; if (type === "clear" || actionBtn?.classList.contains("protyle-toolbar__item--current") || ( action === "range" && rangeTypes.length > 0 && rangeTypes.includes(type) && !textObj )) { // 移除 if (type === "clear") { toolbarElement.querySelectorAll('[data-type="strong"],[data-type="em"],[data-type="u"],[data-type="s"],[data-type="mark"],[data-type="sup"],[data-type="sub"],[data-type="kbd"],[data-type="mark"],[data-type="code"]').forEach(item => { item.classList.remove("protyle-toolbar__item--current"); }); } else if (actionBtn) { actionBtn.classList.remove("protyle-toolbar__item--current"); } if (contents.childNodes.length === 0) { rangeTypes.find((itemType, index) => { if (type === itemType) { rangeTypes.splice(index, 1); return true; } }); if (rangeTypes.length === 0 || type === "clear") { newNodes.push(document.createTextNode(Constants.ZWSP)); } else { // 遇到以下类型结尾不应继承 https://github.com/siyuan-note/siyuan/issues/7200 let removeIndex = 0; while (removeIndex < rangeTypes.length) { if (["inline-memo", "text", "block-ref", "file-annotation-ref", "a"].includes(rangeTypes[removeIndex])) { rangeTypes.splice(removeIndex, 1); } else { ++removeIndex; } } const inlineElement = document.createElement("span"); inlineElement.setAttribute("data-type", rangeTypes.join(" ")); inlineElement.textContent = Constants.ZWSP; newNodes.push(inlineElement); } } contents.childNodes.forEach((item: HTMLElement, index) => { if (item.nodeType !== 3 && item.tagName !== "BR" && item.tagName !== "IMG" && !item.classList.contains("img")) { // 图片后有粗体,仅选中图片后,rang 中会包含一个空的粗体,需移除 if (item.textContent === "") { return; } const types = (item.getAttribute("data-type") || "").split(" "); if (type === "clear") { for (let i = 0; i < types.length; i++) { if (textObj && textObj.type === "text") { if ("text" === types[i]) { types.splice(i, 1); i--; } } else { if (["kbd", "text", "strong", "em", "u", "s", "mark", "sup", "sub", "code"].includes(types[i])) { types.splice(i, 1); i--; } } } } else { types.find((itemType, typeIndex) => { if (type === itemType) { types.splice(typeIndex, 1); return true; } }); } if (types.length === 0) { if (item.textContent === "") { item.textContent = Constants.ZWSP; } newNodes.push(document.createTextNode(item.textContent)); } else { if (selectText && type === "clear" && textObj && textObj.type === "text") { // 选中内容中没有样式需要清除时直接返回,否则清除粗体中部分内容会报错 if (item.style.color === "" && item.style.webkitTextFillColor === "" && item.style.webkitTextStroke === "" && item.style.textShadow === "" && item.style.backgroundColor === "" && item.style.fontSize === "") { item.setAttribute("data-type", types.join(" ")); newNodes.push(item); return true; } } if (type === "clear") { item.style.color = ""; item.style.webkitTextFillColor = ""; item.style.webkitTextStroke = ""; item.style.textShadow = ""; item.style.backgroundColor = ""; item.style.fontSize = ""; } if (index === 0 && previousElement && previousElement.nodeType !== 3 && isArrayEqual(types, (previousElement.getAttribute("data-type") || "").split(" ")) && hasSameTextStyle(item, previousElement, textObj)) { previousIndex = previousElement.textContent.length; previousElement.innerHTML = previousElement.innerHTML + item.innerHTML; } else if (index === contents.childNodes.length - 1 && nextElement && nextElement.nodeType !== 3 && isArrayEqual(types, (nextElement.getAttribute("data-type") || "").split(" ")) && hasSameTextStyle(item, nextElement, textObj)) { nextIndex = item.textContent.length; nextElement.innerHTML = item.innerHTML + nextElement.innerHTML; } else { item.setAttribute("data-type", types.join(" ")); newNodes.push(item); } } } else { newNodes.push(item); } }); } else { // 添加 if (!this.element.classList.contains("fn__none") && type !== "text" && actionBtn) { actionBtn.classList.add("protyle-toolbar__item--current"); } if (selectText === "") { const inlineElement = document.createElement("span"); rangeTypes.push(type); // 遇到以下类型结尾不应继承 https://github.com/siyuan-note/siyuan/issues/7200 if (isEndSpan) { let removeIndex = 0; while (removeIndex < rangeTypes.length) { if (["inline-memo", "text", "block-ref", "file-annotation-ref", "a"].includes(rangeTypes[removeIndex])) { rangeTypes.splice(removeIndex, 1); } else { ++removeIndex; } } } inlineElement.setAttribute("data-type", [...new Set(rangeTypes)].join(" ")); inlineElement.textContent = Constants.ZWSP; // 在 a 元素中 ctrl+m 需继承其链接,也许不需要?没有用户反馈之前先保持现状 // if (type !== "a" && rangeTypes.includes("a") && nextElement.dataset.type.split(" ").includes("a") && // nextElement.isSameNode(previousElement)) { // inlineElement.setAttribute("data-href", nextElement.getAttribute("data-href")); // } setFontStyle(inlineElement, textObj); newNodes.push(inlineElement); } else { // https://github.com/siyuan-note/siyuan/issues/7477 // https://github.com/siyuan-note/siyuan/issues/8825 if (type === "block-ref") { while (contents.childNodes.length > 1) { contents.childNodes[0].remove(); } } contents.childNodes.forEach((item: HTMLElement, index) => { let removeText = ""; if (item.nodeType === 3) { if (index === 0 && previousElement && previousElement.nodeType !== 3 && type === previousElement.getAttribute("data-type") && hasSameTextStyle(item, previousElement, textObj)) { previousIndex = previousElement.textContent.length; previousElement.innerHTML = previousElement.innerHTML + item.textContent; } else if (index === contents.childNodes.length - 1 && nextElement && nextElement.nodeType !== 3 && type === nextElement.getAttribute("data-type") && hasSameTextStyle(item, nextElement, textObj)) { nextIndex = item.textContent.length; nextElement.innerHTML = item.textContent + nextElement.innerHTML; } else if ( // 图片会有零宽空格,但图片不进行处理 https://github.com/siyuan-note/siyuan/issues/12840 item.textContent !== Constants.ZWSP || // tag 会有零宽空格 https://github.com/siyuan-note/siyuan/issues/12922 (item.textContent === Constants.ZWSP && !rangeTypes.includes("img"))) { // ZWSP spin 后会在行内元素外 https://github.com/siyuan-note/siyuan/issues/13871 if (item.textContent.startsWith(Constants.ZWSP)) { newNodes.push(document.createTextNode(Constants.ZWSP)); item.textContent = item.textContent.substring(1); } // https://github.com/siyuan-note/siyuan/issues/14204 while (item.textContent.endsWith("\n")) { item.textContent = item.textContent.substring(0, item.textContent.length - 1); removeText += "\n"; } const inlineElement = document.createElement("span"); inlineElement.setAttribute("data-type", type); inlineElement.textContent = item.textContent; setFontStyle(inlineElement, textObj); if (type === "text" && !inlineElement.getAttribute("style")) { newNodes.push(item); } else { newNodes.push(inlineElement); } } else { newNodes.push(item); } } else { let types = (item.getAttribute("data-type") || "").split(" "); for (let i = 0; i < types.length; i++) { // "backslash", "virtual-block-ref", "search-mark" 只能单独存在 if (["backslash", "virtual-block-ref", "search-mark"].includes(types[i])) { types.splice(i, 1); i--; } } if (!types.includes("img")) { types.push(type); } // 以下行内元素需用 ZWSP 开头 https://github.com/siyuan-note/siyuan/issues/13871 if ((types.includes("code") || types.includes("tag") || types.includes("kbd")) && !item.textContent.startsWith(Constants.ZWSP)) { item.insertAdjacentText("afterbegin", Constants.ZWSP); } // 上标和下标不能同时存在 https://github.com/siyuan-note/insider/issues/1049 if (type === "sub" && types.includes("sup")) { types.find((item, index) => { if (item === "sup") { types.splice(index, 1); toolbarElement.querySelector('[data-type="sup"]').classList.remove("protyle-toolbar__item--current"); return true; } }); } else if (type === "sup" && types.includes("sub")) { types.find((item, index) => { if (item === "sub") { types.splice(index, 1); toolbarElement.querySelector('[data-type="sub"]').classList.remove("protyle-toolbar__item--current"); return true; } }); } else if (type === "block-ref" && (types.includes("a") || types.includes("file-annotation-ref"))) { // 虚拟引用和链接/标注不能同时存在 types.find((item, index) => { if (item === "a" || item === "file-annotation-ref") { types.splice(index, 1); return true; } }); } else if (type === "a" && (types.includes("block-ref") || types.includes("file-annotation-ref"))) { // 链接和引用/标注不能同时存在 types.find((item, index) => { if (item === "block-ref" || item === "file-annotation-ref") { types.splice(index, 1); return true; } }); } else if (type === "file-annotation-ref" && (types.includes("block-ref") || types.includes("a"))) { // 引用和链接/标注不能同时存在 types.find((item, index) => { if (item === "block-ref" || item === "a") { types.splice(index, 1); return true; } }); } else if (type === "inline-memo" && types.includes("inline-math")) { // 数学公式和备注不能同时存在 types.find((item, index) => { if (item === "inline-math") { types.splice(index, 1); return true; } }); if (item.querySelector(".katex")) { // 选中完整的数学公式才进行备注 https://github.com/siyuan-note/siyuan/issues/13667 item.textContent = item.getAttribute("data-content"); } } else if (type === "inline-math" && types.includes("inline-memo")) { // 数学公式和备注不能同时存在 types.find((item, index) => { if (item === "inline-memo") { types.splice(index, 1); return true; } }); } types = [...new Set(types)]; if (types.includes("block-ref") && item.getAttribute("data-subtype") === "d") { // https://github.com/siyuan-note/siyuan/issues/14299 if (previousElement.getAttribute("data-id") === item.getAttribute("data-id")) { previousElement.setAttribute("data-subtype", "s"); item.setAttribute("data-subtype", "s"); } if (nextElement.getAttribute("data-id") === item.getAttribute("data-id")) { nextElement.setAttribute("data-subtype", "s"); item.setAttribute("data-subtype", "s"); } } if (index === 0 && previousElement && previousElement.nodeType !== 3 && isArrayEqual(types, (previousElement.getAttribute("data-type") || "").split(" ")) && hasSameTextStyle(item, previousElement, textObj)) { previousIndex = previousElement.textContent.length; previousElement.innerHTML = previousElement.innerHTML + item.innerHTML; } else if (index === contents.childNodes.length - 1 && nextElement && nextElement.nodeType !== 3 && isArrayEqual(types, (nextElement.getAttribute("data-type") || "").split(" ")) && hasSameTextStyle(item, nextElement, textObj)) { nextIndex = item.textContent.length; nextElement.innerHTML = item.innerHTML + nextElement.innerHTML; } else if (item.tagName !== "BR" && item.tagName !== "IMG") { item.setAttribute("data-type", types.join(" ")); setFontStyle(item, textObj); if (types.includes("text") && !item.getAttribute("style")) { if (types.length === 1) { const tempText = document.createTextNode(item.textContent); newNodes.push(tempText); } else { types.splice(types.indexOf("text"), 1); item.setAttribute("data-type", types.join(" ")); newNodes.push(item); } } else { newNodes.push(item); } } else { newNodes.push(item); } } if (removeText) { newNodes.push(document.createTextNode(removeText)); } }); } } if (this.range.startContainer.nodeType !== 3 && (this.range.startContainer as HTMLElement).tagName === "SPAN" && this.range.startContainer.isSameNode(this.range.endContainer) && !isEndSpan) { // 切割元素 const startContainer = this.range.startContainer as HTMLElement; const afterElement = document.createElement("span"); const attributes = startContainer.attributes; for (let i = 0; i < attributes.length; i++) { afterElement.setAttribute(attributes[i].name, attributes[i].value); } this.range.setEnd(startContainer.lastChild, startContainer.lastChild.textContent.length); afterElement.append(this.range.extractContents()); startContainer.after(afterElement); // https://github.com/siyuan-note/siyuan/issues/13871#issuecomment-2662855319 const firstTypes = startContainer.getAttribute("data-type").split(" "); if (firstTypes.includes("code") || firstTypes.includes("tag") || firstTypes.includes("kbd")) { afterElement.insertAdjacentText("beforebegin", Constants.ZWSP + Constants.ZWSP); afterElement.insertAdjacentText("afterbegin", Constants.ZWSP); this.range.setStart(afterElement.previousSibling, 1); } else { this.range.setStartBefore(afterElement); } this.range.collapse(true); } for (let i = 0; i < newNodes.length; i++) { const currentNewNode = newNodes[i] as HTMLElement; const nextNewNode = newNodes[i + 1] as HTMLElement; const currentType = currentNewNode.nodeType !== 3 ? (currentNewNode.getAttribute("data-type") || "") : ""; if (currentNewNode.nodeType !== 3 && nextNewNode && nextNewNode.nodeType !== 3 && nextNewNode.tagName === currentNewNode.tagName && // 表格内多个换行 https://github.com/siyuan-note/siyuan/issues/12300 currentNewNode.tagName !== "BR" && isArrayEqual((nextNewNode.getAttribute("data-type") || "").split(" "), currentType.split(" ")) && currentNewNode.getAttribute("data-id") === nextNewNode.getAttribute("data-id") && currentNewNode.getAttribute("data-subtype") === nextNewNode.getAttribute("data-subtype") && currentNewNode.style.color === nextNewNode.style.color && currentNewNode.style.webkitTextFillColor === nextNewNode.style.webkitTextFillColor && currentNewNode.style.webkitTextStroke === nextNewNode.style.webkitTextStroke && currentNewNode.style.textShadow === nextNewNode.style.textShadow && currentNewNode.style.fontSize === nextNewNode.style.fontSize && currentNewNode.style.backgroundColor === nextNewNode.style.backgroundColor) { // 合并相同的 node if (currentType.indexOf("inline-math") > -1) { // 数学公式合并 data-content https://github.com/siyuan-note/siyuan/issues/6028 nextNewNode.setAttribute("data-content", currentNewNode.getAttribute("data-content") + nextNewNode.getAttribute("data-content")); } else { // 测试不存在 https://ld246.com/article/1664454663564 情况,故移除引用合并限制 // 搜索结果引用被高亮隔断需进行合并 https://github.com/siyuan-note/siyuan/issues/7588 nextNewNode.innerHTML = currentNewNode.innerHTML + nextNewNode.innerHTML; // 如果为备注时,合并备注内容 if (currentType.indexOf("inline-memo") > -1) { nextNewNode.setAttribute("data-inline-memo-content", (currentNewNode.getAttribute("data-inline-memo-content") || "") + (nextNewNode.getAttribute("data-inline-memo-content") || "")); } } newNodes.splice(i, 1); i--; } else { this.range.insertNode(currentNewNode); if (currentNewNode.nodeType === 1 && ["code", "tag", "kbd"].includes(type)) { // 添加为 span https://github.com/siyuan-note/siyuan/issues/6155 const currentPreviousSibling = hasPreviousSibling(currentNewNode); if (!currentPreviousSibling || currentPreviousSibling.textContent.endsWith("\n")) { currentNewNode.before(document.createTextNode(Constants.ZWSP)); } if (!currentNewNode.textContent.startsWith(Constants.ZWSP)) { currentNewNode.textContent = Constants.ZWSP + currentNewNode.textContent; } const currentNextSibling = hasNextSibling(currentNewNode); if (!currentNextSibling || (currentNextSibling && ( currentNextSibling.nodeType !== 3 || (currentNextSibling.nodeType === 3 && !currentNextSibling.textContent.startsWith(Constants.ZWSP))) ) ) { currentNewNode.after(document.createTextNode(Constants.ZWSP)); } } else if (currentNewNode.nodeType === 3 && ["code", "tag", "kbd", "clear"].includes(type)) { const currentPreviousSibling = hasPreviousSibling(currentNewNode) as HTMLElement; let previousIsCTK = false; if (currentPreviousSibling) { if (currentPreviousSibling.nodeType === 1) { const currentPreviousSiblingTypes = currentPreviousSibling.dataset.type.split(" "); if (currentPreviousSiblingTypes.includes("code") || currentPreviousSiblingTypes.includes("tag") || currentPreviousSiblingTypes.includes("kbd")) { previousIsCTK = true; } } else if (currentPreviousSibling.textContent.endsWith(Constants.ZWSP)) { currentPreviousSibling.textContent = currentPreviousSibling.textContent.substring(0, currentPreviousSibling.textContent.length - 1); } } const currentNextSibling = hasNextSibling(currentNewNode) as HTMLElement; let nextIsCTK = false; if (currentNextSibling) { if (currentNextSibling.nodeType === 1) { const currentNextSiblingTypes = currentNextSibling.dataset.type.split(" "); if (currentNextSiblingTypes.includes("code") || currentNextSiblingTypes.includes("tag") || currentNextSiblingTypes.includes("kbd")) { nextIsCTK = true; } } else if (currentNextSibling.textContent.startsWith(Constants.ZWSP)) { currentNextSibling.textContent = currentNextSibling.textContent.substring(1); } } if (currentNewNode) { if (previousIsCTK) { if (!currentNewNode.textContent.startsWith(Constants.ZWSP)) { currentNewNode.textContent = Constants.ZWSP + currentNewNode.textContent; } } else if (currentNewNode.textContent.startsWith(Constants.ZWSP)) { currentNewNode.textContent = currentNewNode.textContent.substring(1); } if (nextIsCTK) { if (!currentNextSibling.textContent.startsWith(Constants.ZWSP)) { currentNextSibling.textContent = Constants.ZWSP + currentNextSibling.textContent; } } else if (currentNewNode.textContent.endsWith(Constants.ZWSP)) { currentNewNode.textContent = currentNewNode.textContent.substring(0, currentNewNode.textContent.length - 1); } } } this.range.collapse(false); } } if (previousElement) { if (previousElement.nodeType !== 3 && previousElement.textContent === Constants.ZWSP) { // https://github.com/siyuan-note/siyuan/issues/7548 previousElement.remove(); } else { this.mergeNode(previousElement.childNodes); } } if (nextElement) { this.mergeNode(nextElement.childNodes); } if (previousIndex) { this.range.setStart(previousElement.firstChild, previousIndex); } else if (newNodes.length > 0) { if (newNodes[0].nodeType !== 3 && (newNodes[0] as HTMLElement).getAttribute("data-type") === "inline-math") { // 数学公式后面处理 } else { if (newNodes[0].firstChild) { if (newNodes[0].firstChild.textContent === Constants.ZWSP) { // 新建元素时光标消失 https://github.com/siyuan-note/siyuan/issues/6481 // 新建元素粘贴后元素消失 https://ld246.com/article/1665556907936 this.range.setStart(newNodes[0].firstChild, 1); } else { this.range.setStart(newNodes[0].firstChild, 0); } } else if (newNodes[0].nodeType === 3) { this.range.setStart(newNodes[0], 0); } else { this.range.setStartBefore(newNodes[0]); } } } else if (nextElement) { // aaa**bbb** 选中 aaa 加粗 this.range.setStart(nextElement.firstChild, 0); } if (nextIndex) { this.range.setEnd(nextElement.lastChild, nextIndex); } else if (newNodes.length > 0) { const lastNewNode = newNodes[newNodes.length - 1]; if (lastNewNode.nodeType !== 3 && (lastNewNode as HTMLElement).getAttribute("data-type").indexOf("inline-math") > -1) { const mathPreviousSibling = hasPreviousSibling(lastNewNode); if (mathPreviousSibling && mathPreviousSibling.nodeType === 3) { this.range.setStart(mathPreviousSibling, mathPreviousSibling.textContent.length); } else { this.range.setStartBefore(lastNewNode); } const mathNextSibling = hasNextSibling(lastNewNode); if (mathNextSibling && mathNextSibling.nodeType === 3) { // https://github.com/siyuan-note/siyuan/issues/6065 this.range.setEnd(mathNextSibling, 0); } else { this.range.setEndAfter(lastNewNode); } } else { if (lastNewNode.lastChild) { if (lastNewNode.lastChild.textContent === Constants.ZWSP) { // 新建元素时光标消失 https://github.com/siyuan-note/siyuan/issues/6481 // 新建元素粘贴后元素消失 https://ld246.com/article/1665556907936 this.range.collapse(true); } else { this.range.setEnd(lastNewNode.lastChild, lastNewNode.lastChild.textContent.length); } } else if (lastNewNode.nodeType === 3) { this.range.setEnd(lastNewNode, lastNewNode.textContent.length); if (lastNewNode.textContent === Constants.ZWSP) { // 粗体后取消粗体光标不存在 https://github.com/siyuan-note/insider/issues/1056 this.range.collapse(false); } } else { // eg: 表格中有3行时,选中第二行三级,多次加粗会增加换行 this.range.setEndAfter(lastNewNode); } } } else if (previousElement) { // **aaa**bbb 选中 bbb 加粗 // 需进行 mergeNode ,否用 alt+x 为相同颜色 aaabbb 中的 bbb 再次赋值后无法选中 this.range.setEnd(previousElement.firstChild, previousElement.firstChild.textContent.length); } let needFocus = true; if (type === "inline-math") { mathRender(nodeElement); if (selectText === "") { protyle.toolbar.showRender(protyle, newNodes[0] as HTMLElement, undefined, html); return; } } else if (type === "inline-memo") { protyle.toolbar.showRender(protyle, newNodes[0] as HTMLElement, newNodes as Element[], html); return; } else if (type === "block-ref") { this.range.collapse(false); } else if (type === "a") { const aElement = newNodes[0] as HTMLElement; if (aElement.textContent.replace(Constants.ZWSP, "") === "" || !aElement.getAttribute("data-href")) { needFocus = false; linkMenu(protyle, aElement, aElement.getAttribute("data-href") ? true : false); } else { this.range.collapse(false); } } nodeElement.setAttribute("updated", dayjs().format("YYYYMMDDHHmmss")); updateTransaction(protyle, nodeElement.getAttribute("data-node-id"), nodeElement.outerHTML, html); const wbrElement = nodeElement.querySelector("wbr"); if (wbrElement) { wbrElement.remove(); } if (needFocus) { focusByRange(this.range); } } public showRender(protyle: IProtyle, renderElement: Element, updateElements?: Element[], oldHTML?: string) { const nodeElement = hasClosestBlock(renderElement); if (!nodeElement) { return; } hideElements(["hint"], protyle); window.siyuan.menus.menu.remove(); const id = nodeElement.getAttribute("data-node-id"); const types = (renderElement.getAttribute("data-type") || "").split(" "); const html = oldHTML || nodeElement.outerHTML; let title = "HTML"; let placeholder = ""; const isInlineMemo = types.includes("inline-memo"); switch (renderElement.getAttribute("data-subtype")) { case "abc": title = window.siyuan.languages.staff; break; case "echarts": title = window.siyuan.languages.chart; break; case "flowchart": title = "Flow Chart"; break; case "graphviz": title = "Graphviz"; break; case "mermaid": title = "Mermaid"; break; case "mindmap": placeholder = `- foo - bar - baz`; title = window.siyuan.languages.mindmap; break; case "plantuml": title = "UML"; break; case "math": if (types.includes("NodeMathBlock")) { title = window.siyuan.languages.math; } else { title = window.siyuan.languages["inline-math"]; } break; } if (types.includes("NodeBlockQueryEmbed")) { title = window.siyuan.languages.blockEmbed; } else if (isInlineMemo) { title = window.siyuan.languages.memo; } const isPin = this.subElement.querySelector('[data-type="pin"]')?.getAttribute("aria-label") === window.siyuan.languages.unpin; const pinData: IObject = {}; if (isPin) { const textElement = this.subElement.querySelector(".b3-text-field") as HTMLTextAreaElement; pinData.styleH = textElement.style.height; pinData.styleW = textElement.style.width; } else { this.subElement.style.width = ""; this.subElement.style.padding = "0"; } this.subElement.innerHTML = `
时只能保存第一个 https://github.com/siyuan-note/siyuan/issues/5732
if (types.includes("NodeHTMLBlock")) {
const tempElement = document.createElement("template");
tempElement.innerHTML = protyle.lute.SpinBlockDOM(nodeElement.outerHTML);
if (tempElement.content.childElementCount > 1) {
showMessage(window.siyuan.languages.htmlBlockTip);
}
}
updateTransaction(protyle, id, nodeElement.outerHTML, html);
};
this.subElement.style.zIndex = (++window.siyuan.zIndex).toString();
this.subElement.classList.remove("fn__none");
const nodeRect = renderElement.getBoundingClientRect();
this.element.classList.add("fn__none");
if (isPin) {
textElement.style.width = pinData.styleW;
textElement.style.height = pinData.styleH;
} else {
autoHeight();
}
if (!protyle.disabled) {
textElement.select();
}
protyle.app.plugins.forEach(item => {
item.eventBus.emit("open-noneditableblock", {
protyle,
toolbar: this,
blockElement: nodeElement,
renderElement,
});
});
}
public showCodeLanguage(protyle: IProtyle, languageElement: HTMLElement) {
const nodeElement = hasClosestBlock(languageElement);
if (!nodeElement) {
return;
}
hideElements(["hint"], protyle);
window.siyuan.menus.menu.remove();
this.range = getEditorRange(nodeElement);
const id = nodeElement.getAttribute("data-node-id");
let oldHtml = nodeElement.outerHTML;
let html = `${window.siyuan.languages.clear}`;
const hljsLanguages = Constants.ALIAS_CODE_LANGUAGES.concat(window.hljs?.listLanguages() ?? []).sort();
hljsLanguages.forEach((item, index) => {
html += `${item}`;
});
this.subElement.style.width = "";
this.subElement.style.padding = "";
this.subElement.innerHTML = `
${html}
`;
const listElement = this.subElement.lastElementChild.lastElementChild as HTMLElement;
const inputElement = this.subElement.querySelector("input");
inputElement.addEventListener("keydown", (event: KeyboardEvent) => {
event.stopPropagation();
if (event.isComposing) {
return;
}
upDownHint(listElement, event);
if (event.key === "Enter") {
oldHtml = this.updateLanguage(languageElement, protyle, id, nodeElement, oldHtml, this.subElement.querySelector(".b3-list-item--focus").textContent);
event.preventDefault();
event.stopPropagation();
return;
}
if (event.key === "Escape") {
this.subElement.classList.add("fn__none");
focusByRange(this.range);
}
});
inputElement.addEventListener("input", (event) => {
const lowerCaseValue = inputElement.value.toLowerCase();
const matchLanguages = hljsLanguages.filter(item => item.includes(lowerCaseValue));
let html = "";
// sort
let matchInput = false;
matchLanguages.sort((a, b) => {
if (a.startsWith(lowerCaseValue) && b.startsWith(lowerCaseValue)) {
if (a.length < b.length) {
return -1;
} else if (a.length === b.length) {
return 0;
} else {
return 1;
}
} else if (a.startsWith(lowerCaseValue)) {
return -1;
} else if (b.startsWith(lowerCaseValue)) {
return 1;
} else {
return 0;
}
}).forEach((item) => {
if (inputElement.value === item) {
matchInput = true;
}
html += `${item.replace(lowerCaseValue, "" + lowerCaseValue + "")}`;
});
if (inputElement.value.trim() && !matchInput) {
html = `${escapeHtml(inputElement.value.replace(/`| /g, "_"))}${html}`;
}
html = `${window.siyuan.languages.clear}` + html;
listElement.innerHTML = html;
if (listElement.firstElementChild.nextElementSibling) {
listElement.firstElementChild.nextElementSibling.classList.add("b3-list-item--focus");
} else {
listElement.firstElementChild.classList.add("b3-list-item--focus");
}
event.stopPropagation();
});
listElement.addEventListener("click", (event) => {
const target = event.target as HTMLElement;
const listElement = hasClosestByClassName(target, "b3-list-item");
if (!listElement) {
return;
}
oldHtml = this.updateLanguage(languageElement, protyle, id, nodeElement, oldHtml, listElement.textContent);
});
this.subElement.style.zIndex = (++window.siyuan.zIndex).toString();
this.subElement.classList.remove("fn__none");
this.subElementCloseCB = undefined;
/// #if !MOBILE
const nodeRect = languageElement.getBoundingClientRect();
setPosition(this.subElement, nodeRect.left, nodeRect.bottom, nodeRect.height);
/// #else
setPosition(this.subElement, 0, 0);
/// #endif
this.element.classList.add("fn__none");
inputElement.select();
}
public showTpl(protyle: IProtyle, nodeElement: HTMLElement, range: Range) {
this.range = range;
hideElements(["hint"], protyle);
window.siyuan.menus.menu.remove();
this.subElement.style.width = "";
this.subElement.style.padding = "";
this.subElement.innerHTML = `
`;
const listElement = this.subElement.querySelector(".b3-list");
resizeSide(this.subElement.querySelector(".toolbarResize"), listElement.parentElement);
const previewElement = this.subElement.firstElementChild.lastElementChild;
let previewPath: string;
listElement.addEventListener("mouseover", (event) => {
const target = event.target as HTMLElement;
const hoverItemElement = hasClosestByClassName(target, "b3-list-item");
if (!hoverItemElement) {
return;
}
const currentPath = hoverItemElement.getAttribute("data-value");
if (previewPath === currentPath) {
return;
}
previewPath = currentPath;
previewTemplate(previewPath, previewElement, protyle.block.parentID);
event.stopPropagation();
});
const inputElement = this.subElement.querySelector("input");
inputElement.addEventListener("keydown", (event: KeyboardEvent) => {
event.stopPropagation();
if (event.isComposing) {
return;
}
const isEmpty = !this.subElement.querySelector(".b3-list-item");
if (!isEmpty) {
const currentElement = upDownHint(listElement, event);
if (currentElement) {
const currentPath = currentElement.getAttribute("data-value");
if (previewPath === currentPath) {
return;
}
previewPath = currentPath;
previewTemplate(previewPath, previewElement, protyle.block.parentID);
}
}
if (event.key === "Enter") {
if (!isEmpty) {
hintRenderTemplate(decodeURIComponent(this.subElement.querySelector(".b3-list-item--focus").getAttribute("data-value")), protyle, nodeElement);
} else {
focusByRange(this.range);
}
this.subElement.classList.add("fn__none");
event.preventDefault();
} else if (event.key === "Escape") {
this.subElement.classList.add("fn__none");
focusByRange(this.range);
}
});
inputElement.addEventListener("input", (event) => {
event.stopPropagation();
fetchPost("/api/search/searchTemplate", {
k: inputElement.value,
}, (response) => {
let searchHTML = "";
response.data.blocks.forEach((item: { path: string, content: string }, index: number) => {
searchHTML += `${item.content}`;
});
listElement.innerHTML = searchHTML || `