diff --git a/app/src/history/history.ts b/app/src/history/history.ts index 839aa43d1..99e75303b 100644 --- a/app/src/history/history.ts +++ b/app/src/history/history.ts @@ -712,7 +712,7 @@ const bindEvent = (app: App, element: Element, dialog?: Dialog) => { protyle: historyEditor.protyle, action: [Constants.CB_GET_HISTORY, Constants.CB_GET_HTML], }); - searchMarkRender(historyEditor.protyle, ["TODO"]); + searchMarkRender(historyEditor.protyle, ["TODO"], false); } }); } diff --git a/app/src/layout/dock/Backlink.ts b/app/src/layout/dock/Backlink.ts index 69bf6a14b..d06b6f7a9 100644 --- a/app/src/layout/dock/Backlink.ts +++ b/app/src/layout/dock/Backlink.ts @@ -458,7 +458,7 @@ export class Backlink extends Model { } }); editor.protyle.notebookId = liElement.getAttribute("data-notebook-id"); - searchMarkRender(editor.protyle, ["TODO"]); + searchMarkRender(editor.protyle, ["TODO"], false); this.editors.push(editor); }); } diff --git a/app/src/protyle/render/searchMarkRender.ts b/app/src/protyle/render/searchMarkRender.ts index 92ee04516..ba5ef9cbe 100644 --- a/app/src/protyle/render/searchMarkRender.ts +++ b/app/src/protyle/render/searchMarkRender.ts @@ -1,31 +1,79 @@ -export const searchMarkRender = (protyle: IProtyle, keys: string[]) => { +import {Constants} from "../../constants"; + +export const searchMarkRender = (protyle: IProtyle, keys: string[], isHL:boolean) => { if (!isSupportCSSHL()) { return; } - protyle.highlight.markHL.clear(); - protyle.highlight.markHL.clear(); - protyle.highlight.ranges = []; - matchElements.forEach((item, index) => { - const range = new Range(); - if (item.getAttribute("data-type") === "search-mark") { - const contentElement = item.firstChild; - item.replaceWith(contentElement); - range.selectNodeContents(contentElement); - } else { - item.setAttribute("data-type", item.getAttribute("data-type").replace(" search-mark", "").replace("search-mark ", "")); - range.selectNodeContents(item); + setTimeout(() => { + protyle.highlight.markHL.clear(); + protyle.highlight.markHL.clear(); + protyle.highlight.ranges = []; + + + // 准备一个数组来保存所有文本节点 + const textNodes: Node[] = []; + const textNodesSize: number[] = []; + let currentSize = 0; + + const treeWalker = document.createTreeWalker(protyle.wysiwyg.element, NodeFilter.SHOW_TEXT); + let currentNode = treeWalker.nextNode(); + while (currentNode) { + textNodes.push(currentNode); + currentSize += currentNode.textContent.length + textNodesSize.push(currentSize); + currentNode = treeWalker.nextNode(); } - if (index === protyle.highlight.rangeIndex && !protyle.options.backlinkData) { - protyle.highlight.markHL.add(range); - } else { - protyle.highlight.mark.add(range); + + const text = protyle.wysiwyg.element.textContent; + const rangeIndexes: { range: Range, startIndex: number }[] = []; + + keys.forEach(key => { + let startIndex = 0; + let endIndex = 0; + let currentNodeIndex = 0; + while ((startIndex = text.indexOf(key, startIndex)) !== -1) { + const range = new Range(); + endIndex = startIndex + key.length; + try { + while (currentNodeIndex < textNodes.length && textNodesSize[currentNodeIndex] <= startIndex) { + currentNodeIndex++; + } + let currentTextNode = textNodes[currentNodeIndex]; + range.setStart(currentTextNode, startIndex - (currentNodeIndex ? textNodesSize[currentNodeIndex - 1] : 0)); + + while (currentNodeIndex < textNodes.length && textNodesSize[currentNodeIndex] < endIndex) { + currentNodeIndex++ + } + currentTextNode = textNodes[currentNodeIndex] + range.setEnd(currentTextNode, endIndex - (currentNodeIndex ? textNodesSize[currentNodeIndex - 1] : 0)); + + rangeIndexes.push({range, startIndex}); + } catch (e) { + console.error("searchMarkRender error:", e); + } + startIndex = endIndex; + } + }) + + rangeIndexes.sort((b, a) => { + if (a.startIndex > b.startIndex) { + return -1 + } else { + return 0 + } + }).forEach((item, index) => { + if (index === protyle.highlight.rangeIndex && isHL) { + protyle.highlight.markHL.add(item.range); + } else { + protyle.highlight.mark.add(item.range); + } + protyle.highlight.ranges.push(item.range); + }); + CSS.highlights.set("search-mark-" + protyle.highlight.styleElement.dataset.uuid, protyle.highlight.mark); + if (!protyle.options.backlinkData) { + CSS.highlights.set("search-mark-hl-" + protyle.highlight.styleElement.dataset.uuid, protyle.highlight.markHL); } - protyle.highlight.ranges.push(range); - }); - CSS.highlights.set("search-mark-" + protyle.highlight.styleElement.dataset.uuid, protyle.highlight.mark); - if (!protyle.options.backlinkData) { - CSS.highlights.set("search-mark-hl-" + protyle.highlight.styleElement.dataset.uuid, protyle.highlight.markHL); - } + }, protyle.wysiwyg.element.querySelector(".hljs") ? Constants.TIMEOUT_TRANSITION : 0); }; diff --git a/app/src/protyle/util/reload.ts b/app/src/protyle/util/reload.ts index d5b030ac1..6aa6d0107 100644 --- a/app/src/protyle/util/reload.ts +++ b/app/src/protyle/util/reload.ts @@ -45,7 +45,7 @@ export const reloadProtyle = (protyle: IProtyle, focus: boolean, updateReadonly? }, response => { protyle.options.backlinkData = isMention ? response.data.backmentions : response.data.backlinks; renderBacklink(protyle, protyle.options.backlinkData); - searchMarkRender(protyle, ["TODO"]); + searchMarkRender(protyle, ["TODO"], false); }); } } else { @@ -57,7 +57,7 @@ export const reloadProtyle = (protyle: IProtyle, focus: boolean, updateReadonly? updateReadonly, cb() { if (protyle.query?.key) { - searchMarkRender(protyle, ["TODO"]); + searchMarkRender(protyle, ["TODO"], true); } } }); diff --git a/app/src/search/util.ts b/app/src/search/util.ts index d3f36a6e3..e84dbff5d 100644 --- a/app/src/search/util.ts +++ b/app/src/search/util.ts @@ -1239,7 +1239,7 @@ export const getArticle = (options: { let matchRectTop: number; if (isSupportCSSHL()) { options.edit.protyle.highlight.rangeIndex = 0; - searchMarkRender(options.edit.protyle, ["TODO"]); + searchMarkRender(options.edit.protyle, ["TODO", "得到"], true); matchRectTop = options.edit.protyle.highlight.ranges[0].getBoundingClientRect().top; } else { const matchElements = options.edit.protyle.wysiwyg.element.querySelectorAll('span[data-type~="search-mark"]');