blob: b460e3b9f5bbdf0dc53bc6fe85308e7f23951062 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
|
function walk(el, callback) {
callback(el);
let node = el.firstChild;
while (node) {
walk(node, callback);
node = node.nextSibling;
}
}
export class Highlight {
constructor(
opts = {
contentSelector: '.content',
markClass: 'da-highlight-mark'
}
) {
this.opts = opts;
this.nodeStack = [];
}
apply(re) {
const treeWalker = document.createTreeWalker(
this.content(),
NodeFilter.SHOW_TEXT,
{
acceptNode: (node) => {
if (node.parentNode.classList.contains(this.opts.markClass)) {
return NodeFilter.FILTER_REJECT;
}
if (/\S/.test(node.data)) {
return NodeFilter.FILTER_ACCEPT;
}
return NodeFilter.FILTER_REJECT;
}
},
false
);
// Firs pass: Collect all the text nodes matching the provided regexp.
// TODO(bep) improve text matching.
let matches = [];
while (treeWalker.nextNode()) {
let node = treeWalker.currentNode;
if (node.data.match(re)) {
matches.push(node);
}
}
// Second pass: Replace the matches with nodes wrapped in <mark> tags.
matches.forEach((node) => {
// Clone the parent so we can restore it.
let parentClone = node.parentNode.cloneNode(true);
parentClone.childNodes.forEach((node) => {
if (node.nodeType !== Node.TEXT_NODE) {
return;
}
let match = node.data.match(re);
if (!match) {
return;
}
let pos = node.data.indexOf(match[0], match.index);
if (pos === -1) {
return;
}
let mark = document.createElement('mark');
mark.classList.add(this.opts.markClass);
let wordNode = node.splitText(pos);
wordNode.splitText(match[0].length);
let wordClone = wordNode.cloneNode(true);
mark.appendChild(wordClone);
parentClone.replaceChild(mark, wordNode);
});
if (node.parentNode && node.parentNode.parentNode) {
this.nodeStack.push({
old: node.parentNode,
new: parentClone
});
node.parentNode.parentNode.replaceChild(parentClone, node.parentNode);
}
});
}
remove() {
while (this.nodeStack.length) {
let pair = this.nodeStack.pop();
pair.new.parentNode.replaceChild(pair.old, pair.new);
}
}
content() {
return document.querySelector(this.opts.contentSelector);
}
}
|