import {Constants} from "../../constants"; import {hasClosestBlock, hasClosestByAttribute, hasClosestByClassName, hasClosestByMatchTag} from "../util/hasClosest"; import { focusBlock, focusByRange, focusByWbr, getEditorRange, getSelectionOffset, getSelectionPosition } from "../util/selection"; import {genHintItemHTML, hintEmbed, hintRef, hintSlash} from "./extend"; import {getSavePath} from "../../util/newFile"; import {upDownHint} from "../../util/upDownHint"; import {setPosition} from "../../util/setPosition"; import {getContenteditableElement, hasNextSibling, hasPreviousSibling} from "../wysiwyg/getBlock"; import {transaction, updateTransaction} from "../wysiwyg/transaction"; import {insertHTML} from "../util/insertHTML"; import {highlightRender} from "../render/highlightRender"; import {assetMenu, imgMenu} from "../../menus/protyle"; import {hideElements} from "../ui/hideElements"; import {fetchPost} from "../../util/fetch"; import {getDisplayName, pathPosix} from "../../util/pathName"; import {addEmoji, filterEmoji, lazyLoadEmoji, lazyLoadEmojiImg, unicode2Emoji} from "../../emoji"; import {blockRender} from "../render/blockRender"; import {uploadFiles} from "../upload"; /// #if !MOBILE import {openFileById} from "../../editor/util"; /// #endif import {openMobileFileById} from "../../mobile/editor"; import {processRender} from "../util/processCode"; import {AIChat} from "../../ai/chat"; import {isMobile} from "../../util/functions"; import {isIPhone, isNotCtrl, isOnlyMeta} from "../util/compatibility"; import {avRender} from "../render/av/render"; import {genIconHTML} from "../render/util"; export class Hint { public timeId: number; public element: HTMLDivElement; public enableSlash = true; private enableEmoji = true; public enableExtend = false; public splitChar = ""; public lastIndex = -1; private source: THintSource; constructor(protyle: IProtyle) { this.element = document.createElement("div"); this.element.setAttribute("data-close", "false"); // height 402 根据 .emojis max-height+8 得来 this.element.setAttribute("style", `width:${Math.max(protyle.element.clientWidth / 2, 320)}px;`); this.element.className = "protyle-hint b3-list b3-list--background fn__none"; this.element.addEventListener("click", (event) => { const eventTarget = event.target as HTMLElement; if (eventTarget.tagName === "INPUT") { event.stopPropagation(); return; } const btnElement = hasClosestByMatchTag(eventTarget, "BUTTON"); if (btnElement && !btnElement.classList.contains("emojis__item") && !btnElement.classList.contains("emojis__type")) { if (this.source !== "search") { this.fill(decodeURIComponent(btnElement.getAttribute("data-value")), protyle, true, isOnlyMeta(event)); } else { setTimeout(() => { this.fill(decodeURIComponent(btnElement.getAttribute("data-value")), protyle, true, isNotCtrl(event)); }, 148); // 划选引用点击,需先重置 range } focusByRange(protyle.toolbar.range); event.preventDefault(); event.stopPropagation(); // https://github.com/siyuan-note/siyuan/issues/3710 return; } const emojisContentElement = this.element.querySelector(".emojis__panel"); const typeElement = hasClosestByClassName(eventTarget, "emojis__type"); if (typeElement) { const titleElement = emojisContentElement.querySelector(`[data-type="${typeElement.getAttribute("data-type")}"]`) as HTMLElement; if (titleElement) { const index = titleElement.nextElementSibling.getAttribute("data-index"); if (index) { let html = ""; window.siyuan.emojis[parseInt(index)].items.forEach(emoji => { html += ``; }); titleElement.nextElementSibling.innerHTML = html; titleElement.nextElementSibling.removeAttribute("data-index"); } emojisContentElement.scrollTo({ top: titleElement.offsetTop, // behavior: "smooth" 不能使用,否则无法定位 }); } return; } const emojiElement = hasClosestByClassName(eventTarget, "emojis__item"); if (emojiElement) { const unicode = emojiElement.getAttribute("data-unicode"); if (this.element.querySelectorAll(".emojis__title").length > 2) { // /emoji 后会自动添加冒号,导致 range 无法计算,因此不依赖 this.fill const range = getSelection().getRangeAt(0); if (range.endContainer.nodeType !== 3) { range.endContainer.childNodes[range.endOffset - 1]?.remove(); } else if (range.endContainer.textContent === ":") { // iphone range.endContainer.textContent = ""; } addEmoji(unicode); let emoji; if (unicode.indexOf(".") > -1) { emoji = `:${unicode.split(".")[0]}: `; } else { emoji = unicode2Emoji(unicode) + " "; } insertHTML(protyle.lute.SpinBlockDOM(emoji), protyle, false, true); this.element.classList.add("fn__none"); } else { this.fill(unicode, protyle); } } }); } public render(protyle: IProtyle) { if (!window.getSelection().focusNode) { this.element.classList.add("fn__none"); clearTimeout(this.timeId); return; } if (!this.enableExtend) { clearTimeout(this.timeId); return; } protyle.toolbar.range = getSelection().getRangeAt(0); // 粘贴后 range.startContainer 为空 https://github.com/siyuan-note/siyuan/issues/7360 if (protyle.toolbar.range.startContainer.nodeType === 3 && protyle.toolbar.range.startContainer.textContent === "") { const lastSibling = hasPreviousSibling(protyle.toolbar.range.startContainer) as Text; if (lastSibling && lastSibling.nodeType === 3) { if (lastSibling.wholeText !== lastSibling.textContent) { let previousSibling = lastSibling.previousSibling; while (previousSibling && previousSibling.nodeType === 3) { if (previousSibling.textContent === "") { previousSibling = previousSibling.previousSibling; previousSibling.nextSibling.remove(); } else { lastSibling.textContent = previousSibling.textContent + lastSibling.textContent; previousSibling.remove(); break; } } } protyle.toolbar.range.setStart(lastSibling, lastSibling.textContent.length); protyle.toolbar.range.collapse(true); } } const start = getSelectionOffset(protyle.toolbar.range.startContainer, protyle.wysiwyg.element).start; const currentLineValue = protyle.toolbar.range.startContainer.textContent.substring(0, start) || ""; const key = this.getKey(currentLineValue, protyle.options.hint.extend); if (typeof key === "undefined" || hasClosestByAttribute(protyle.toolbar.range.startContainer, "data-type", "code") || hasClosestByAttribute(protyle.toolbar.range.startContainer, "data-type", "NodeCodeBlock")) { this.element.classList.add("fn__none"); clearTimeout(this.timeId); return; } // https://github.com/siyuan-note/siyuan/issues/7933 if (this.splitChar === "#") { const blockElement = hasClosestBlock(protyle.toolbar.range.startContainer); if (blockElement && blockElement.getAttribute("data-type") === "NodeHeading") { const blockIndex = getSelectionOffset(protyle.toolbar.range.startContainer, blockElement).start; if (blockElement.textContent.startsWith("#".repeat(blockIndex))) { this.element.classList.add("fn__none"); clearTimeout(this.timeId); return; } } } if (this.splitChar === ":") { clearTimeout(this.timeId); if (key) { this.genEmojiHTML(protyle, key); } else { this.element.classList.add("fn__none"); } return; } // https://github.com/siyuan-note/siyuan/issues/5083 if (this.splitChar === "/" || this.splitChar === "、") { clearTimeout(this.timeId); if (this.enableSlash && !isMobile()) { this.genHTML(hintSlash(key, protyle), protyle, false, "hint"); } return; } protyle.options.hint.extend.forEach((item) => { if (item.key === this.splitChar) { clearTimeout(this.timeId); this.timeId = window.setTimeout(() => { this.genHTML(item.hint(key, protyle, "hint"), protyle, false, "hint"); }, protyle.options.hint.delay); } }); } public genLoading(protyle: IProtyle) { if (this.element.classList.contains("fn__none")) { this.element.innerHTML = '
'; this.element.classList.remove("fn__none"); const textareaPosition = getSelectionPosition(protyle.wysiwyg.element); setPosition(this.element, textareaPosition.left, textareaPosition.top + 26, 30); } else { this.element.insertAdjacentHTML("beforeend", '
'); } } public bindUploadEvent(protyle: IProtyle, element: HTMLElement) { const uploadElement = element.querySelector('input[type="file"]'); if (uploadElement) { uploadElement.addEventListener("change", (event: InputEvent & { target: HTMLInputElement }) => { if (event.target.files.length === 0) { return; } const range = getEditorRange(protyle.wysiwyg.element); if (this.lastIndex > -1) { range.setStart(range.startContainer, this.lastIndex); } range.deleteContents(); uploadFiles(protyle, event.target.files, event.target); hideElements(["hint", "toolbar"], protyle); }); } } private getHTMLByData(data: IHintData[]) { let hintsHTML = '
'; if (this.source !== "hint") { hintsHTML = '
'; } data.forEach((hintData, i) => { // https://github.com/siyuan-note/siyuan/issues/1229 提示时,新建文件不应默认选中 let focusClass = ""; if ((i === 1 && data[i].focus) || (i === 0 && (data.length === 1 || !data[1].focus))) { focusClass = " b3-list-item--focus"; } if (hintData.html === "separator") { hintsHTML += '
'; } else { hintsHTML += ``; } }); return `${hintsHTML}
`; } public genHTML(data: IHintData[], protyle: IProtyle, hide = false, source: THintSource) { this.source = source; if (data.length === 0) { if (!this.element.querySelector(".fn__loading") || hide) { this.element.classList.add("fn__none"); } return; } this.element.innerHTML = this.getHTMLByData(data); this.element.classList.remove("fn__none"); // https://github.com/siyuan-note/siyuan/issues/4575 if (data[0].filter) { this.element.classList.add("hint--menu"); } else { this.element.classList.remove("hint--menu"); } this.element.style.width = Math.max(protyle.element.clientWidth / 2, 320) + "px"; if (this.source === "av") { const cellElement = hasClosestByClassName(protyle.toolbar.range.startContainer, "av__cell"); if (cellElement) { const cellRect = cellElement.getBoundingClientRect(); setPosition(this.element, cellRect.left, cellRect.bottom, cellRect.height); } } else { const textareaPosition = getSelectionPosition(protyle.wysiwyg.element); setPosition(this.element, textareaPosition.left, textareaPosition.top + 26, 30); } this.element.scrollTop = 0; this.bindUploadEvent(protyle, this.element); if (this.source !== "hint") { const searchElement = this.element.querySelector("input.b3-text-field") as HTMLInputElement; const oldValue = this.element.querySelector("mark")?.textContent || ""; searchElement.value = oldValue; searchElement.select(); searchElement.addEventListener("keydown", (event: KeyboardEvent) => { if (event.key !== "Meta" && event.key !== "Control") { // 需要冒泡以满足光标在块标位置时 ctrl 弹出悬浮层 event.stopPropagation(); } if (event.isComposing) { return; } upDownHint(this.element.lastElementChild, event); if (event.key === "Enter") { setTimeout(() => { this.fill(decodeURIComponent(this.element.querySelector(".b3-list-item--focus").getAttribute("data-value")), protyle, true, isNotCtrl(event)); }, 148); // 划选引用点击,需先重置 range focusByRange(protyle.toolbar.range); event.preventDefault(); } else if (event.key === "Escape") { this.element.classList.add("fn__none"); focusByRange(protyle.toolbar.range); } }); const nodeElement = protyle.toolbar.range ? hasClosestBlock(protyle.toolbar.range.startContainer) : false; searchElement.addEventListener("input", (event: InputEvent) => { if (event.isComposing) { return; } event.stopPropagation(); this.genSearchHTML(protyle, searchElement, nodeElement, oldValue); }); searchElement.addEventListener("compositionend", (event: InputEvent) => { event.stopPropagation(); this.genSearchHTML(protyle, searchElement, nodeElement, oldValue); }); } } private genSearchHTML(protyle: IProtyle, searchElement: HTMLInputElement, nodeElement: false | HTMLElement, oldValue: string) { this.element.lastElementChild.innerHTML = '
'; fetchPost("/api/search/searchRefBlock", { k: searchElement.value, id: nodeElement ? nodeElement.getAttribute("data-node-id") : protyle.block.parentID, beforeLen: Math.floor((Math.max(protyle.element.clientWidth / 2, 320) - 58) / 28.8), rootID: protyle.block.rootID, }, (response) => { let searchHTML = ""; if (response.data.newDoc) { const blockRefText = `((newFile "${oldValue}"${Constants.ZWSP}'${response.data.k}${Lute.Caret}'))`; searchHTML += ``; } response.data.blocks.forEach((item: IBlock, index: number) => { const blockRefHTML = `${oldValue}`; searchHTML += ``; }); if (searchHTML === "") { searchHTML = ``; } this.element.lastElementChild.innerHTML = searchHTML; setPosition(this.element, parseInt(this.element.style.left), parseInt(this.element.style.right)); }); } private genEmojiHTML(protyle: IProtyle, value = "") { if (value && !this.enableEmoji) { return; } const panelElement = this.element.querySelector(".emojis__panel"); if (panelElement) { panelElement.innerHTML = filterEmoji(value, 256); if (value) { panelElement.nextElementSibling.classList.add("fn__none"); } else { panelElement.nextElementSibling.classList.remove("fn__none"); } lazyLoadEmojiImg(panelElement); } else { this.element.innerHTML = `
${filterEmoji(value, 256)}
`; lazyLoadEmoji(this.element); lazyLoadEmojiImg(this.element); } const firstEmojiElement = this.element.querySelector(".emojis__item"); if (firstEmojiElement) { firstEmojiElement.classList.add("emojis__item--current"); this.element.classList.remove("fn__none"); this.element.style.width = Math.max(protyle.element.clientWidth / 2, 320) + "px"; const textareaPosition = getSelectionPosition(protyle.wysiwyg.element); setPosition(this.element, textareaPosition.left, textareaPosition.top + 26, 30); this.element.querySelector(".emojis__panel").scrollTop = 0; } else { this.element.classList.add("fn__none"); } } public fill(value: string, protyle: IProtyle, updateRange = true, refIsS = false) { hideElements(["hint", "toolbar"], protyle); if (updateRange && this.source !== "av") { protyle.toolbar.range = getEditorRange(protyle.wysiwyg.element); } const range = protyle.toolbar.range; let nodeElement = hasClosestBlock(protyle.toolbar.range.startContainer) as HTMLElement; if (!nodeElement) { return; } if (this.source === "av") { const cellElement = hasClosestByClassName(protyle.toolbar.range.startContainer, "av__cell"); if (!cellElement) { return; } const previousID = cellElement.dataset.blockId; const avID = nodeElement.getAttribute("data-av-id"); let tempElement = document.createElement("div"); tempElement.innerHTML = value.replace(//g, "").replace(/<\/mark>/g, ""); tempElement = tempElement.firstElementChild as HTMLDivElement; if (value.startsWith("((newFile ") && value.endsWith(`${Lute.Caret}'))`)) { const fileNames = value.substring(11, value.length - 4).split(`"${Constants.ZWSP}'`); const realFileName = fileNames.length === 1 ? fileNames[0] : fileNames[1]; getSavePath(protyle.path, protyle.notebookId, (pathString) => { fetchPost("/api/filetree/createDocWithMd", { notebook: protyle.notebookId, path: pathPosix().join(pathString, realFileName), parentID: protyle.block.rootID, markdown: "" }, response => { transaction(protyle, [{ action: "replaceAttrViewBlock", avID, previousID, nextID: response.data, isDetached: false, }], [{ action: "replaceAttrViewBlock", avID, previousID: response.data, nextID: previousID, isDetached: true, }]); }); }); } else { const sourceId = tempElement.getAttribute("data-id"); transaction(protyle, [{ action: "replaceAttrViewBlock", avID, previousID, nextID: sourceId, isDetached: false, }], [{ action: "replaceAttrViewBlock", avID, previousID: sourceId, nextID: previousID, isDetached: true, }]); } return; } this.enableExtend = false; let id = ""; if (nodeElement) { id = nodeElement.getAttribute("data-node-id"); } const html = nodeElement.outerHTML; // 自顶向下法新建文档后光标定位问题 https://github.com/siyuan-note/siyuan/issues/299 // QQ 拼音输入法自动补全需移除补全内容 https://github.com/siyuan-note/siyuan/issues/320 // 前后有标记符的情况 https://github.com/siyuan-note/siyuan/issues/2511 const endSplit = Constants.BLOCK_HINT_CLOSE_KEYS[this.splitChar]; if (Constants.BLOCK_HINT_KEYS.includes(this.splitChar) && endSplit && range.startContainer.nodeType === 3 && (range.startContainer as Text).wholeText.indexOf(endSplit) > -1 // 在包含 )) 的块中引用时会丢失字符 https://ld246.com/article/1679980200782 && (range.startContainer as Text).wholeText.indexOf(this.splitChar) > -1) { let matchEndChar = 0; let textNode = range.startContainer; while (textNode && matchEndChar < 2) { const index = textNode.textContent.indexOf(endSplit); const startIndex = textNode.textContent.indexOf(this.splitChar); if (index > -1 && (index < startIndex || startIndex < 0)) { matchEndChar = 2; range.setEnd(textNode, index + 2); break; } const indexOne = textNode.textContent.indexOf(endSplit.substr(1)); if (indexOne > -1) { matchEndChar += 1; } if (matchEndChar === 2) { range.setEnd(textNode, indexOne + 1); break; } textNode = textNode.nextSibling; } } if (this.lastIndex > -1) { range.setStart(range.startContainer, this.lastIndex); if (isIPhone()) { focusByRange(range); } } // 新建文件 if (Constants.BLOCK_HINT_KEYS.includes(this.splitChar) && value.startsWith("((newFile ") && value.endsWith(`${Lute.Caret}'))`)) { focusByRange(range); const fileNames = value.substring(11, value.length - 4).split(`"${Constants.ZWSP}'`); const realFileName = fileNames.length === 1 ? fileNames[0] : fileNames[1]; getSavePath(protyle.path, protyle.notebookId, (pathString) => { fetchPost("/api/filetree/createDocWithMd", { notebook: protyle.notebookId, path: pathPosix().join(pathString, realFileName), parentID: protyle.block.rootID, markdown: "" }, response => { protyle.toolbar.setInlineMark(protyle, "block-ref", "range", { type: "id", color: `${response.data}${Constants.ZWSP}${refIsS ? "s" : "d"}${Constants.ZWSP}${(refIsS ? fileNames[0] : realFileName).substring(0, window.siyuan.config.editor.blockRefDynamicAnchorTextMaxLen)}` }); }); }); return; } if (Constants.BLOCK_HINT_KEYS.includes(this.splitChar)) { if (value === "") { const editElement = getContenteditableElement(nodeElement); if (editElement.textContent === "") { editElement.innerHTML = ""; focusByWbr(editElement, range); } return; } let tempElement = document.createElement("div"); tempElement.innerHTML = value.replace(//g, "").replace(/<\/mark>/g, ""); tempElement = tempElement.firstElementChild as HTMLDivElement; if (refIsS) { const staticText = range.toString().replace(this.splitChar, ""); if (staticText) { tempElement.setAttribute("data-subtype", "s"); tempElement.innerText = staticText; } } else { tempElement.setAttribute("data-subtype", "d"); const dynamicTexts = tempElement.innerText.split(Constants.ZWSP); if (dynamicTexts.length === 2) { tempElement.innerText = dynamicTexts[1]; } } protyle.toolbar.setInlineMark(protyle, "block-ref", "range", { type: "id", color: `${tempElement.getAttribute("data-id")}${Constants.ZWSP}${tempElement.getAttribute("data-subtype")}${Constants.ZWSP}${tempElement.textContent}` }); return; } else if (this.splitChar === ":") { addEmoji(value); let emoji; if (value.indexOf(".") > -1) { emoji = `:${value.split(".")[0]}: `; } else { emoji = unicode2Emoji(value) + " "; } insertHTML(protyle.lute.SpinBlockDOM(emoji), protyle); } else if (["「「", "「『", "『「", "『『", "{{"].includes(this.splitChar) || this.splitChar === "#" || this.splitChar === ":") { if (value === "") { const editElement = getContenteditableElement(nodeElement); if (editElement.textContent === "") { editElement.innerHTML = ""; focusByWbr(editElement, range); } return; } insertHTML(protyle.lute.SpinBlockDOM(value), protyle, false, isMobile()); blockRender(protyle, protyle.wysiwyg.element); return; } else if (this.splitChar === "/" || this.splitChar === "、") { this.enableExtend = true; if (value === "((" || value === "{{") { if (value === "((") { hintRef("", protyle, "hint"); } else { hintEmbed("", protyle); } this.splitChar = value; this.lastIndex = 0; range.deleteContents(); const textNode = document.createTextNode(value); range.insertNode(textNode); range.setEnd(textNode, value.length); range.collapse(false); return; } else if (value === Constants.ZWSP) { range.deleteContents(); this.fixImageCursor(range); protyle.toolbar.showTpl(protyle, nodeElement, range); updateTransaction(protyle, id, nodeElement.outerHTML, html); return; } else if (value === Constants.ZWSP + 1) { range.deleteContents(); this.fixImageCursor(range); protyle.toolbar.showWidget(protyle, nodeElement, range); updateTransaction(protyle, id, nodeElement.outerHTML, html); return; } else if (value === Constants.ZWSP + 2) { range.deleteContents(); this.fixImageCursor(range); protyle.toolbar.range = range; const rangePosition = getSelectionPosition(nodeElement, range); assetMenu(protyle, {x: rangePosition.left, y: rangePosition.top + 26, w: 0, h: 26}); updateTransaction(protyle, id, nodeElement.outerHTML, html); return; } else if (value === Constants.ZWSP + 3) { range.deleteContents(); return; } else if (value === Constants.ZWSP + 4) { const newSubDocId = Lute.NewNodeID(); fetchPost("/api/filetree/createDoc", { notebook: protyle.notebookId, path: pathPosix().join(getDisplayName(protyle.path, false, true), newSubDocId + ".sy"), title: "Untitled", md: "" }, () => { insertHTML(`Untitled`, protyle); /// #if MOBILE openMobileFileById(protyle.app, newSubDocId, [Constants.CB_GET_HL, Constants.CB_GET_CONTEXT]); /// #else openFileById({ app: protyle.app, id: newSubDocId, action: [Constants.CB_GET_HL, Constants.CB_GET_CONTEXT] }); /// #endif }); return; } else if (value === Constants.ZWSP + 5) { range.deleteContents(); AIChat(protyle, nodeElement); return; } else if (Constants.INLINE_TYPE.includes(value)) { range.deleteContents(); focusByRange(range); if (["a", "block-ref", "inline-math", "inline-memo", "text"].includes(value)) { protyle.toolbar.element.querySelector(`[data-type="${value}"]`).dispatchEvent(new CustomEvent("click")); return; } protyle.toolbar.setInlineMark(protyle, value, "range"); return; } else if (value === "emoji") { range.deleteContents(); range.insertNode(document.createTextNode(":")); range.collapse(false); this.genEmojiHTML(protyle); return; } else if (value.indexOf("style") > -1) { range.deleteContents(); this.fixImageCursor(range); nodeElement.setAttribute("style", value.split(Constants.ZWSP)[1] || ""); updateTransaction(protyle, id, nodeElement.outerHTML, html); return; } else if (value.startsWith("plugin")) { protyle.app.plugins.find((plugin) => { const ids = value.split(Constants.ZWSP); if (ids[1] === plugin.name) { plugin.protyleSlash.find((slash) => { if (slash.id === ids[2]) { slash.callback(protyle.getInstance()); return true; } }); return true; } }); return; } else { range.deleteContents(); if (value !== "![]()") { this.fixImageCursor(range); } let textContent = value; if (value === "```") { textContent = value + window.siyuan.storage[Constants.LOCAL_CODELANG] + Lute.Caret + "\n```"; } const editableElement = getContenteditableElement(nodeElement); if (value === "![]()") { // https://github.com/siyuan-note/siyuan/issues/4586 1 let newHTML = ""; range.insertNode(document.createElement("wbr")); range.insertNode(document.createTextNode(value)); newHTML = protyle.lute.SpinBlockDOM(nodeElement.outerHTML); nodeElement.outerHTML = newHTML; nodeElement = protyle.wysiwyg.element.querySelector(`[data-node-id="${id}"]`); focusByWbr(nodeElement, range); updateTransaction(protyle, id, nodeElement.outerHTML, html); let imgElement: HTMLElement = range.startContainer.childNodes[range.startOffset - 1] as HTMLElement || range.startContainer as HTMLElement; if (imgElement && imgElement.nodeType !== 3 && imgElement.classList.contains("img")) { // 已经找到图片 } else if (imgElement.previousSibling?.nodeType !== 3 && (imgElement.previousSibling as HTMLElement).classList.contains("img")) { // https://github.com/siyuan-note/siyuan/issues/7540 imgElement = imgElement.previousSibling as HTMLElement; } else { Array.from(nodeElement.querySelectorAll(".img")).find((item: HTMLElement) => { if (item.querySelector("img").getAttribute("data-src") === "") { imgElement = item; return true; } }); } const rect = imgElement.getBoundingClientRect(); imgMenu(protyle, range, imgElement, { clientX: rect.left, clientY: rect.top }); return; } else if (editableElement.textContent === "" && nodeElement.getAttribute("data-type") === "NodeParagraph") { let newHTML = ""; if (value === "
") { newHTML = `
${genIconHTML()}
${Constants.ZWSP}
`; } else { editableElement.textContent = textContent; newHTML = protyle.lute.SpinBlockDOM(nodeElement.outerHTML); } nodeElement.outerHTML = newHTML; nodeElement = protyle.wysiwyg.element.querySelector(`[data-node-id="${id}"]`); // https://github.com/siyuan-note/siyuan/issues/6864 if (nodeElement.getAttribute("data-type") === "NodeTable") { nodeElement.querySelectorAll("colgroup col").forEach((item: HTMLElement) => { item.style.minWidth = "60px"; }); newHTML = nodeElement.outerHTML; } updateTransaction(protyle, id, newHTML, html); } else { let newHTML = protyle.lute.SpinBlockDOM(textContent); if (value === "
") { newHTML = `
${genIconHTML()}
${Constants.ZWSP}
`; } nodeElement.insertAdjacentHTML("afterend", newHTML); const oldHTML = nodeElement.outerHTML; const newId = newHTML.substr(newHTML.indexOf('data-node-id="') + 14, 22); nodeElement = protyle.wysiwyg.element.querySelector(`[data-node-id="${newId}"]`); // https://github.com/siyuan-note/siyuan/issues/6864 if (nodeElement.getAttribute("data-type") === "NodeTable") { nodeElement.querySelectorAll("colgroup col").forEach((item: HTMLElement) => { item.style.minWidth = "60px"; }); } transaction(protyle, [{ data: oldHTML, id, action: "update" }, { data: nodeElement.outerHTML, id: newId, previousID: id, action: "insert" }], [{ id: newId, action: "delete" }, { data: html, id, action: "update" }]); } if (value === "
" || value === "$$" || (value.indexOf("```") > -1 && value.length > 3)) { protyle.toolbar.showRender(protyle, nodeElement); processRender(nodeElement); } else if (value.startsWith("```")) { highlightRender(nodeElement); } else if (value.startsWith(" 2) { // /emoji 后会自动添加冒号,导致 range 无法计算,因此不依赖 this.fill const range = getSelection().getRangeAt(0); if (range.endContainer.nodeType !== 3) { range.endContainer.childNodes[range.endOffset - 1]?.remove(); } addEmoji(unicode); let emoji; if (unicode.indexOf(".") > -1) { emoji = `:${unicode.split(".")[0]}: `; } else { emoji = unicode2Emoji(unicode) + " "; } insertHTML(protyle.lute.SpinBlockDOM(emoji), protyle); this.element.classList.add("fn__none"); } else { this.fill(unicode, protyle); } } else { const mark = decodeURIComponent(this.element.querySelector(".b3-list-item--focus").getAttribute("data-value")); if (mark === Constants.ZWSP + 3) { (this.element.querySelector(".b3-list-item--focus input") as HTMLElement).click(); } else { this.fill(mark, protyle, true, isOnlyMeta(event)); } } event.preventDefault(); event.stopPropagation(); return true; } if (isEmojiPanel) { const currentElement = this.element.querySelector(".emojis__item--current"); if (!currentElement) { return false; } let newCurrentElement: HTMLElement; if (event.key === "ArrowLeft" || event.key === "ArrowUp") { if (currentElement.previousElementSibling) { currentElement.classList.remove("emojis__item--current"); newCurrentElement = currentElement.previousElementSibling as HTMLElement; } else if (currentElement.parentElement.previousElementSibling?.previousElementSibling) { currentElement.classList.remove("emojis__item--current"); newCurrentElement = currentElement.parentElement.previousElementSibling.previousElementSibling.lastElementChild as HTMLElement; } } else if (event.key === "ArrowRight" || event.key === "ArrowDown") { if (currentElement.nextElementSibling) { currentElement.classList.remove("emojis__item--current"); newCurrentElement = currentElement.nextElementSibling as HTMLElement; } else if (currentElement.parentElement.nextElementSibling?.nextElementSibling) { currentElement.classList.remove("emojis__item--current"); newCurrentElement = currentElement.parentElement.nextElementSibling.nextElementSibling.firstElementChild as HTMLElement; } } if (newCurrentElement) { newCurrentElement.classList.add("emojis__item--current"); const topHeight = 4; const emojisContentElement = this.element.querySelector(".emojis__panel"); if (newCurrentElement.offsetTop - topHeight < emojisContentElement.scrollTop) { emojisContentElement.scrollTop = newCurrentElement.offsetTop - topHeight; } else if (newCurrentElement.offsetTop - topHeight - emojisContentElement.clientHeight + newCurrentElement.clientHeight > emojisContentElement.scrollTop) { emojisContentElement.scrollTop = newCurrentElement.offsetTop - topHeight - emojisContentElement.clientHeight + newCurrentElement.clientHeight; } } event.preventDefault(); event.stopPropagation(); return true; } else if (event.key === "ArrowDown" || event.key === "ArrowUp") { upDownHint(this.element.firstElementChild, event); event.preventDefault(); event.stopPropagation(); return true; } if (event.key === "ArrowLeft" || event.key === "ArrowRight") { hideElements(["hint"], protyle); event.preventDefault(); event.stopPropagation(); return true; } return false; } private fixImageCursor(range: Range) { const previous = hasPreviousSibling(range.startContainer); if (previous && previous.nodeType !== 3 && (previous as HTMLElement).classList.contains("img")) { if (!hasNextSibling(previous)) { range.insertNode(document.createTextNode(Constants.ZWSP)); range.collapse(false); } } } private getKey(currentLineValue: string, extend: IHintExtend[]) { this.lastIndex = -1; this.splitChar = ""; extend.forEach((item) => { let currentLastIndex = currentLineValue.lastIndexOf(item.key); // https://ld246.com/article/1701670704754 if (Constants.BLOCK_HINT_KEYS.includes(item.key) && currentLastIndex > -1) { const thirdLastIndex = currentLineValue.lastIndexOf(item.key + item.key.substring(0, 1)) if (thirdLastIndex > -1) { currentLastIndex = Math.min(currentLastIndex, currentLineValue.lastIndexOf(item.key + item.key.substring(0, 1))) } } if (this.lastIndex < currentLastIndex) { if (Constants.BLOCK_HINT_KEYS.includes(this.splitChar) && (item.key === ":" || item.key === "#" || item.key === "/" || item.key === "、")) { // 块搜索中忽略以上符号 } else if (this.splitChar === "#" && (item.key === "/" || item.key === "、")) { // 标签中忽略以上符号 } else { this.splitChar = item.key; this.lastIndex = currentLastIndex; } } }); if (this.lastIndex === -1) { return undefined; } // 冒号前为数字或冒号不进行emoji提示 if (this.splitChar === ":") { this.enableEmoji = !(/\d/.test(currentLineValue.substr(this.lastIndex - 1, 1)) || currentLineValue.substr(this.lastIndex - 1, 2) === "::"); } const lineArray = currentLineValue.split(this.splitChar); const lastItem = lineArray[lineArray.length - 1]; if (lineArray.length > 1 && lastItem.trim() === lastItem && lastItem.length < Constants.SIZE_TITLE) { // 输入法自动补全 https://github.com/siyuan-note/insider/issues/100 if (this.splitChar === "【【" && currentLineValue.endsWith("【【】")) { return ""; } return lastItem; } return undefined; } }