A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
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};