@meddleware/dev 0.0.4 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. package/docs/.vitepress/cache/deps/@meddleware_ui.js +614 -0
  2. package/docs/.vitepress/cache/deps/@meddleware_ui.js.map +1 -0
  3. package/docs/.vitepress/cache/deps/_metadata.json +56 -0
  4. package/docs/.vitepress/cache/deps/package.json +3 -0
  5. package/docs/.vitepress/cache/deps/vitepress_n_@vue_devtools-api.js +3808 -0
  6. package/docs/.vitepress/cache/deps/vitepress_n_@vue_devtools-api.js.map +1 -0
  7. package/docs/.vitepress/cache/deps/vitepress_n_@vueuse_core.js +10171 -0
  8. package/docs/.vitepress/cache/deps/vitepress_n_@vueuse_core.js.map +1 -0
  9. package/docs/.vitepress/cache/deps/vitepress_n_@vueuse_integrations_useFocusTrap.js +1225 -0
  10. package/docs/.vitepress/cache/deps/vitepress_n_@vueuse_integrations_useFocusTrap.js.map +1 -0
  11. package/docs/.vitepress/cache/deps/vitepress_n_mark__js_src_vanilla__js.js +1492 -0
  12. package/docs/.vitepress/cache/deps/vitepress_n_mark__js_src_vanilla__js.js.map +1 -0
  13. package/docs/.vitepress/cache/deps/vitepress_n_minisearch.js +1773 -0
  14. package/docs/.vitepress/cache/deps/vitepress_n_minisearch.js.map +1 -0
  15. package/docs/.vitepress/cache/deps/vue.js +2 -0
  16. package/docs/.vitepress/cache/deps/vue.runtime.esm-bundler-Bo_ScjpA.js +8890 -0
  17. package/docs/.vitepress/cache/deps/vue.runtime.esm-bundler-Bo_ScjpA.js.map +1 -0
  18. package/docs/.vitepress/config.ts +1 -4
  19. package/docs/.vitepress/theme/Layout.vue +13 -0
  20. package/docs/.vitepress/theme/custom.css +6 -0
  21. package/docs/.vitepress/theme/index.ts +2 -0
  22. package/docs/index.md +0 -4
  23. package/package.json +2 -2
  24. package/tsconfig.json +1 -1
@@ -0,0 +1,1492 @@
1
+ //#region node_modules/mark.js/src/lib/domiterator.js
2
+ /**
3
+ * A NodeIterator with iframes support and a method to check if an element is
4
+ * matching a specified selector
5
+ * @example
6
+ * const iterator = new DOMIterator(
7
+ * document.querySelector("#context"), true
8
+ * );
9
+ * iterator.forEachNode(NodeFilter.SHOW_TEXT, node => {
10
+ * console.log(node);
11
+ * }, node => {
12
+ * if(DOMIterator.matches(node.parentNode, ".ignore")){
13
+ * return NodeFilter.FILTER_REJECT;
14
+ * } else {
15
+ * return NodeFilter.FILTER_ACCEPT;
16
+ * }
17
+ * }, () => {
18
+ * console.log("DONE");
19
+ * });
20
+ * @todo Outsource into separate repository
21
+ */
22
+ var DOMIterator = class DOMIterator {
23
+ /**
24
+ * @param {HTMLElement|HTMLElement[]|NodeList|string} ctx - The context DOM
25
+ * element, an array of DOM elements, a NodeList or a selector
26
+ * @param {boolean} [iframes=true] - A boolean indicating if iframes should
27
+ * be handled
28
+ * @param {string[]} [exclude=[]] - An array containing exclusion selectors
29
+ * for iframes
30
+ * @param {number} [iframesTimeout=5000] - A number indicating the ms to
31
+ * wait before an iframe should be skipped, in case the load event isn't
32
+ * fired. This also applies if the user is offline and the resource of the
33
+ * iframe is online (either by the browsers "offline" mode or because
34
+ * there's no internet connection)
35
+ */
36
+ constructor(ctx, iframes = true, exclude = [], iframesTimeout = 5e3) {
37
+ /**
38
+ * The context of the instance. Either a DOM element, an array of DOM
39
+ * elements, a NodeList or a selector
40
+ * @type {HTMLElement|HTMLElement[]|NodeList|string}
41
+ * @access protected
42
+ */
43
+ this.ctx = ctx;
44
+ /**
45
+ * Boolean indicating if iframe support is enabled
46
+ * @type {boolean}
47
+ * @access protected
48
+ */
49
+ this.iframes = iframes;
50
+ /**
51
+ * An array containing exclusion selectors for iframes
52
+ * @type {string[]}
53
+ */
54
+ this.exclude = exclude;
55
+ /**
56
+ * The maximum ms to wait for a load event before skipping an iframe
57
+ * @type {number}
58
+ */
59
+ this.iframesTimeout = iframesTimeout;
60
+ }
61
+ /**
62
+ * Checks if the specified DOM element matches the selector
63
+ * @param {HTMLElement} element - The DOM element
64
+ * @param {string|string[]} selector - The selector or an array with
65
+ * selectors
66
+ * @return {boolean}
67
+ * @access public
68
+ */
69
+ static matches(element, selector) {
70
+ const selectors = typeof selector === "string" ? [selector] : selector, fn = element.matches || element.matchesSelector || element.msMatchesSelector || element.mozMatchesSelector || element.oMatchesSelector || element.webkitMatchesSelector;
71
+ if (fn) {
72
+ let match = false;
73
+ selectors.every((sel) => {
74
+ if (fn.call(element, sel)) {
75
+ match = true;
76
+ return false;
77
+ }
78
+ return true;
79
+ });
80
+ return match;
81
+ } else return false;
82
+ }
83
+ /**
84
+ * Returns all contexts filtered by duplicates (even nested)
85
+ * @return {HTMLElement[]} - An array containing DOM contexts
86
+ * @access protected
87
+ */
88
+ getContexts() {
89
+ let ctx, filteredCtx = [];
90
+ if (typeof this.ctx === "undefined" || !this.ctx) ctx = [];
91
+ else if (NodeList.prototype.isPrototypeOf(this.ctx)) ctx = Array.prototype.slice.call(this.ctx);
92
+ else if (Array.isArray(this.ctx)) ctx = this.ctx;
93
+ else if (typeof this.ctx === "string") ctx = Array.prototype.slice.call(document.querySelectorAll(this.ctx));
94
+ else ctx = [this.ctx];
95
+ ctx.forEach((ctx) => {
96
+ const isDescendant = filteredCtx.filter((contexts) => {
97
+ return contexts.contains(ctx);
98
+ }).length > 0;
99
+ if (filteredCtx.indexOf(ctx) === -1 && !isDescendant) filteredCtx.push(ctx);
100
+ });
101
+ return filteredCtx;
102
+ }
103
+ /**
104
+ * @callback DOMIterator~getIframeContentsSuccessCallback
105
+ * @param {HTMLDocument} contents - The contentDocument of the iframe
106
+ */
107
+ /**
108
+ * Calls the success callback function with the iframe document. If it can't
109
+ * be accessed it calls the error callback function
110
+ * @param {HTMLElement} ifr - The iframe DOM element
111
+ * @param {DOMIterator~getIframeContentsSuccessCallback} successFn
112
+ * @param {function} [errorFn]
113
+ * @access protected
114
+ */
115
+ getIframeContents(ifr, successFn, errorFn = () => {}) {
116
+ let doc;
117
+ try {
118
+ const ifrWin = ifr.contentWindow;
119
+ doc = ifrWin.document;
120
+ if (!ifrWin || !doc) throw new Error("iframe inaccessible");
121
+ } catch (e) {
122
+ errorFn();
123
+ }
124
+ if (doc) successFn(doc);
125
+ }
126
+ /**
127
+ * Checks if an iframe is empty (if about:blank is the shown page)
128
+ * @param {HTMLElement} ifr - The iframe DOM element
129
+ * @return {boolean}
130
+ * @access protected
131
+ */
132
+ isIframeBlank(ifr) {
133
+ const bl = "about:blank", src = ifr.getAttribute("src").trim();
134
+ return ifr.contentWindow.location.href === bl && src !== bl && src;
135
+ }
136
+ /**
137
+ * Observes the onload event of an iframe and calls the success callback or
138
+ * the error callback if the iframe is inaccessible. If the event isn't
139
+ * fired within the specified {@link DOMIterator#iframesTimeout}, then it'll
140
+ * call the error callback too
141
+ * @param {HTMLElement} ifr - The iframe DOM element
142
+ * @param {DOMIterator~getIframeContentsSuccessCallback} successFn
143
+ * @param {function} errorFn
144
+ * @access protected
145
+ */
146
+ observeIframeLoad(ifr, successFn, errorFn) {
147
+ let called = false, tout = null;
148
+ const listener = () => {
149
+ if (called) return;
150
+ called = true;
151
+ clearTimeout(tout);
152
+ try {
153
+ if (!this.isIframeBlank(ifr)) {
154
+ ifr.removeEventListener("load", listener);
155
+ this.getIframeContents(ifr, successFn, errorFn);
156
+ }
157
+ } catch (e) {
158
+ errorFn();
159
+ }
160
+ };
161
+ ifr.addEventListener("load", listener);
162
+ tout = setTimeout(listener, this.iframesTimeout);
163
+ }
164
+ /**
165
+ * Callback when the iframe is ready
166
+ * @callback DOMIterator~onIframeReadySuccessCallback
167
+ * @param {HTMLDocument} contents - The contentDocument of the iframe
168
+ */
169
+ /**
170
+ * Callback if the iframe can't be accessed
171
+ * @callback DOMIterator~onIframeReadyErrorCallback
172
+ */
173
+ /**
174
+ * Calls the callback if the specified iframe is ready for DOM access
175
+ * @param {HTMLElement} ifr - The iframe DOM element
176
+ * @param {DOMIterator~onIframeReadySuccessCallback} successFn - Success
177
+ * callback
178
+ * @param {DOMIterator~onIframeReadyErrorCallback} errorFn - Error callback
179
+ * @see {@link http://stackoverflow.com/a/36155560/3894981} for
180
+ * background information
181
+ * @access protected
182
+ */
183
+ onIframeReady(ifr, successFn, errorFn) {
184
+ try {
185
+ if (ifr.contentWindow.document.readyState === "complete") {
186
+ if (this.isIframeBlank(ifr)) this.observeIframeLoad(ifr, successFn, errorFn);
187
+ else this.getIframeContents(ifr, successFn, errorFn);
188
+ } else this.observeIframeLoad(ifr, successFn, errorFn);
189
+ } catch (e) {
190
+ errorFn();
191
+ }
192
+ }
193
+ /**
194
+ * Callback when all iframes are ready for DOM access
195
+ * @callback DOMIterator~waitForIframesDoneCallback
196
+ */
197
+ /**
198
+ * Iterates over all iframes and calls the done callback when all of them
199
+ * are ready for DOM access (including nested ones)
200
+ * @param {HTMLElement} ctx - The context DOM element
201
+ * @param {DOMIterator~waitForIframesDoneCallback} done - Done callback
202
+ */
203
+ waitForIframes(ctx, done) {
204
+ let eachCalled = 0;
205
+ this.forEachIframe(ctx, () => true, (ifr) => {
206
+ eachCalled++;
207
+ this.waitForIframes(ifr.querySelector("html"), () => {
208
+ if (!--eachCalled) done();
209
+ });
210
+ }, (handled) => {
211
+ if (!handled) done();
212
+ });
213
+ }
214
+ /**
215
+ * Callback allowing to filter an iframe. Must return true when the element
216
+ * should remain, otherwise false
217
+ * @callback DOMIterator~forEachIframeFilterCallback
218
+ * @param {HTMLElement} iframe - The iframe DOM element
219
+ */
220
+ /**
221
+ * Callback for each iframe content
222
+ * @callback DOMIterator~forEachIframeEachCallback
223
+ * @param {HTMLElement} content - The iframe document
224
+ */
225
+ /**
226
+ * Callback if all iframes inside the context were handled
227
+ * @callback DOMIterator~forEachIframeEndCallback
228
+ * @param {number} handled - The number of handled iframes (those who
229
+ * wheren't filtered)
230
+ */
231
+ /**
232
+ * Iterates over all iframes inside the specified context and calls the
233
+ * callbacks when they're ready. Filters iframes based on the instance
234
+ * exclusion selectors
235
+ * @param {HTMLElement} ctx - The context DOM element
236
+ * @param {DOMIterator~forEachIframeFilterCallback} filter - Filter callback
237
+ * @param {DOMIterator~forEachIframeEachCallback} each - Each callback
238
+ * @param {DOMIterator~forEachIframeEndCallback} [end] - End callback
239
+ * @access protected
240
+ */
241
+ forEachIframe(ctx, filter, each, end = () => {}) {
242
+ let ifr = ctx.querySelectorAll("iframe"), open = ifr.length, handled = 0;
243
+ ifr = Array.prototype.slice.call(ifr);
244
+ const checkEnd = () => {
245
+ if (--open <= 0) end(handled);
246
+ };
247
+ if (!open) checkEnd();
248
+ ifr.forEach((ifr) => {
249
+ if (DOMIterator.matches(ifr, this.exclude)) checkEnd();
250
+ else this.onIframeReady(ifr, (con) => {
251
+ if (filter(ifr)) {
252
+ handled++;
253
+ each(con);
254
+ }
255
+ checkEnd();
256
+ }, checkEnd);
257
+ });
258
+ }
259
+ /**
260
+ * Creates a NodeIterator on the specified context
261
+ * @see {@link https://developer.mozilla.org/en/docs/Web/API/NodeIterator}
262
+ * @param {HTMLElement} ctx - The context DOM element
263
+ * @param {DOMIterator~whatToShow} whatToShow
264
+ * @param {DOMIterator~filterCb} filter
265
+ * @return {NodeIterator}
266
+ * @access protected
267
+ */
268
+ createIterator(ctx, whatToShow, filter) {
269
+ return document.createNodeIterator(ctx, whatToShow, filter, false);
270
+ }
271
+ /**
272
+ * Creates an instance of DOMIterator in an iframe
273
+ * @param {HTMLDocument} contents - Iframe document
274
+ * @return {DOMIterator}
275
+ * @access protected
276
+ */
277
+ createInstanceOnIframe(contents) {
278
+ return new DOMIterator(contents.querySelector("html"), this.iframes);
279
+ }
280
+ /**
281
+ * Checks if an iframe occurs between two nodes, more specifically if an
282
+ * iframe occurs before the specified node and after the specified prevNode
283
+ * @param {HTMLElement} node - The node that should occur after the iframe
284
+ * @param {HTMLElement} prevNode - The node that should occur before the
285
+ * iframe
286
+ * @param {HTMLElement} ifr - The iframe to check against
287
+ * @return {boolean}
288
+ * @access protected
289
+ */
290
+ compareNodeIframe(node, prevNode, ifr) {
291
+ if (node.compareDocumentPosition(ifr) & Node.DOCUMENT_POSITION_PRECEDING) {
292
+ if (prevNode !== null) {
293
+ if (prevNode.compareDocumentPosition(ifr) & Node.DOCUMENT_POSITION_FOLLOWING) return true;
294
+ } else return true;
295
+ }
296
+ return false;
297
+ }
298
+ /**
299
+ * @typedef {DOMIterator~getIteratorNodeReturn}
300
+ * @type {object.<string>}
301
+ * @property {HTMLElement} prevNode - The previous node or null if there is
302
+ * no
303
+ * @property {HTMLElement} node - The current node
304
+ */
305
+ /**
306
+ * Returns the previous and current node of the specified iterator
307
+ * @param {NodeIterator} itr - The iterator
308
+ * @return {DOMIterator~getIteratorNodeReturn}
309
+ * @access protected
310
+ */
311
+ getIteratorNode(itr) {
312
+ const prevNode = itr.previousNode();
313
+ let node;
314
+ if (prevNode === null) node = itr.nextNode();
315
+ else node = itr.nextNode() && itr.nextNode();
316
+ return {
317
+ prevNode,
318
+ node
319
+ };
320
+ }
321
+ /**
322
+ * An array containing objects. The object key "val" contains an iframe
323
+ * DOM element. The object key "handled" contains a boolean indicating if
324
+ * the iframe was handled already.
325
+ * It wouldn't be enough to save all open or all already handled iframes.
326
+ * The information of open iframes is necessary because they may occur after
327
+ * all other text nodes (and compareNodeIframe would never be true). The
328
+ * information of already handled iframes is necessary as otherwise they may
329
+ * be handled multiple times
330
+ * @typedef DOMIterator~checkIframeFilterIfr
331
+ * @type {object[]}
332
+ */
333
+ /**
334
+ * Checks if an iframe wasn't handled already and if so, calls
335
+ * {@link DOMIterator#compareNodeIframe} to check if it should be handled.
336
+ * Information wheter an iframe was or wasn't handled is given within the
337
+ * <code>ifr</code> dictionary
338
+ * @param {HTMLElement} node - The node that should occur after the iframe
339
+ * @param {HTMLElement} prevNode - The node that should occur before the
340
+ * iframe
341
+ * @param {HTMLElement} currIfr - The iframe to check
342
+ * @param {DOMIterator~checkIframeFilterIfr} ifr - The iframe dictionary.
343
+ * Will be manipulated (by reference)
344
+ * @return {boolean} Returns true when it should be handled, otherwise false
345
+ * @access protected
346
+ */
347
+ checkIframeFilter(node, prevNode, currIfr, ifr) {
348
+ let key = false, handled = false;
349
+ ifr.forEach((ifrDict, i) => {
350
+ if (ifrDict.val === currIfr) {
351
+ key = i;
352
+ handled = ifrDict.handled;
353
+ }
354
+ });
355
+ if (this.compareNodeIframe(node, prevNode, currIfr)) {
356
+ if (key === false && !handled) ifr.push({
357
+ val: currIfr,
358
+ handled: true
359
+ });
360
+ else if (key !== false && !handled) ifr[key].handled = true;
361
+ return true;
362
+ }
363
+ if (key === false) ifr.push({
364
+ val: currIfr,
365
+ handled: false
366
+ });
367
+ return false;
368
+ }
369
+ /**
370
+ * Creates an iterator on all open iframes in the specified array and calls
371
+ * the end callback when finished
372
+ * @param {DOMIterator~checkIframeFilterIfr} ifr
373
+ * @param {DOMIterator~whatToShow} whatToShow
374
+ * @param {DOMIterator~forEachNodeCallback} eCb - Each callback
375
+ * @param {DOMIterator~filterCb} fCb
376
+ * @access protected
377
+ */
378
+ handleOpenIframes(ifr, whatToShow, eCb, fCb) {
379
+ ifr.forEach((ifrDict) => {
380
+ if (!ifrDict.handled) this.getIframeContents(ifrDict.val, (con) => {
381
+ this.createInstanceOnIframe(con).forEachNode(whatToShow, eCb, fCb);
382
+ });
383
+ });
384
+ }
385
+ /**
386
+ * Iterates through all nodes in the specified context and handles iframe
387
+ * nodes at the correct position
388
+ * @param {DOMIterator~whatToShow} whatToShow
389
+ * @param {HTMLElement} ctx - The context
390
+ * @param {DOMIterator~forEachNodeCallback} eachCb - Each callback
391
+ * @param {DOMIterator~filterCb} filterCb - Filter callback
392
+ * @param {DOMIterator~forEachNodeEndCallback} doneCb - End callback
393
+ * @access protected
394
+ */
395
+ iterateThroughNodes(whatToShow, ctx, eachCb, filterCb, doneCb) {
396
+ const itr = this.createIterator(ctx, whatToShow, filterCb);
397
+ let ifr = [], elements = [], node, prevNode, retrieveNodes = () => {
398
+ ({prevNode, node} = this.getIteratorNode(itr));
399
+ return node;
400
+ };
401
+ while (retrieveNodes()) {
402
+ if (this.iframes) this.forEachIframe(ctx, (currIfr) => {
403
+ return this.checkIframeFilter(node, prevNode, currIfr, ifr);
404
+ }, (con) => {
405
+ this.createInstanceOnIframe(con).forEachNode(whatToShow, (ifrNode) => elements.push(ifrNode), filterCb);
406
+ });
407
+ elements.push(node);
408
+ }
409
+ elements.forEach((node) => {
410
+ eachCb(node);
411
+ });
412
+ if (this.iframes) this.handleOpenIframes(ifr, whatToShow, eachCb, filterCb);
413
+ doneCb();
414
+ }
415
+ /**
416
+ * Callback for each node
417
+ * @callback DOMIterator~forEachNodeCallback
418
+ * @param {HTMLElement} node - The DOM text node element
419
+ */
420
+ /**
421
+ * Callback if all contexts were handled
422
+ * @callback DOMIterator~forEachNodeEndCallback
423
+ */
424
+ /**
425
+ * Iterates over all contexts and initializes
426
+ * {@link DOMIterator#iterateThroughNodes iterateThroughNodes} on them
427
+ * @param {DOMIterator~whatToShow} whatToShow
428
+ * @param {DOMIterator~forEachNodeCallback} each - Each callback
429
+ * @param {DOMIterator~filterCb} filter - Filter callback
430
+ * @param {DOMIterator~forEachNodeEndCallback} done - End callback
431
+ * @access public
432
+ */
433
+ forEachNode(whatToShow, each, filter, done = () => {}) {
434
+ const contexts = this.getContexts();
435
+ let open = contexts.length;
436
+ if (!open) done();
437
+ contexts.forEach((ctx) => {
438
+ const ready = () => {
439
+ this.iterateThroughNodes(whatToShow, ctx, each, filter, () => {
440
+ if (--open <= 0) done();
441
+ });
442
+ };
443
+ if (this.iframes) this.waitForIframes(ctx, ready);
444
+ else ready();
445
+ });
446
+ }
447
+ };
448
+ //#endregion
449
+ //#region node_modules/mark.js/src/lib/mark.js
450
+ /**
451
+ * Marks search terms in DOM elements
452
+ * @example
453
+ * new Mark(document.querySelector(".context")).mark("lorem ipsum");
454
+ * @example
455
+ * new Mark(document.querySelector(".context")).markRegExp(/lorem/gmi);
456
+ */
457
+ var Mark$1 = class {
458
+ /**
459
+ * @param {HTMLElement|HTMLElement[]|NodeList|string} ctx - The context DOM
460
+ * element, an array of DOM elements, a NodeList or a selector
461
+ */
462
+ constructor(ctx) {
463
+ /**
464
+ * The context of the instance. Either a DOM element, an array of DOM
465
+ * elements, a NodeList or a selector
466
+ * @type {HTMLElement|HTMLElement[]|NodeList|string}
467
+ * @access protected
468
+ */
469
+ this.ctx = ctx;
470
+ /**
471
+ * Specifies if the current browser is a IE (necessary for the node
472
+ * normalization bug workaround). See {@link Mark#unwrapMatches}
473
+ * @type {boolean}
474
+ * @access protected
475
+ */
476
+ this.ie = false;
477
+ const ua = window.navigator.userAgent;
478
+ if (ua.indexOf("MSIE") > -1 || ua.indexOf("Trident") > -1) this.ie = true;
479
+ }
480
+ /**
481
+ * Options defined by the user. They will be initialized from one of the
482
+ * public methods. See {@link Mark#mark}, {@link Mark#markRegExp},
483
+ * {@link Mark#markRanges} and {@link Mark#unmark} for option properties.
484
+ * @type {object}
485
+ * @param {object} [val] - An object that will be merged with defaults
486
+ * @access protected
487
+ */
488
+ set opt(val) {
489
+ this._opt = Object.assign({}, {
490
+ "element": "",
491
+ "className": "",
492
+ "exclude": [],
493
+ "iframes": false,
494
+ "iframesTimeout": 5e3,
495
+ "separateWordSearch": true,
496
+ "diacritics": true,
497
+ "synonyms": {},
498
+ "accuracy": "partially",
499
+ "acrossElements": false,
500
+ "caseSensitive": false,
501
+ "ignoreJoiners": false,
502
+ "ignoreGroups": 0,
503
+ "ignorePunctuation": [],
504
+ "wildcards": "disabled",
505
+ "each": () => {},
506
+ "noMatch": () => {},
507
+ "filter": () => true,
508
+ "done": () => {},
509
+ "debug": false,
510
+ "log": window.console
511
+ }, val);
512
+ }
513
+ get opt() {
514
+ return this._opt;
515
+ }
516
+ /**
517
+ * An instance of DOMIterator
518
+ * @type {DOMIterator}
519
+ * @access protected
520
+ */
521
+ get iterator() {
522
+ return new DOMIterator(this.ctx, this.opt.iframes, this.opt.exclude, this.opt.iframesTimeout);
523
+ }
524
+ /**
525
+ * Logs a message if log is enabled
526
+ * @param {string} msg - The message to log
527
+ * @param {string} [level="debug"] - The log level, e.g. <code>warn</code>
528
+ * <code>error</code>, <code>debug</code>
529
+ * @access protected
530
+ */
531
+ log(msg, level = "debug") {
532
+ const log = this.opt.log;
533
+ if (!this.opt.debug) return;
534
+ if (typeof log === "object" && typeof log[level] === "function") log[level](`mark.js: ${msg}`);
535
+ }
536
+ /**
537
+ * Escapes a string for usage within a regular expression
538
+ * @param {string} str - The string to escape
539
+ * @return {string}
540
+ * @access protected
541
+ */
542
+ escapeStr(str) {
543
+ return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
544
+ }
545
+ /**
546
+ * Creates a regular expression string to match the specified search
547
+ * term including synonyms, diacritics and accuracy if defined
548
+ * @param {string} str - The search term to be used
549
+ * @return {string}
550
+ * @access protected
551
+ */
552
+ createRegExp(str) {
553
+ if (this.opt.wildcards !== "disabled") str = this.setupWildcardsRegExp(str);
554
+ str = this.escapeStr(str);
555
+ if (Object.keys(this.opt.synonyms).length) str = this.createSynonymsRegExp(str);
556
+ if (this.opt.ignoreJoiners || this.opt.ignorePunctuation.length) str = this.setupIgnoreJoinersRegExp(str);
557
+ if (this.opt.diacritics) str = this.createDiacriticsRegExp(str);
558
+ str = this.createMergedBlanksRegExp(str);
559
+ if (this.opt.ignoreJoiners || this.opt.ignorePunctuation.length) str = this.createJoinersRegExp(str);
560
+ if (this.opt.wildcards !== "disabled") str = this.createWildcardsRegExp(str);
561
+ str = this.createAccuracyRegExp(str);
562
+ return str;
563
+ }
564
+ /**
565
+ * Creates a regular expression string to match the defined synonyms
566
+ * @param {string} str - The search term to be used
567
+ * @return {string}
568
+ * @access protected
569
+ */
570
+ createSynonymsRegExp(str) {
571
+ const syn = this.opt.synonyms, sens = this.opt.caseSensitive ? "" : "i", joinerPlaceholder = this.opt.ignoreJoiners || this.opt.ignorePunctuation.length ? "\0" : "";
572
+ for (let index in syn) if (syn.hasOwnProperty(index)) {
573
+ const value = syn[index], k1 = this.opt.wildcards !== "disabled" ? this.setupWildcardsRegExp(index) : this.escapeStr(index), k2 = this.opt.wildcards !== "disabled" ? this.setupWildcardsRegExp(value) : this.escapeStr(value);
574
+ if (k1 !== "" && k2 !== "") str = str.replace(new RegExp(`(${this.escapeStr(k1)}|${this.escapeStr(k2)})`, `gm${sens}`), joinerPlaceholder + `(${this.processSynomyms(k1)}|${this.processSynomyms(k2)})` + joinerPlaceholder);
575
+ }
576
+ return str;
577
+ }
578
+ /**
579
+ * Setup synonyms to work with ignoreJoiners and or ignorePunctuation
580
+ * @param {string} str - synonym key or value to process
581
+ * @return {string} - processed synonym string
582
+ */
583
+ processSynomyms(str) {
584
+ if (this.opt.ignoreJoiners || this.opt.ignorePunctuation.length) str = this.setupIgnoreJoinersRegExp(str);
585
+ return str;
586
+ }
587
+ /**
588
+ * Sets up the regular expression string to allow later insertion of
589
+ * wildcard regular expression matches
590
+ * @param {string} str - The search term to be used
591
+ * @return {string}
592
+ * @access protected
593
+ */
594
+ setupWildcardsRegExp(str) {
595
+ str = str.replace(/(?:\\)*\?/g, (val) => {
596
+ return val.charAt(0) === "\\" ? "?" : "";
597
+ });
598
+ return str.replace(/(?:\\)*\*/g, (val) => {
599
+ return val.charAt(0) === "\\" ? "*" : "";
600
+ });
601
+ }
602
+ /**
603
+ * Sets up the regular expression string to allow later insertion of
604
+ * wildcard regular expression matches
605
+ * @param {string} str - The search term to be used
606
+ * @return {string}
607
+ * @access protected
608
+ */
609
+ createWildcardsRegExp(str) {
610
+ let spaces = this.opt.wildcards === "withSpaces";
611
+ return str.replace(/\u0001/g, spaces ? "[\\S\\s]?" : "\\S?").replace(/\u0002/g, spaces ? "[\\S\\s]*?" : "\\S*");
612
+ }
613
+ /**
614
+ * Sets up the regular expression string to allow later insertion of
615
+ * designated characters (soft hyphens & zero width characters)
616
+ * @param {string} str - The search term to be used
617
+ * @return {string}
618
+ * @access protected
619
+ */
620
+ setupIgnoreJoinersRegExp(str) {
621
+ return str.replace(/[^(|)\\]/g, (val, indx, original) => {
622
+ let nextChar = original.charAt(indx + 1);
623
+ if (/[(|)\\]/.test(nextChar) || nextChar === "") return val;
624
+ else return val + "\0";
625
+ });
626
+ }
627
+ /**
628
+ * Creates a regular expression string to allow ignoring of designated
629
+ * characters (soft hyphens, zero width characters & punctuation) based on
630
+ * the specified option values of <code>ignorePunctuation</code> and
631
+ * <code>ignoreJoiners</code>
632
+ * @param {string} str - The search term to be used
633
+ * @return {string}
634
+ * @access protected
635
+ */
636
+ createJoinersRegExp(str) {
637
+ let joiner = [];
638
+ const ignorePunctuation = this.opt.ignorePunctuation;
639
+ if (Array.isArray(ignorePunctuation) && ignorePunctuation.length) joiner.push(this.escapeStr(ignorePunctuation.join("")));
640
+ if (this.opt.ignoreJoiners) joiner.push("\\u00ad\\u200b\\u200c\\u200d");
641
+ return joiner.length ? str.split(/\u0000+/).join(`[${joiner.join("")}]*`) : str;
642
+ }
643
+ /**
644
+ * Creates a regular expression string to match diacritics
645
+ * @param {string} str - The search term to be used
646
+ * @return {string}
647
+ * @access protected
648
+ */
649
+ createDiacriticsRegExp(str) {
650
+ const sens = this.opt.caseSensitive ? "" : "i", dct = this.opt.caseSensitive ? [
651
+ "aàáảãạăằắẳẵặâầấẩẫậäåāą",
652
+ "AÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬÄÅĀĄ",
653
+ "cçćč",
654
+ "CÇĆČ",
655
+ "dđď",
656
+ "DĐĎ",
657
+ "eèéẻẽẹêềếểễệëěēę",
658
+ "EÈÉẺẼẸÊỀẾỂỄỆËĚĒĘ",
659
+ "iìíỉĩịîïī",
660
+ "IÌÍỈĨỊÎÏĪ",
661
+ "lł",
662
+ "LŁ",
663
+ "nñňń",
664
+ "NÑŇŃ",
665
+ "oòóỏõọôồốổỗộơởỡớờợöøō",
666
+ "OÒÓỎÕỌÔỒỐỔỖỘƠỞỠỚỜỢÖØŌ",
667
+ "rř",
668
+ "RŘ",
669
+ "sšśșş",
670
+ "SŠŚȘŞ",
671
+ "tťțţ",
672
+ "TŤȚŢ",
673
+ "uùúủũụưừứửữựûüůū",
674
+ "UÙÚỦŨỤƯỪỨỬỮỰÛÜŮŪ",
675
+ "yýỳỷỹỵÿ",
676
+ "YÝỲỶỸỴŸ",
677
+ "zžżź",
678
+ "ZŽŻŹ"
679
+ ] : [
680
+ "aàáảãạăằắẳẵặâầấẩẫậäåāąAÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬÄÅĀĄ",
681
+ "cçćčCÇĆČ",
682
+ "dđďDĐĎ",
683
+ "eèéẻẽẹêềếểễệëěēęEÈÉẺẼẸÊỀẾỂỄỆËĚĒĘ",
684
+ "iìíỉĩịîïīIÌÍỈĨỊÎÏĪ",
685
+ "lłLŁ",
686
+ "nñňńNÑŇŃ",
687
+ "oòóỏõọôồốổỗộơởỡớờợöøōOÒÓỎÕỌÔỒỐỔỖỘƠỞỠỚỜỢÖØŌ",
688
+ "rřRŘ",
689
+ "sšśșşSŠŚȘŞ",
690
+ "tťțţTŤȚŢ",
691
+ "uùúủũụưừứửữựûüůūUÙÚỦŨỤƯỪỨỬỮỰÛÜŮŪ",
692
+ "yýỳỷỹỵÿYÝỲỶỸỴŸ",
693
+ "zžżźZŽŻŹ"
694
+ ];
695
+ let handled = [];
696
+ str.split("").forEach((ch) => {
697
+ dct.every((dct) => {
698
+ if (dct.indexOf(ch) !== -1) {
699
+ if (handled.indexOf(dct) > -1) return false;
700
+ str = str.replace(new RegExp(`[${dct}]`, `gm${sens}`), `[${dct}]`);
701
+ handled.push(dct);
702
+ }
703
+ return true;
704
+ });
705
+ });
706
+ return str;
707
+ }
708
+ /**
709
+ * Creates a regular expression string that merges whitespace characters
710
+ * including subsequent ones into a single pattern, one or multiple
711
+ * whitespaces
712
+ * @param {string} str - The search term to be used
713
+ * @return {string}
714
+ * @access protected
715
+ */
716
+ createMergedBlanksRegExp(str) {
717
+ return str.replace(/[\s]+/gim, "[\\s]+");
718
+ }
719
+ /**
720
+ * Creates a regular expression string to match the specified string with
721
+ * the defined accuracy. As in the regular expression of "exactly" can be
722
+ * a group containing a blank at the beginning, all regular expressions will
723
+ * be created with two groups. The first group can be ignored (may contain
724
+ * the said blank), the second contains the actual match
725
+ * @param {string} str - The searm term to be used
726
+ * @return {str}
727
+ * @access protected
728
+ */
729
+ createAccuracyRegExp(str) {
730
+ const chars = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~¡¿";
731
+ let acc = this.opt.accuracy, val = typeof acc === "string" ? acc : acc.value, ls = typeof acc === "string" ? [] : acc.limiters, lsJoin = "";
732
+ ls.forEach((limiter) => {
733
+ lsJoin += `|${this.escapeStr(limiter)}`;
734
+ });
735
+ switch (val) {
736
+ case "partially":
737
+ default: return `()(${str})`;
738
+ case "complementary":
739
+ lsJoin = "\\s" + (lsJoin ? lsJoin : this.escapeStr(chars));
740
+ return `()([^${lsJoin}]*${str}[^${lsJoin}]*)`;
741
+ case "exactly": return `(^|\\s${lsJoin})(${str})(?=$|\\s${lsJoin})`;
742
+ }
743
+ }
744
+ /**
745
+ * @typedef Mark~separatedKeywords
746
+ * @type {object.<string>}
747
+ * @property {array.<string>} keywords - The list of keywords
748
+ * @property {number} length - The length
749
+ */
750
+ /**
751
+ * Returns a list of keywords dependent on whether separate word search
752
+ * was defined. Also it filters empty keywords
753
+ * @param {array} sv - The array of keywords
754
+ * @return {Mark~separatedKeywords}
755
+ * @access protected
756
+ */
757
+ getSeparatedKeywords(sv) {
758
+ let stack = [];
759
+ sv.forEach((kw) => {
760
+ if (!this.opt.separateWordSearch) {
761
+ if (kw.trim() && stack.indexOf(kw) === -1) stack.push(kw);
762
+ } else kw.split(" ").forEach((kwSplitted) => {
763
+ if (kwSplitted.trim() && stack.indexOf(kwSplitted) === -1) stack.push(kwSplitted);
764
+ });
765
+ });
766
+ return {
767
+ "keywords": stack.sort((a, b) => {
768
+ return b.length - a.length;
769
+ }),
770
+ "length": stack.length
771
+ };
772
+ }
773
+ /**
774
+ * Check if a value is a number
775
+ * @param {number|string} value - the value to check;
776
+ * numeric strings allowed
777
+ * @return {boolean}
778
+ * @access protected
779
+ */
780
+ isNumeric(value) {
781
+ return Number(parseFloat(value)) == value;
782
+ }
783
+ /**
784
+ * @typedef Mark~rangeObject
785
+ * @type {object}
786
+ * @property {number} start - The start position within the composite value
787
+ * @property {number} length - The length of the string to mark within the
788
+ * composite value.
789
+ */
790
+ /**
791
+ * @typedef Mark~setOfRanges
792
+ * @type {object[]}
793
+ * @property {Mark~rangeObject}
794
+ */
795
+ /**
796
+ * Returns a processed list of integer offset indexes that do not overlap
797
+ * each other, and remove any string values or additional elements
798
+ * @param {Mark~setOfRanges} array - unprocessed raw array
799
+ * @return {Mark~setOfRanges} - processed array with any invalid entries
800
+ * removed
801
+ * @throws Will throw an error if an array of objects is not passed
802
+ * @access protected
803
+ */
804
+ checkRanges(array) {
805
+ if (!Array.isArray(array) || Object.prototype.toString.call(array[0]) !== "[object Object]") {
806
+ this.log("markRanges() will only accept an array of objects");
807
+ this.opt.noMatch(array);
808
+ return [];
809
+ }
810
+ const stack = [];
811
+ let last = 0;
812
+ array.sort((a, b) => {
813
+ return a.start - b.start;
814
+ }).forEach((item) => {
815
+ let { start, end, valid } = this.callNoMatchOnInvalidRanges(item, last);
816
+ if (valid) {
817
+ item.start = start;
818
+ item.length = end - start;
819
+ stack.push(item);
820
+ last = end;
821
+ }
822
+ });
823
+ return stack;
824
+ }
825
+ /**
826
+ * @typedef Mark~validObject
827
+ * @type {object}
828
+ * @property {number} start - The start position within the composite value
829
+ * @property {number} end - The calculated end position within the composite
830
+ * value.
831
+ * @property {boolean} valid - boolean value indicating that the start and
832
+ * calculated end range is valid
833
+ */
834
+ /**
835
+ * Initial validation of ranges for markRanges. Preliminary checks are done
836
+ * to ensure the start and length values exist and are not zero or non-
837
+ * numeric
838
+ * @param {Mark~rangeObject} range - the current range object
839
+ * @param {number} last - last index of range
840
+ * @return {Mark~validObject}
841
+ * @access protected
842
+ */
843
+ callNoMatchOnInvalidRanges(range, last) {
844
+ let start, end, valid = false;
845
+ if (range && typeof range.start !== "undefined") {
846
+ start = parseInt(range.start, 10);
847
+ end = start + parseInt(range.length, 10);
848
+ if (this.isNumeric(range.start) && this.isNumeric(range.length) && end - last > 0 && end - start > 0) valid = true;
849
+ else {
850
+ this.log(`Ignoring invalid or overlapping range: ${JSON.stringify(range)}`);
851
+ this.opt.noMatch(range);
852
+ }
853
+ } else {
854
+ this.log(`Ignoring invalid range: ${JSON.stringify(range)}`);
855
+ this.opt.noMatch(range);
856
+ }
857
+ return {
858
+ start,
859
+ end,
860
+ valid
861
+ };
862
+ }
863
+ /**
864
+ * Check valid range for markRanges. Check ranges with access to the context
865
+ * string. Range values are double checked, lengths that extend the mark
866
+ * beyond the string length are limitied and ranges containing only
867
+ * whitespace are ignored
868
+ * @param {Mark~rangeObject} range - the current range object
869
+ * @param {number} originalLength - original length of the context string
870
+ * @param {string} string - current content string
871
+ * @return {Mark~validObject}
872
+ * @access protected
873
+ */
874
+ checkWhitespaceRanges(range, originalLength, string) {
875
+ let end, valid = true, max = string.length, offset = originalLength - max, start = parseInt(range.start, 10) - offset;
876
+ start = start > max ? max : start;
877
+ end = start + parseInt(range.length, 10);
878
+ if (end > max) {
879
+ end = max;
880
+ this.log(`End range automatically set to the max value of ${max}`);
881
+ }
882
+ if (start < 0 || end - start < 0 || start > max || end > max) {
883
+ valid = false;
884
+ this.log(`Invalid range: ${JSON.stringify(range)}`);
885
+ this.opt.noMatch(range);
886
+ } else if (string.substring(start, end).replace(/\s+/g, "") === "") {
887
+ valid = false;
888
+ this.log("Skipping whitespace only range: " + JSON.stringify(range));
889
+ this.opt.noMatch(range);
890
+ }
891
+ return {
892
+ start,
893
+ end,
894
+ valid
895
+ };
896
+ }
897
+ /**
898
+ * @typedef Mark~getTextNodesDict
899
+ * @type {object.<string>}
900
+ * @property {string} value - The composite value of all text nodes
901
+ * @property {object[]} nodes - An array of objects
902
+ * @property {number} nodes.start - The start position within the composite
903
+ * value
904
+ * @property {number} nodes.end - The end position within the composite
905
+ * value
906
+ * @property {HTMLElement} nodes.node - The DOM text node element
907
+ */
908
+ /**
909
+ * Callback
910
+ * @callback Mark~getTextNodesCallback
911
+ * @param {Mark~getTextNodesDict}
912
+ */
913
+ /**
914
+ * Calls the callback with an object containing all text nodes (including
915
+ * iframe text nodes) with start and end positions and the composite value
916
+ * of them (string)
917
+ * @param {Mark~getTextNodesCallback} cb - Callback
918
+ * @access protected
919
+ */
920
+ getTextNodes(cb) {
921
+ let val = "", nodes = [];
922
+ this.iterator.forEachNode(NodeFilter.SHOW_TEXT, (node) => {
923
+ nodes.push({
924
+ start: val.length,
925
+ end: (val += node.textContent).length,
926
+ node
927
+ });
928
+ }, (node) => {
929
+ if (this.matchesExclude(node.parentNode)) return NodeFilter.FILTER_REJECT;
930
+ else return NodeFilter.FILTER_ACCEPT;
931
+ }, () => {
932
+ cb({
933
+ value: val,
934
+ nodes
935
+ });
936
+ });
937
+ }
938
+ /**
939
+ * Checks if an element matches any of the specified exclude selectors. Also
940
+ * it checks for elements in which no marks should be performed (e.g.
941
+ * script and style tags) and optionally already marked elements
942
+ * @param {HTMLElement} el - The element to check
943
+ * @return {boolean}
944
+ * @access protected
945
+ */
946
+ matchesExclude(el) {
947
+ return DOMIterator.matches(el, this.opt.exclude.concat([
948
+ "script",
949
+ "style",
950
+ "title",
951
+ "head",
952
+ "html"
953
+ ]));
954
+ }
955
+ /**
956
+ * Wraps the instance element and class around matches that fit the start
957
+ * and end positions within the node
958
+ * @param {HTMLElement} node - The DOM text node
959
+ * @param {number} start - The position where to start wrapping
960
+ * @param {number} end - The position where to end wrapping
961
+ * @return {HTMLElement} Returns the splitted text node that will appear
962
+ * after the wrapped text node
963
+ * @access protected
964
+ */
965
+ wrapRangeInTextNode(node, start, end) {
966
+ const hEl = !this.opt.element ? "mark" : this.opt.element, startNode = node.splitText(start), ret = startNode.splitText(end - start);
967
+ let repl = document.createElement(hEl);
968
+ repl.setAttribute("data-markjs", "true");
969
+ if (this.opt.className) repl.setAttribute("class", this.opt.className);
970
+ repl.textContent = startNode.textContent;
971
+ startNode.parentNode.replaceChild(repl, startNode);
972
+ return ret;
973
+ }
974
+ /**
975
+ * @typedef Mark~wrapRangeInMappedTextNodeDict
976
+ * @type {object.<string>}
977
+ * @property {string} value - The composite value of all text nodes
978
+ * @property {object[]} nodes - An array of objects
979
+ * @property {number} nodes.start - The start position within the composite
980
+ * value
981
+ * @property {number} nodes.end - The end position within the composite
982
+ * value
983
+ * @property {HTMLElement} nodes.node - The DOM text node element
984
+ */
985
+ /**
986
+ * Each callback
987
+ * @callback Mark~wrapMatchesEachCallback
988
+ * @param {HTMLElement} node - The wrapped DOM element
989
+ * @param {number} lastIndex - The last matching position within the
990
+ * composite value of text nodes
991
+ */
992
+ /**
993
+ * Filter callback
994
+ * @callback Mark~wrapMatchesFilterCallback
995
+ * @param {HTMLElement} node - The matching text node DOM element
996
+ */
997
+ /**
998
+ * Determines matches by start and end positions using the text node
999
+ * dictionary even across text nodes and calls
1000
+ * {@link Mark#wrapRangeInTextNode} to wrap them
1001
+ * @param {Mark~wrapRangeInMappedTextNodeDict} dict - The dictionary
1002
+ * @param {number} start - The start position of the match
1003
+ * @param {number} end - The end position of the match
1004
+ * @param {Mark~wrapMatchesFilterCallback} filterCb - Filter callback
1005
+ * @param {Mark~wrapMatchesEachCallback} eachCb - Each callback
1006
+ * @access protected
1007
+ */
1008
+ wrapRangeInMappedTextNode(dict, start, end, filterCb, eachCb) {
1009
+ dict.nodes.every((n, i) => {
1010
+ const sibl = dict.nodes[i + 1];
1011
+ if (typeof sibl === "undefined" || sibl.start > start) {
1012
+ if (!filterCb(n.node)) return false;
1013
+ const s = start - n.start, e = (end > n.end ? n.end : end) - n.start, startStr = dict.value.substr(0, n.start), endStr = dict.value.substr(e + n.start);
1014
+ n.node = this.wrapRangeInTextNode(n.node, s, e);
1015
+ dict.value = startStr + endStr;
1016
+ dict.nodes.forEach((k, j) => {
1017
+ if (j >= i) {
1018
+ if (dict.nodes[j].start > 0 && j !== i) dict.nodes[j].start -= e;
1019
+ dict.nodes[j].end -= e;
1020
+ }
1021
+ });
1022
+ end -= e;
1023
+ eachCb(n.node.previousSibling, n.start);
1024
+ if (end > n.end) start = n.end;
1025
+ else return false;
1026
+ }
1027
+ return true;
1028
+ });
1029
+ }
1030
+ /**
1031
+ * Filter callback before each wrapping
1032
+ * @callback Mark~wrapMatchesFilterCallback
1033
+ * @param {string} match - The matching string
1034
+ * @param {HTMLElement} node - The text node where the match occurs
1035
+ */
1036
+ /**
1037
+ * Callback for each wrapped element
1038
+ * @callback Mark~wrapMatchesEachCallback
1039
+ * @param {HTMLElement} element - The marked DOM element
1040
+ */
1041
+ /**
1042
+ * Callback on end
1043
+ * @callback Mark~wrapMatchesEndCallback
1044
+ */
1045
+ /**
1046
+ * Wraps the instance element and class around matches within single HTML
1047
+ * elements in all contexts
1048
+ * @param {RegExp} regex - The regular expression to be searched for
1049
+ * @param {number} ignoreGroups - A number indicating the amount of RegExp
1050
+ * matching groups to ignore
1051
+ * @param {Mark~wrapMatchesFilterCallback} filterCb
1052
+ * @param {Mark~wrapMatchesEachCallback} eachCb
1053
+ * @param {Mark~wrapMatchesEndCallback} endCb
1054
+ * @access protected
1055
+ */
1056
+ wrapMatches(regex, ignoreGroups, filterCb, eachCb, endCb) {
1057
+ const matchIdx = ignoreGroups === 0 ? 0 : ignoreGroups + 1;
1058
+ this.getTextNodes((dict) => {
1059
+ dict.nodes.forEach((node) => {
1060
+ node = node.node;
1061
+ let match;
1062
+ while ((match = regex.exec(node.textContent)) !== null && match[matchIdx] !== "") {
1063
+ if (!filterCb(match[matchIdx], node)) continue;
1064
+ let pos = match.index;
1065
+ if (matchIdx !== 0) for (let i = 1; i < matchIdx; i++) pos += match[i].length;
1066
+ node = this.wrapRangeInTextNode(node, pos, pos + match[matchIdx].length);
1067
+ eachCb(node.previousSibling);
1068
+ regex.lastIndex = 0;
1069
+ }
1070
+ });
1071
+ endCb();
1072
+ });
1073
+ }
1074
+ /**
1075
+ * Callback for each wrapped element
1076
+ * @callback Mark~wrapMatchesAcrossElementsEachCallback
1077
+ * @param {HTMLElement} element - The marked DOM element
1078
+ */
1079
+ /**
1080
+ * Filter callback before each wrapping
1081
+ * @callback Mark~wrapMatchesAcrossElementsFilterCallback
1082
+ * @param {string} match - The matching string
1083
+ * @param {HTMLElement} node - The text node where the match occurs
1084
+ */
1085
+ /**
1086
+ * Callback on end
1087
+ * @callback Mark~wrapMatchesAcrossElementsEndCallback
1088
+ */
1089
+ /**
1090
+ * Wraps the instance element and class around matches across all HTML
1091
+ * elements in all contexts
1092
+ * @param {RegExp} regex - The regular expression to be searched for
1093
+ * @param {number} ignoreGroups - A number indicating the amount of RegExp
1094
+ * matching groups to ignore
1095
+ * @param {Mark~wrapMatchesAcrossElementsFilterCallback} filterCb
1096
+ * @param {Mark~wrapMatchesAcrossElementsEachCallback} eachCb
1097
+ * @param {Mark~wrapMatchesAcrossElementsEndCallback} endCb
1098
+ * @access protected
1099
+ */
1100
+ wrapMatchesAcrossElements(regex, ignoreGroups, filterCb, eachCb, endCb) {
1101
+ const matchIdx = ignoreGroups === 0 ? 0 : ignoreGroups + 1;
1102
+ this.getTextNodes((dict) => {
1103
+ let match;
1104
+ while ((match = regex.exec(dict.value)) !== null && match[matchIdx] !== "") {
1105
+ let start = match.index;
1106
+ if (matchIdx !== 0) for (let i = 1; i < matchIdx; i++) start += match[i].length;
1107
+ const end = start + match[matchIdx].length;
1108
+ this.wrapRangeInMappedTextNode(dict, start, end, (node) => {
1109
+ return filterCb(match[matchIdx], node);
1110
+ }, (node, lastIndex) => {
1111
+ regex.lastIndex = lastIndex;
1112
+ eachCb(node);
1113
+ });
1114
+ }
1115
+ endCb();
1116
+ });
1117
+ }
1118
+ /**
1119
+ * Callback for each wrapped element
1120
+ * @callback Mark~wrapRangeFromIndexEachCallback
1121
+ * @param {HTMLElement} element - The marked DOM element
1122
+ * @param {Mark~rangeObject} range - the current range object; provided
1123
+ * start and length values will be numeric integers modified from the
1124
+ * provided original ranges.
1125
+ */
1126
+ /**
1127
+ * Filter callback before each wrapping
1128
+ * @callback Mark~wrapRangeFromIndexFilterCallback
1129
+ * @param {HTMLElement} node - The text node which includes the range
1130
+ * @param {Mark~rangeObject} range - the current range object
1131
+ * @param {string} match - string extracted from the matching range
1132
+ * @param {number} counter - A counter indicating the number of all marks
1133
+ */
1134
+ /**
1135
+ * Callback on end
1136
+ * @callback Mark~wrapRangeFromIndexEndCallback
1137
+ */
1138
+ /**
1139
+ * Wraps the indicated ranges across all HTML elements in all contexts
1140
+ * @param {Mark~setOfRanges} ranges
1141
+ * @param {Mark~wrapRangeFromIndexFilterCallback} filterCb
1142
+ * @param {Mark~wrapRangeFromIndexEachCallback} eachCb
1143
+ * @param {Mark~wrapRangeFromIndexEndCallback} endCb
1144
+ * @access protected
1145
+ */
1146
+ wrapRangeFromIndex(ranges, filterCb, eachCb, endCb) {
1147
+ this.getTextNodes((dict) => {
1148
+ const originalLength = dict.value.length;
1149
+ ranges.forEach((range, counter) => {
1150
+ let { start, end, valid } = this.checkWhitespaceRanges(range, originalLength, dict.value);
1151
+ if (valid) this.wrapRangeInMappedTextNode(dict, start, end, (node) => {
1152
+ return filterCb(node, range, dict.value.substring(start, end), counter);
1153
+ }, (node) => {
1154
+ eachCb(node, range);
1155
+ });
1156
+ });
1157
+ endCb();
1158
+ });
1159
+ }
1160
+ /**
1161
+ * Unwraps the specified DOM node with its content (text nodes or HTML)
1162
+ * without destroying possibly present events (using innerHTML) and
1163
+ * normalizes the parent at the end (merge splitted text nodes)
1164
+ * @param {HTMLElement} node - The DOM node to unwrap
1165
+ * @access protected
1166
+ */
1167
+ unwrapMatches(node) {
1168
+ const parent = node.parentNode;
1169
+ let docFrag = document.createDocumentFragment();
1170
+ while (node.firstChild) docFrag.appendChild(node.removeChild(node.firstChild));
1171
+ parent.replaceChild(docFrag, node);
1172
+ if (!this.ie) parent.normalize();
1173
+ else this.normalizeTextNode(parent);
1174
+ }
1175
+ /**
1176
+ * Normalizes text nodes. It's a workaround for the native normalize method
1177
+ * that has a bug in IE (see attached link). Should only be used in IE
1178
+ * browsers as it's slower than the native method.
1179
+ * @see {@link http://tinyurl.com/z5asa8c}
1180
+ * @param {HTMLElement} node - The DOM node to normalize
1181
+ * @access protected
1182
+ */
1183
+ normalizeTextNode(node) {
1184
+ if (!node) return;
1185
+ if (node.nodeType === 3) while (node.nextSibling && node.nextSibling.nodeType === 3) {
1186
+ node.nodeValue += node.nextSibling.nodeValue;
1187
+ node.parentNode.removeChild(node.nextSibling);
1188
+ }
1189
+ else this.normalizeTextNode(node.firstChild);
1190
+ this.normalizeTextNode(node.nextSibling);
1191
+ }
1192
+ /**
1193
+ * Callback when finished
1194
+ * @callback Mark~commonDoneCallback
1195
+ * @param {number} totalMatches - The number of marked elements
1196
+ */
1197
+ /**
1198
+ * @typedef Mark~commonOptions
1199
+ * @type {object.<string>}
1200
+ * @property {string} [element="mark"] - HTML element tag name
1201
+ * @property {string} [className] - An optional class name
1202
+ * @property {string[]} [exclude] - An array with exclusion selectors.
1203
+ * Elements matching those selectors will be ignored
1204
+ * @property {boolean} [iframes=false] - Whether to search inside iframes
1205
+ * @property {Mark~commonDoneCallback} [done]
1206
+ * @property {boolean} [debug=false] - Wheter to log messages
1207
+ * @property {object} [log=window.console] - Where to log messages (only if
1208
+ * debug is true)
1209
+ */
1210
+ /**
1211
+ * Callback for each marked element
1212
+ * @callback Mark~markRegExpEachCallback
1213
+ * @param {HTMLElement} element - The marked DOM element
1214
+ */
1215
+ /**
1216
+ * Callback if there were no matches
1217
+ * @callback Mark~markRegExpNoMatchCallback
1218
+ * @param {RegExp} regexp - The regular expression
1219
+ */
1220
+ /**
1221
+ * Callback to filter matches
1222
+ * @callback Mark~markRegExpFilterCallback
1223
+ * @param {HTMLElement} textNode - The text node which includes the match
1224
+ * @param {string} match - The matching string for the RegExp
1225
+ * @param {number} counter - A counter indicating the number of all marks
1226
+ */
1227
+ /**
1228
+ * These options also include the common options from
1229
+ * {@link Mark~commonOptions}
1230
+ * @typedef Mark~markRegExpOptions
1231
+ * @type {object.<string>}
1232
+ * @property {Mark~markRegExpEachCallback} [each]
1233
+ * @property {Mark~markRegExpNoMatchCallback} [noMatch]
1234
+ * @property {Mark~markRegExpFilterCallback} [filter]
1235
+ */
1236
+ /**
1237
+ * Marks a custom regular expression
1238
+ * @param {RegExp} regexp - The regular expression
1239
+ * @param {Mark~markRegExpOptions} [opt] - Optional options object
1240
+ * @access public
1241
+ */
1242
+ markRegExp(regexp, opt) {
1243
+ this.opt = opt;
1244
+ this.log(`Searching with expression "${regexp}"`);
1245
+ let totalMatches = 0, fn = "wrapMatches";
1246
+ const eachCb = (element) => {
1247
+ totalMatches++;
1248
+ this.opt.each(element);
1249
+ };
1250
+ if (this.opt.acrossElements) fn = "wrapMatchesAcrossElements";
1251
+ this[fn](regexp, this.opt.ignoreGroups, (match, node) => {
1252
+ return this.opt.filter(node, match, totalMatches);
1253
+ }, eachCb, () => {
1254
+ if (totalMatches === 0) this.opt.noMatch(regexp);
1255
+ this.opt.done(totalMatches);
1256
+ });
1257
+ }
1258
+ /**
1259
+ * Callback for each marked element
1260
+ * @callback Mark~markEachCallback
1261
+ * @param {HTMLElement} element - The marked DOM element
1262
+ */
1263
+ /**
1264
+ * Callback if there were no matches
1265
+ * @callback Mark~markNoMatchCallback
1266
+ * @param {RegExp} term - The search term that was not found
1267
+ */
1268
+ /**
1269
+ * Callback to filter matches
1270
+ * @callback Mark~markFilterCallback
1271
+ * @param {HTMLElement} textNode - The text node which includes the match
1272
+ * @param {string} match - The matching term
1273
+ * @param {number} totalCounter - A counter indicating the number of all
1274
+ * marks
1275
+ * @param {number} termCounter - A counter indicating the number of marks
1276
+ * for the specific match
1277
+ */
1278
+ /**
1279
+ * @typedef Mark~markAccuracyObject
1280
+ * @type {object.<string>}
1281
+ * @property {string} value - A accuracy string value
1282
+ * @property {string[]} limiters - A custom array of limiters. For example
1283
+ * <code>["-", ","]</code>
1284
+ */
1285
+ /**
1286
+ * @typedef Mark~markAccuracySetting
1287
+ * @type {string}
1288
+ * @property {"partially"|"complementary"|"exactly"|Mark~markAccuracyObject}
1289
+ * [accuracy="partially"] - Either one of the following string values:
1290
+ * <ul>
1291
+ * <li><i>partially</i>: When searching for "lor" only "lor" inside
1292
+ * "lorem" will be marked</li>
1293
+ * <li><i>complementary</i>: When searching for "lor" the whole word
1294
+ * "lorem" will be marked</li>
1295
+ * <li><i>exactly</i>: When searching for "lor" only those exact words
1296
+ * will be marked. In this example nothing inside "lorem". This value
1297
+ * is equivalent to the previous option <i>wordBoundary</i></li>
1298
+ * </ul>
1299
+ * Or an object containing two properties:
1300
+ * <ul>
1301
+ * <li><i>value</i>: One of the above named string values</li>
1302
+ * <li><i>limiters</i>: A custom array of string limiters for accuracy
1303
+ * "exactly" or "complementary"</li>
1304
+ * </ul>
1305
+ */
1306
+ /**
1307
+ * @typedef Mark~markWildcardsSetting
1308
+ * @type {string}
1309
+ * @property {"disabled"|"enabled"|"withSpaces"}
1310
+ * [wildcards="disabled"] - Set to any of the following string values:
1311
+ * <ul>
1312
+ * <li><i>disabled</i>: Disable wildcard usage</li>
1313
+ * <li><i>enabled</i>: When searching for "lor?m", the "?" will match zero
1314
+ * or one non-space character (e.g. "lorm", "loram", "lor3m", etc). When
1315
+ * searching for "lor*m", the "*" will match zero or more non-space
1316
+ * characters (e.g. "lorm", "loram", "lor123m", etc).</li>
1317
+ * <li><i>withSpaces</i>: When searching for "lor?m", the "?" will
1318
+ * match zero or one space or non-space character (e.g. "lor m", "loram",
1319
+ * etc). When searching for "lor*m", the "*" will match zero or more space
1320
+ * or non-space characters (e.g. "lorm", "lore et dolor ipsum", "lor: m",
1321
+ * etc).</li>
1322
+ * </ul>
1323
+ */
1324
+ /**
1325
+ * @typedef Mark~markIgnorePunctuationSetting
1326
+ * @type {string[]}
1327
+ * @property {string} The strings in this setting will contain punctuation
1328
+ * marks that will be ignored:
1329
+ * <ul>
1330
+ * <li>These punctuation marks can be between any characters, e.g. setting
1331
+ * this option to <code>["'"]</code> would match "Worlds", "World's" and
1332
+ * "Wo'rlds"</li>
1333
+ * <li>One or more apostrophes between the letters would still produce a
1334
+ * match (e.g. "W'o''r'l'd's").</li>
1335
+ * <li>A typical setting for this option could be as follows:
1336
+ * <pre>ignorePunctuation: ":;.,-–—‒_(){}[]!'\"+=".split(""),</pre> This
1337
+ * setting includes common punctuation as well as a minus, en-dash,
1338
+ * em-dash and figure-dash
1339
+ * ({@link https://en.wikipedia.org/wiki/Dash#Figure_dash ref}), as well
1340
+ * as an underscore.</li>
1341
+ * </ul>
1342
+ */
1343
+ /**
1344
+ * These options also include the common options from
1345
+ * {@link Mark~commonOptions}
1346
+ * @typedef Mark~markOptions
1347
+ * @type {object.<string>}
1348
+ * @property {boolean} [separateWordSearch=true] - Whether to search for
1349
+ * each word separated by a blank instead of the complete term
1350
+ * @property {boolean} [diacritics=true] - If diacritic characters should be
1351
+ * matched. ({@link https://en.wikipedia.org/wiki/Diacritic Diacritics})
1352
+ * @property {object} [synonyms] - An object with synonyms. The key will be
1353
+ * a synonym for the value and the value for the key
1354
+ * @property {Mark~markAccuracySetting} [accuracy]
1355
+ * @property {Mark~markWildcardsSetting} [wildcards]
1356
+ * @property {boolean} [acrossElements=false] - Whether to find matches
1357
+ * across HTML elements. By default, only matches within single HTML
1358
+ * elements will be found
1359
+ * @property {boolean} [ignoreJoiners=false] - Whether to ignore word
1360
+ * joiners inside of key words. These include soft-hyphens, zero-width
1361
+ * space, zero-width non-joiners and zero-width joiners.
1362
+ * @property {Mark~markIgnorePunctuationSetting} [ignorePunctuation]
1363
+ * @property {Mark~markEachCallback} [each]
1364
+ * @property {Mark~markNoMatchCallback} [noMatch]
1365
+ * @property {Mark~markFilterCallback} [filter]
1366
+ */
1367
+ /**
1368
+ * Marks the specified search terms
1369
+ * @param {string|string[]} [sv] - Search value, either a search string or
1370
+ * an array containing multiple search strings
1371
+ * @param {Mark~markOptions} [opt] - Optional options object
1372
+ * @access public
1373
+ */
1374
+ mark(sv, opt) {
1375
+ this.opt = opt;
1376
+ let totalMatches = 0, fn = "wrapMatches";
1377
+ const { keywords: kwArr, length: kwArrLen } = this.getSeparatedKeywords(typeof sv === "string" ? [sv] : sv), sens = this.opt.caseSensitive ? "" : "i", handler = (kw) => {
1378
+ let regex = new RegExp(this.createRegExp(kw), `gm${sens}`), matches = 0;
1379
+ this.log(`Searching with expression "${regex}"`);
1380
+ this[fn](regex, 1, (term, node) => {
1381
+ return this.opt.filter(node, kw, totalMatches, matches);
1382
+ }, (element) => {
1383
+ matches++;
1384
+ totalMatches++;
1385
+ this.opt.each(element);
1386
+ }, () => {
1387
+ if (matches === 0) this.opt.noMatch(kw);
1388
+ if (kwArr[kwArrLen - 1] === kw) this.opt.done(totalMatches);
1389
+ else handler(kwArr[kwArr.indexOf(kw) + 1]);
1390
+ });
1391
+ };
1392
+ if (this.opt.acrossElements) fn = "wrapMatchesAcrossElements";
1393
+ if (kwArrLen === 0) this.opt.done(totalMatches);
1394
+ else handler(kwArr[0]);
1395
+ }
1396
+ /**
1397
+ * Callback for each marked element
1398
+ * @callback Mark~markRangesEachCallback
1399
+ * @param {HTMLElement} element - The marked DOM element
1400
+ * @param {array} range - array of range start and end points
1401
+ */
1402
+ /**
1403
+ * Callback if a processed range is invalid, out-of-bounds, overlaps another
1404
+ * range, or only matches whitespace
1405
+ * @callback Mark~markRangesNoMatchCallback
1406
+ * @param {Mark~rangeObject} range - a range object
1407
+ */
1408
+ /**
1409
+ * Callback to filter matches
1410
+ * @callback Mark~markRangesFilterCallback
1411
+ * @param {HTMLElement} node - The text node which includes the range
1412
+ * @param {array} range - array of range start and end points
1413
+ * @param {string} match - string extracted from the matching range
1414
+ * @param {number} counter - A counter indicating the number of all marks
1415
+ */
1416
+ /**
1417
+ * These options also include the common options from
1418
+ * {@link Mark~commonOptions}
1419
+ * @typedef Mark~markRangesOptions
1420
+ * @type {object.<string>}
1421
+ * @property {Mark~markRangesEachCallback} [each]
1422
+ * @property {Mark~markRangesNoMatchCallback} [noMatch]
1423
+ * @property {Mark~markRangesFilterCallback} [filter]
1424
+ */
1425
+ /**
1426
+ * Marks an array of objects containing a start with an end or length of the
1427
+ * string to mark
1428
+ * @param {Mark~setOfRanges} rawRanges - The original (preprocessed)
1429
+ * array of objects
1430
+ * @param {Mark~markRangesOptions} [opt] - Optional options object
1431
+ * @access public
1432
+ */
1433
+ markRanges(rawRanges, opt) {
1434
+ this.opt = opt;
1435
+ let totalMatches = 0, ranges = this.checkRanges(rawRanges);
1436
+ if (ranges && ranges.length) {
1437
+ this.log("Starting to mark with the following ranges: " + JSON.stringify(ranges));
1438
+ this.wrapRangeFromIndex(ranges, (node, range, match, counter) => {
1439
+ return this.opt.filter(node, range, match, counter);
1440
+ }, (element, range) => {
1441
+ totalMatches++;
1442
+ this.opt.each(element, range);
1443
+ }, () => {
1444
+ this.opt.done(totalMatches);
1445
+ });
1446
+ } else this.opt.done(totalMatches);
1447
+ }
1448
+ /**
1449
+ * Removes all marked elements inside the context with their HTML and
1450
+ * normalizes the parent at the end
1451
+ * @param {Mark~commonOptions} [opt] - Optional options object
1452
+ * @access public
1453
+ */
1454
+ unmark(opt) {
1455
+ this.opt = opt;
1456
+ let sel = this.opt.element ? this.opt.element : "*";
1457
+ sel += "[data-markjs]";
1458
+ if (this.opt.className) sel += `.${this.opt.className}`;
1459
+ this.log(`Removal selector "${sel}"`);
1460
+ this.iterator.forEachNode(NodeFilter.SHOW_ELEMENT, (node) => {
1461
+ this.unwrapMatches(node);
1462
+ }, (node) => {
1463
+ const matchesSel = DOMIterator.matches(node, sel), matchesExclude = this.matchesExclude(node);
1464
+ if (!matchesSel || matchesExclude) return NodeFilter.FILTER_REJECT;
1465
+ else return NodeFilter.FILTER_ACCEPT;
1466
+ }, this.opt.done);
1467
+ }
1468
+ };
1469
+ //#endregion
1470
+ //#region node_modules/mark.js/src/vanilla.js
1471
+ function Mark(ctx) {
1472
+ const instance = new Mark$1(ctx);
1473
+ this.mark = (sv, opt) => {
1474
+ instance.mark(sv, opt);
1475
+ return this;
1476
+ };
1477
+ this.markRegExp = (sv, opt) => {
1478
+ instance.markRegExp(sv, opt);
1479
+ return this;
1480
+ };
1481
+ this.markRanges = (sv, opt) => {
1482
+ instance.markRanges(sv, opt);
1483
+ return this;
1484
+ };
1485
+ this.unmark = (opt) => {
1486
+ instance.unmark(opt);
1487
+ return this;
1488
+ };
1489
+ return this;
1490
+ }
1491
+ //#endregion
1492
+ export { Mark as default };