A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
at lambda-fork/main 124 lines 5.3 kB view raw
1import {Constants} from "../../constants"; 2import {isInEmbedBlock} from "../util/hasClosest"; 3 4export const searchMarkRender = (protyle: IProtyle, keys: string[], hlId?: string | number, cb?: () => void) => { 5 if (!isSupportCSSHL() || ((!keys || keys.length === 0) && !hlId)) { 6 return; 7 } 8 setTimeout(() => { 9 protyle.highlight.markHL.clear(); 10 protyle.highlight.mark.clear(); 11 protyle.highlight.rangeIndex = 0; 12 protyle.highlight.ranges = []; 13 let isSetHL = false; 14 let hlBlockElement: Element; 15 if (typeof hlId === "string") { 16 Array.from(protyle.wysiwyg.element.querySelectorAll(`[data-node-id='${hlId}']`)).find(item => { 17 if (!isInEmbedBlock(item)) { 18 hlBlockElement = item; 19 return true; 20 } 21 }); 22 } 23 24 if (!hlBlockElement && hlId === protyle.block.rootID && protyle.title && !protyle.title.element.classList.contains("fn__none")) { 25 hlBlockElement = protyle.title.element; 26 } 27 28 // 准备一个数组来保存所有文本节点 29 const textNodes: Node[] = []; 30 const textNodesSize: number[] = []; 31 let currentSize = 0; 32 33 const treeWalker = document.createTreeWalker(protyle.contentElement, NodeFilter.SHOW_TEXT); 34 let currentNode = treeWalker.nextNode(); 35 while (currentNode) { 36 textNodes.push(currentNode); 37 currentSize += currentNode.textContent.length; 38 textNodesSize.push(currentSize); 39 currentNode = treeWalker.nextNode(); 40 } 41 42 const text = protyle.contentElement.textContent; 43 const rangeIndexes: { range: Range, startIndex: number, isCurrent: boolean }[] = []; 44 if (keys && keys.length > 0) { 45 keys.forEach(key => { 46 if (!key) { 47 return; 48 } 49 let startIndex = 0; 50 let endIndex = 0; 51 let currentNodeIndex = 0; 52 while ((startIndex = text.indexOf(key, startIndex)) !== -1) { 53 const range = new Range(); 54 endIndex = startIndex + key.length; 55 try { 56 while (currentNodeIndex < textNodes.length && textNodesSize[currentNodeIndex] <= startIndex) { 57 currentNodeIndex++; 58 } 59 let currentTextNode = textNodes[currentNodeIndex]; 60 range.setStart(currentTextNode, startIndex - (currentNodeIndex ? textNodesSize[currentNodeIndex - 1] : 0)); 61 62 while (currentNodeIndex < textNodes.length && textNodesSize[currentNodeIndex] < endIndex) { 63 currentNodeIndex++; 64 } 65 currentTextNode = textNodes[currentNodeIndex]; 66 range.setEnd(currentTextNode, endIndex - (currentNodeIndex ? textNodesSize[currentNodeIndex - 1] : 0)); 67 68 let isCurrent = false; 69 if (!isSetHL && hlBlockElement && hlBlockElement.contains(currentTextNode)) { 70 isSetHL = true; 71 isCurrent = true; 72 } 73 74 rangeIndexes.push({range, startIndex, isCurrent}); 75 } catch (e) { 76 console.error("searchMarkRender error:", e); 77 } 78 startIndex = endIndex; 79 } 80 }); 81 } 82 83 // 没有匹配到关键字,但是有高亮块时,需将其添加进去 84 if (!isSetHL && hlBlockElement) { 85 const startIndex = text.indexOf(hlBlockElement.textContent); 86 if (startIndex > -1) { 87 const range = new Range(); 88 let currentNodeIndex = 0; 89 while (textNodes.length && textNodesSize[currentNodeIndex] <= startIndex) { 90 currentNodeIndex++; 91 } 92 range.setStart(textNodes[currentNodeIndex], 0); 93 rangeIndexes.push({range, startIndex, isCurrent: true}); 94 } 95 } 96 97 rangeIndexes.sort((b, a) => { 98 if (a.startIndex > b.startIndex) { 99 return -1; 100 } else { 101 return 0; 102 } 103 }).forEach((item, index) => { 104 if ((typeof hlId === "string" && item.isCurrent) || (typeof hlId === "number" && hlId === index)) { 105 protyle.highlight.rangeIndex = index; 106 protyle.highlight.markHL.add(item.range); 107 } else { 108 protyle.highlight.mark.add(item.range); 109 } 110 protyle.highlight.ranges.push(item.range); 111 }); 112 CSS.highlights.set("search-mark-" + protyle.highlight.styleElement.dataset.uuid, protyle.highlight.mark); 113 if (typeof hlId !== "undefined") { 114 CSS.highlights.set("search-mark-hl-" + protyle.highlight.styleElement.dataset.uuid, protyle.highlight.markHL); 115 } 116 if (cb) { 117 cb(); 118 } 119 }, protyle.wysiwyg.element.querySelector(".hljs") ? Constants.TIMEOUT_TRANSITION : 0); 120}; 121 122export const isSupportCSSHL = () => { 123 return !!(CSS && CSS.highlights); 124};