reactive-route 0.0.1-alpha.26 → 0.0.1-alpha.28

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