git ssb

0+

cel / text-node-searcher



Tree: 5e58ae1fa193ed1aaf2d8f17757a788cac5725e5

Files: 5e58ae1fa193ed1aaf2d8f17757a788cac5725e5 / index.js

6435 bytesRaw
1/*
2 * TextNodeSeacher
3 * Copyright (c) 2015 Charles Lehner
4 *
5 * Usage of the works is permitted provided that this instrument is
6 * retained with the works, so that any entity that uses the works is
7 * notified of this instrument.
8 *
9 * DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY.
10 */
11
12(function (global) {
13
14function addAccents(str) {
15 // http://www.the-art-of-web.com/javascript/search-highlight/
16 return str
17 // .replace(/([ao])e/ig, "$1")
18 .replace(/e/ig, "[eèéêë]")
19 .replace(/a/ig, "([aàâä]|ae)")
20 .replace(/i/ig, "[iîï]")
21 .replace(/o/ig, "([oôö]|oe)")
22 .replace(/u/ig, "[uùûü]")
23 .replace(/y/ig, "[yÿ]");
24}
25
26function quoteRegex(str) {
27 return str.replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
28}
29
30function setSelection(startNode, startOffset, endNode, endOffset) {
31 var range = document.createRange();
32 range.setStart(startNode, startOffset);
33 range.setEnd(endNode, endOffset);
34
35 var sel = window.getSelection();
36 sel.removeAllRanges();
37 sel.addRange(range);
38}
39
40function selectText(node, offset, len, align) {
41 // Put the text into its own element so we can scroll it into view
42 var parent = node.parentNode;
43 var el = document.createElement("span");
44 var middle = offset > 0 ? node.splitText(offset) : node;
45 var end = middle.splitText(len);
46 el.appendChild(middle);
47 parent.insertBefore(el, end);
48 el.scrollIntoView(align);
49
50 // Restore the text and set the selection
51 parent.removeChild(el);
52 parent.insertBefore(middle, end);
53 parent.normalize();
54 setSelection(node, offset, node, offset + len);
55}
56
57function TextNodeSearcher(opt) {
58 if (!opt)
59 opt = {};
60 else if (opt instanceof window.Element)
61 opt = {container: opt};
62 this.container = opt.container || document.body;
63 this.highlightTagName = opt.highlightTagName || this.highlightTagName;
64}
65
66TextNodeSearcher.prototype.highlightTagName = "highlight";
67
68TextNodeSearcher.prototype.setQuery = function (str) {
69 if (str == this.queryStr)
70 return;
71
72 this.queryStr = str;
73 this.query = new RegExp(addAccents(quoteRegex(str)), "ig");
74};
75
76function shouldDescendInto(node) {
77 return node.nodeName != "SCRIPT" && node.nodeName != "STYLE";
78}
79
80function getNextTextNode(node, container) {
81 do {
82 if (shouldDescendInto(node) && node.firstChild) {
83 node = node.firstChild;
84 } else {
85 while (!node.nextSibling) {
86 node = node.parentNode;
87 if (node == container || !node)
88 return null;
89 }
90 node = node.nextSibling;
91 }
92 } while (node.nodeType != node.TEXT_NODE);
93 return node;
94}
95
96function getPreviousTextNode(node, container) {
97 if (node == container) {
98 while (node.lastChild && shouldDescendInto(node))
99 node = node.lastChild;
100 if (node.nodeType == node.TEXT_NODE)
101 return node;
102 }
103 do {
104 if (!node || node == container) {
105 return null;
106 } else if (node.previousSibling) {
107 node = node.previousSibling;
108 while (shouldDescendInto(node) && node.lastChild)
109 node = node.lastChild;
110 } else {
111 node = node.parentNode;
112 }
113 } while (node.nodeType != node.TEXT_NODE);
114 return node;
115}
116
117function matchLast(re, str) {
118 var last;
119 re.lastIndex = 0;
120 for (var m = re.exec(str); m; m = re.exec(str))
121 last = m;
122 return last;
123}
124
125TextNodeSearcher.prototype.highlight = function () {
126 if (this.highlightedQuery == this.query)
127 return;
128 else if (this.highlightedQuery)
129 this.unhighlight();
130 var query = this.highlightedQuery = this.query;
131
132 query.lastIndex = 0;
133 for (var node = getNextTextNode(this.container, this.container); node;
134 node = getNextTextNode(node, this.container)) {
135 var m = query.exec(node.data);
136 if (m) {
137 var offset = m.index;
138 var len = m[0].length;
139 if (len === 0)
140 return;
141 var hl = document.createElement(this.highlightTagName);
142 var middle = offset > 0 ? node.splitText(offset) : node;
143 var next;
144 if (middle.data.length > len) {
145 next = middle.splitText(len);
146 } else {
147 next = middle.nextSibling;
148 }
149 var parent = node.parentNode;
150 hl.appendChild(middle);
151 if (next)
152 parent.insertBefore(hl, next);
153 else
154 parent.appendChild(hl);
155 node = middle;
156 query.lastIndex = len;
157 }
158 }
159};
160
161TextNodeSearcher.prototype.unhighlight = function () {
162 this.highlightedQuery = null;
163 var els = this.container.getElementsByTagName(this.highlightTagName);
164 els = [].slice.call(els);
165 for (var i = 0; i < els.length; i++) {
166 var el = els[i];
167 var parent = el.parentNode;
168 var text = el.firstChild;
169 parent.insertBefore(text, el);
170 parent.removeChild(el);
171 parent.normalize();
172 }
173};
174
175TextNodeSearcher.prototype.selectNext = function () {
176 if (!this.queryStr || !this.container)
177 return;
178
179 var sel = window.getSelection();
180 var startNode = sel.focusNode;
181 var startOffset = 0;
182 if (!startNode || !this.container.contains(startNode))
183 startNode = getNextTextNode(this.container, this.container);
184 else if (startNode.nodeType != startNode.TEXT_NODE)
185 startNode = getNextTextNode(startNode, this.container);
186 else
187 startOffset = sel.focusOffset;
188
189 var wrapped = false;
190 for (var node = startNode; node;) {
191 var str = node.data;
192 this.query.lastIndex = startOffset;
193 if (startOffset)
194 startOffset = 0;
195 var m = this.query.exec(str);
196 if (m) {
197 selectText(node, m.index, m[0].length, false);
198 return;
199 }
200 node = getNextTextNode(node, this.container);
201 if (!node) {
202 if (wrapped)
203 return;
204 wrapped = true;
205 node = getNextTextNode(this.container, this.container);
206 }
207 }
208};
209
210TextNodeSearcher.prototype.selectPrevious = function () {
211 if (!this.queryStr || !this.container)
212 return;
213
214 var sel = window.getSelection();
215 var endNode = sel.anchorNode;
216 var endOffset = 0;
217 if (!endNode || !this.container.contains(endNode))
218 endNode = getPreviousTextNode(this.container, this.container);
219 else if (endNode.nodeType != endNode.TEXT_NODE)
220 endNode = getPreviousTextNode(endNode, this.container);
221 else
222 endOffset = sel.anchorOffset;
223
224 var wrapped = false;
225 for (var node = endNode; node;) {
226 var str = node.data;
227 if (endOffset < Infinity) {
228 str = node.data.substr(0, endOffset);
229 endOffset = Infinity;
230 }
231 var m = matchLast(this.query, str);
232 if (m) {
233 selectText(node, m.index, m[0].length, false);
234 return;
235 }
236 node = getPreviousTextNode(node, this.container);
237 if (!node) {
238 if (wrapped)
239 return;
240 wrapped = true;
241 node = getPreviousTextNode(this.container, this.container);
242 }
243 }
244};
245
246if (typeof module != "undefined")
247 module.exports = TextNodeSearcher;
248else if (global)
249 global.TextNodeSearcher = TextNodeSearcher;
250}(this));
251

Built with git-ssb-web