[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
1 (function () { 2 'use strict'; 3 4 function _defineProperties(target, props) { 5 for (var i = 0; i < props.length; i++) { 6 var descriptor = props[i]; 7 descriptor.enumerable = descriptor.enumerable || false; 8 descriptor.configurable = true; 9 if ("value" in descriptor) descriptor.writable = true; 10 Object.defineProperty(target, descriptor.key, descriptor); 11 } 12 } 13 14 function _createClass(Constructor, protoProps, staticProps) { 15 if (protoProps) _defineProperties(Constructor.prototype, protoProps); 16 if (staticProps) _defineProperties(Constructor, staticProps); 17 Object.defineProperty(Constructor, "prototype", { 18 writable: false 19 }); 20 return Constructor; 21 } 22 23 /** 24 * A NodeIterator with iframes support and a method to check if an element is 25 * matching a specified selector 26 * @example 27 * const iterator = new DOMIterator( 28 * document.querySelector("#context"), true 29 * ); 30 * iterator.forEachNode(NodeFilter.SHOW_TEXT, node => { 31 * console.log(node); 32 * }, node => { 33 * if(DOMIterator.matches(node.parentNode, ".ignore")){ 34 * return NodeFilter.FILTER_REJECT; 35 * } else { 36 * return NodeFilter.FILTER_ACCEPT; 37 * } 38 * }, () => { 39 * console.log("DONE"); 40 * }); 41 * @todo Outsource into separate repository 42 */ 43 var DOMIterator = /*#__PURE__*/function () { 44 /** 45 * @param {HTMLElement|HTMLElement[]|NodeList|string} ctx - The context DOM 46 * element, an array of DOM elements, a NodeList or a selector 47 * @param {boolean} [iframes=true] - A boolean indicating if iframes should 48 * be handled 49 * @param {string[]} [exclude=[]] - An array containing exclusion selectors 50 * for iframes 51 * @param {number} [iframesTimeout=5000] - A number indicating the ms to 52 * wait before an iframe should be skipped, in case the load event isn't 53 * fired. This also applies if the user is offline and the resource of the 54 * iframe is online (either by the browsers "offline" mode or because 55 * there's no internet connection) 56 */ 57 function DOMIterator(ctx, iframes, exclude, iframesTimeout) { 58 if (iframes === void 0) { 59 iframes = true; 60 } 61 62 if (exclude === void 0) { 63 exclude = []; 64 } 65 66 if (iframesTimeout === void 0) { 67 iframesTimeout = 5000; 68 } 69 70 /** 71 * The context of the instance. Either a DOM element, an array of DOM 72 * elements, a NodeList or a selector 73 * @type {HTMLElement|HTMLElement[]|NodeList|string} 74 * @access protected 75 */ 76 this.ctx = ctx; 77 /** 78 * Boolean indicating if iframe support is enabled 79 * @type {boolean} 80 * @access protected 81 */ 82 83 this.iframes = iframes; 84 /** 85 * An array containing exclusion selectors for iframes 86 * @type {string[]} 87 */ 88 89 this.exclude = exclude; 90 /** 91 * The maximum ms to wait for a load event before skipping an iframe 92 * @type {number} 93 */ 94 95 this.iframesTimeout = iframesTimeout; 96 } 97 /** 98 * Checks if the specified DOM element matches the selector 99 * @param {HTMLElement} element - The DOM element 100 * @param {string|string[]} selector - The selector or an array with 101 * selectors 102 * @return {boolean} 103 * @access public 104 */ 105 106 107 DOMIterator.matches = function matches(element, selector) { 108 var selectors = typeof selector === 'string' ? [selector] : selector, 109 fn = element.matches || element.matchesSelector || element.msMatchesSelector || element.mozMatchesSelector || element.oMatchesSelector || element.webkitMatchesSelector; 110 111 if (fn) { 112 var match = false; 113 selectors.every(function (sel) { 114 if (fn.call(element, sel)) { 115 match = true; 116 return false; 117 } 118 119 return true; 120 }); 121 return match; 122 } else { 123 // may be false e.g. when el is a textNode 124 return false; 125 } 126 } 127 /** 128 * Returns all contexts filtered by duplicates (even nested) 129 * @return {HTMLElement[]} - An array containing DOM contexts 130 * @access protected 131 */ 132 ; 133 134 var _proto = DOMIterator.prototype; 135 136 _proto.getContexts = function getContexts() { 137 var ctx, 138 filteredCtx = []; 139 140 if (typeof this.ctx === 'undefined' || !this.ctx) { 141 // e.g. null 142 ctx = []; 143 } else if (NodeList.prototype.isPrototypeOf(this.ctx)) { 144 ctx = Array.prototype.slice.call(this.ctx); 145 } else if (Array.isArray(this.ctx)) { 146 ctx = this.ctx; 147 } else if (typeof this.ctx === 'string') { 148 ctx = Array.prototype.slice.call(document.querySelectorAll(this.ctx)); 149 } else { 150 // e.g. HTMLElement or element inside iframe 151 ctx = [this.ctx]; 152 } // filter duplicate text nodes 153 154 155 ctx.forEach(function (ctx) { 156 var isDescendant = filteredCtx.filter(function (contexts) { 157 return contexts.contains(ctx); 158 }).length > 0; 159 160 if (filteredCtx.indexOf(ctx) === -1 && !isDescendant) { 161 filteredCtx.push(ctx); 162 } 163 }); 164 return filteredCtx; 165 } 166 /** 167 * @callback DOMIterator~getIframeContentsSuccessCallback 168 * @param {HTMLDocument} contents - The contentDocument of the iframe 169 */ 170 171 /** 172 * Calls the success callback function with the iframe document. If it can't 173 * be accessed it calls the error callback function 174 * @param {HTMLElement} ifr - The iframe DOM element 175 * @param {DOMIterator~getIframeContentsSuccessCallback} successFn 176 * @param {function} [errorFn] 177 * @access protected 178 */ 179 ; 180 181 _proto.getIframeContents = function getIframeContents(ifr, successFn, errorFn) { 182 if (errorFn === void 0) { 183 errorFn = function errorFn() {}; 184 } 185 186 var doc; 187 188 try { 189 var ifrWin = ifr.contentWindow; 190 doc = ifrWin.document; 191 192 if (!ifrWin || !doc) { 193 // no permission = null. Undefined in Phantom 194 throw new Error('iframe inaccessible'); 195 } 196 } catch (e) { 197 errorFn(); 198 } 199 200 if (doc) { 201 successFn(doc); 202 } 203 } 204 /** 205 * Checks if an iframe is empty (if about:blank is the shown page) 206 * @param {HTMLElement} ifr - The iframe DOM element 207 * @return {boolean} 208 * @access protected 209 */ 210 ; 211 212 _proto.isIframeBlank = function isIframeBlank(ifr) { 213 var bl = 'about:blank', 214 src = ifr.getAttribute('src').trim(), 215 href = ifr.contentWindow.location.href; 216 return href === bl && src !== bl && src; 217 } 218 /** 219 * Observes the onload event of an iframe and calls the success callback or 220 * the error callback if the iframe is inaccessible. If the event isn't 221 * fired within the specified {@link DOMIterator#iframesTimeout}, then it'll 222 * call the error callback too 223 * @param {HTMLElement} ifr - The iframe DOM element 224 * @param {DOMIterator~getIframeContentsSuccessCallback} successFn 225 * @param {function} errorFn 226 * @access protected 227 */ 228 ; 229 230 _proto.observeIframeLoad = function observeIframeLoad(ifr, successFn, errorFn) { 231 var _this = this; 232 233 var called = false, 234 tout = null; 235 236 var listener = function listener() { 237 if (called) { 238 return; 239 } 240 241 called = true; 242 clearTimeout(tout); 243 244 try { 245 if (!_this.isIframeBlank(ifr)) { 246 ifr.removeEventListener('load', listener); 247 248 _this.getIframeContents(ifr, successFn, errorFn); 249 } 250 } catch (e) { 251 // isIframeBlank maybe throws throws an error 252 errorFn(); 253 } 254 }; 255 256 ifr.addEventListener('load', listener); 257 tout = setTimeout(listener, this.iframesTimeout); 258 } 259 /** 260 * Callback when the iframe is ready 261 * @callback DOMIterator~onIframeReadySuccessCallback 262 * @param {HTMLDocument} contents - The contentDocument of the iframe 263 */ 264 265 /** 266 * Callback if the iframe can't be accessed 267 * @callback DOMIterator~onIframeReadyErrorCallback 268 */ 269 270 /** 271 * Calls the callback if the specified iframe is ready for DOM access 272 * @param {HTMLElement} ifr - The iframe DOM element 273 * @param {DOMIterator~onIframeReadySuccessCallback} successFn - Success 274 * callback 275 * @param {DOMIterator~onIframeReadyErrorCallback} errorFn - Error callback 276 * @see {@link http://stackoverflow.com/a/36155560/3894981} for 277 * background information 278 * @access protected 279 */ 280 ; 281 282 _proto.onIframeReady = function onIframeReady(ifr, successFn, errorFn) { 283 try { 284 if (ifr.contentWindow.document.readyState === 'complete') { 285 if (this.isIframeBlank(ifr)) { 286 this.observeIframeLoad(ifr, successFn, errorFn); 287 } else { 288 this.getIframeContents(ifr, successFn, errorFn); 289 } 290 } else { 291 this.observeIframeLoad(ifr, successFn, errorFn); 292 } 293 } catch (e) { 294 // accessing document failed 295 errorFn(); 296 } 297 } 298 /** 299 * Callback when all iframes are ready for DOM access 300 * @callback DOMIterator~waitForIframesDoneCallback 301 */ 302 303 /** 304 * Iterates over all iframes and calls the done callback when all of them 305 * are ready for DOM access (including nested ones) 306 * @param {HTMLElement} ctx - The context DOM element 307 * @param {DOMIterator~waitForIframesDoneCallback} done - Done callback 308 */ 309 ; 310 311 _proto.waitForIframes = function waitForIframes(ctx, done) { 312 var _this2 = this; 313 314 var eachCalled = 0; 315 this.forEachIframe(ctx, function () { 316 return true; 317 }, function (ifr) { 318 eachCalled++; 319 320 _this2.waitForIframes(ifr.querySelector('html'), function () { 321 if (! --eachCalled) { 322 done(); 323 } 324 }); 325 }, function (handled) { 326 if (!handled) { 327 done(); 328 } 329 }); 330 } 331 /** 332 * Callback allowing to filter an iframe. Must return true when the element 333 * should remain, otherwise false 334 * @callback DOMIterator~forEachIframeFilterCallback 335 * @param {HTMLElement} iframe - The iframe DOM element 336 */ 337 338 /** 339 * Callback for each iframe content 340 * @callback DOMIterator~forEachIframeEachCallback 341 * @param {HTMLElement} content - The iframe document 342 */ 343 344 /** 345 * Callback if all iframes inside the context were handled 346 * @callback DOMIterator~forEachIframeEndCallback 347 * @param {number} handled - The number of handled iframes (those who 348 * wheren't filtered) 349 */ 350 351 /** 352 * Iterates over all iframes inside the specified context and calls the 353 * callbacks when they're ready. Filters iframes based on the instance 354 * exclusion selectors 355 * @param {HTMLElement} ctx - The context DOM element 356 * @param {DOMIterator~forEachIframeFilterCallback} filter - Filter callback 357 * @param {DOMIterator~forEachIframeEachCallback} each - Each callback 358 * @param {DOMIterator~forEachIframeEndCallback} [end] - End callback 359 * @access protected 360 */ 361 ; 362 363 _proto.forEachIframe = function forEachIframe(ctx, filter, each, end) { 364 var _this3 = this; 365 366 if (end === void 0) { 367 end = function end() {}; 368 } 369 370 var ifr = ctx.querySelectorAll('iframe'), 371 open = ifr.length, 372 handled = 0; 373 ifr = Array.prototype.slice.call(ifr); 374 375 var checkEnd = function checkEnd() { 376 if (--open <= 0) { 377 end(handled); 378 } 379 }; 380 381 if (!open) { 382 checkEnd(); 383 } 384 385 ifr.forEach(function (ifr) { 386 if (DOMIterator.matches(ifr, _this3.exclude)) { 387 checkEnd(); 388 } else { 389 _this3.onIframeReady(ifr, function (con) { 390 if (filter(ifr)) { 391 handled++; 392 each(con); 393 } 394 395 checkEnd(); 396 }, checkEnd); 397 } 398 }); 399 } 400 /** 401 * Creates a NodeIterator on the specified context 402 * @see {@link https://developer.mozilla.org/en/docs/Web/API/NodeIterator} 403 * @param {HTMLElement} ctx - The context DOM element 404 * @param {DOMIterator~whatToShow} whatToShow 405 * @param {DOMIterator~filterCb} filter 406 * @return {NodeIterator} 407 * @access protected 408 */ 409 ; 410 411 _proto.createIterator = function createIterator(ctx, whatToShow, filter) { 412 return document.createNodeIterator(ctx, whatToShow, filter, false); 413 } 414 /** 415 * Creates an instance of DOMIterator in an iframe 416 * @param {HTMLDocument} contents - Iframe document 417 * @return {DOMIterator} 418 * @access protected 419 */ 420 ; 421 422 _proto.createInstanceOnIframe = function createInstanceOnIframe(contents) { 423 return new DOMIterator(contents.querySelector('html'), this.iframes); 424 } 425 /** 426 * Checks if an iframe occurs between two nodes, more specifically if an 427 * iframe occurs before the specified node and after the specified prevNode 428 * @param {HTMLElement} node - The node that should occur after the iframe 429 * @param {HTMLElement} prevNode - The node that should occur before the 430 * iframe 431 * @param {HTMLElement} ifr - The iframe to check against 432 * @return {boolean} 433 * @access protected 434 */ 435 ; 436 437 _proto.compareNodeIframe = function compareNodeIframe(node, prevNode, ifr) { 438 var compCurr = node.compareDocumentPosition(ifr), 439 prev = Node.DOCUMENT_POSITION_PRECEDING; 440 441 if (compCurr & prev) { 442 if (prevNode !== null) { 443 var compPrev = prevNode.compareDocumentPosition(ifr), 444 after = Node.DOCUMENT_POSITION_FOLLOWING; 445 446 if (compPrev & after) { 447 return true; 448 } 449 } else { 450 return true; 451 } 452 } 453 454 return false; 455 } 456 /** 457 * @typedef {DOMIterator~getIteratorNodeReturn} 458 * @type {object.<string>} 459 * @property {HTMLElement} prevNode - The previous node or null if there is 460 * no 461 * @property {HTMLElement} node - The current node 462 */ 463 464 /** 465 * Returns the previous and current node of the specified iterator 466 * @param {NodeIterator} itr - The iterator 467 * @return {DOMIterator~getIteratorNodeReturn} 468 * @access protected 469 */ 470 ; 471 472 _proto.getIteratorNode = function getIteratorNode(itr) { 473 var prevNode = itr.previousNode(); 474 var node; 475 476 if (prevNode === null) { 477 node = itr.nextNode(); 478 } else { 479 node = itr.nextNode() && itr.nextNode(); 480 } 481 482 return { 483 prevNode: prevNode, 484 node: node 485 }; 486 } 487 /** 488 * An array containing objects. The object key "val" contains an iframe 489 * DOM element. The object key "handled" contains a boolean indicating if 490 * the iframe was handled already. 491 * It wouldn't be enough to save all open or all already handled iframes. 492 * The information of open iframes is necessary because they may occur after 493 * all other text nodes (and compareNodeIframe would never be true). The 494 * information of already handled iframes is necessary as otherwise they may 495 * be handled multiple times 496 * @typedef DOMIterator~checkIframeFilterIfr 497 * @type {object[]} 498 */ 499 500 /** 501 * Checks if an iframe wasn't handled already and if so, calls 502 * {@link DOMIterator#compareNodeIframe} to check if it should be handled. 503 * Information wheter an iframe was or wasn't handled is given within the 504 * <code>ifr</code> dictionary 505 * @param {HTMLElement} node - The node that should occur after the iframe 506 * @param {HTMLElement} prevNode - The node that should occur before the 507 * iframe 508 * @param {HTMLElement} currIfr - The iframe to check 509 * @param {DOMIterator~checkIframeFilterIfr} ifr - The iframe dictionary. 510 * Will be manipulated (by reference) 511 * @return {boolean} Returns true when it should be handled, otherwise false 512 * @access protected 513 */ 514 ; 515 516 _proto.checkIframeFilter = function checkIframeFilter(node, prevNode, currIfr, ifr) { 517 var key = false, 518 // false === doesn't exist 519 handled = false; 520 ifr.forEach(function (ifrDict, i) { 521 if (ifrDict.val === currIfr) { 522 key = i; 523 handled = ifrDict.handled; 524 } 525 }); 526 527 if (this.compareNodeIframe(node, prevNode, currIfr)) { 528 if (key === false && !handled) { 529 ifr.push({ 530 val: currIfr, 531 handled: true 532 }); 533 } else if (key !== false && !handled) { 534 ifr[key].handled = true; 535 } 536 537 return true; 538 } 539 540 if (key === false) { 541 ifr.push({ 542 val: currIfr, 543 handled: false 544 }); 545 } 546 547 return false; 548 } 549 /** 550 * Creates an iterator on all open iframes in the specified array and calls 551 * the end callback when finished 552 * @param {DOMIterator~checkIframeFilterIfr} ifr 553 * @param {DOMIterator~whatToShow} whatToShow 554 * @param {DOMIterator~forEachNodeCallback} eCb - Each callback 555 * @param {DOMIterator~filterCb} fCb 556 * @access protected 557 */ 558 ; 559 560 _proto.handleOpenIframes = function handleOpenIframes(ifr, whatToShow, eCb, fCb) { 561 var _this4 = this; 562 563 ifr.forEach(function (ifrDict) { 564 if (!ifrDict.handled) { 565 _this4.getIframeContents(ifrDict.val, function (con) { 566 _this4.createInstanceOnIframe(con).forEachNode(whatToShow, eCb, fCb); 567 }); 568 } 569 }); 570 } 571 /** 572 * Iterates through all nodes in the specified context and handles iframe 573 * nodes at the correct position 574 * @param {DOMIterator~whatToShow} whatToShow 575 * @param {HTMLElement} ctx - The context 576 * @param {DOMIterator~forEachNodeCallback} eachCb - Each callback 577 * @param {DOMIterator~filterCb} filterCb - Filter callback 578 * @param {DOMIterator~forEachNodeEndCallback} doneCb - End callback 579 * @access protected 580 */ 581 ; 582 583 _proto.iterateThroughNodes = function iterateThroughNodes(whatToShow, ctx, eachCb, filterCb, doneCb) { 584 var _this5 = this; 585 586 var itr = this.createIterator(ctx, whatToShow, filterCb); 587 588 var ifr = [], 589 elements = [], 590 node, 591 prevNode, 592 retrieveNodes = function retrieveNodes() { 593 var _this5$getIteratorNod = _this5.getIteratorNode(itr); 594 595 prevNode = _this5$getIteratorNod.prevNode; 596 node = _this5$getIteratorNod.node; 597 return node; 598 }; 599 600 while (retrieveNodes()) { 601 if (this.iframes) { 602 this.forEachIframe(ctx, function (currIfr) { 603 // note that ifr will be manipulated here 604 return _this5.checkIframeFilter(node, prevNode, currIfr, ifr); 605 }, function (con) { 606 _this5.createInstanceOnIframe(con).forEachNode(whatToShow, function (ifrNode) { 607 return elements.push(ifrNode); 608 }, filterCb); 609 }); 610 } // it's faster to call the each callback in an array loop 611 // than in this while loop 612 613 614 elements.push(node); 615 } 616 617 elements.forEach(function (node) { 618 eachCb(node); 619 }); 620 621 if (this.iframes) { 622 this.handleOpenIframes(ifr, whatToShow, eachCb, filterCb); 623 } 624 625 doneCb(); 626 } 627 /** 628 * Callback for each node 629 * @callback DOMIterator~forEachNodeCallback 630 * @param {HTMLElement} node - The DOM text node element 631 */ 632 633 /** 634 * Callback if all contexts were handled 635 * @callback DOMIterator~forEachNodeEndCallback 636 */ 637 638 /** 639 * Iterates over all contexts and initializes 640 * {@link DOMIterator#iterateThroughNodes iterateThroughNodes} on them 641 * @param {DOMIterator~whatToShow} whatToShow 642 * @param {DOMIterator~forEachNodeCallback} each - Each callback 643 * @param {DOMIterator~filterCb} filter - Filter callback 644 * @param {DOMIterator~forEachNodeEndCallback} done - End callback 645 * @access public 646 */ 647 ; 648 649 _proto.forEachNode = function forEachNode(whatToShow, each, filter, done) { 650 var _this6 = this; 651 652 if (done === void 0) { 653 done = function done() {}; 654 } 655 656 var contexts = this.getContexts(); 657 var open = contexts.length; 658 659 if (!open) { 660 done(); 661 } 662 663 contexts.forEach(function (ctx) { 664 var ready = function ready() { 665 _this6.iterateThroughNodes(whatToShow, ctx, each, filter, function () { 666 if (--open <= 0) { 667 // call end all contexts were handled 668 done(); 669 } 670 }); 671 }; // wait for iframes to avoid recursive calls, otherwise this would 672 // perhaps reach the recursive function call limit with many nodes 673 674 675 if (_this6.iframes) { 676 _this6.waitForIframes(ctx, ready); 677 } else { 678 ready(); 679 } 680 }); 681 } 682 /** 683 * Callback to filter nodes. Can return e.g. NodeFilter.FILTER_ACCEPT or 684 * NodeFilter.FILTER_REJECT 685 * @see {@link http://tinyurl.com/zdczmm2} 686 * @callback DOMIterator~filterCb 687 * @param {HTMLElement} node - The node to filter 688 */ 689 690 /** 691 * @typedef DOMIterator~whatToShow 692 * @see {@link http://tinyurl.com/zfqqkx2} 693 * @type {number} 694 */ 695 ; 696 697 return DOMIterator; 698 }(); 699 /** 700 * Marks search terms in DOM elements 701 * @example 702 * new Mark(document.querySelector(".context")).mark("lorem ipsum"); 703 * @example 704 * new Mark(document.querySelector(".context")).markRegExp(/lorem/gmi); 705 */ 706 707 708 var Mark$1 = /*#__PURE__*/function () { 709 // eslint-disable-line no-unused-vars 710 711 /** 712 * @param {HTMLElement|HTMLElement[]|NodeList|string} ctx - The context DOM 713 * element, an array of DOM elements, a NodeList or a selector 714 */ 715 function Mark$1(ctx) { 716 /** 717 * The context of the instance. Either a DOM element, an array of DOM 718 * elements, a NodeList or a selector 719 * @type {HTMLElement|HTMLElement[]|NodeList|string} 720 * @access protected 721 */ 722 this.ctx = ctx; 723 /** 724 * Specifies if the current browser is a IE (necessary for the node 725 * normalization bug workaround). See {@link Mark#unwrapMatches} 726 * @type {boolean} 727 * @access protected 728 */ 729 730 this.ie = false; 731 var ua = window.navigator.userAgent; 732 733 if (ua.indexOf('MSIE') > -1 || ua.indexOf('Trident') > -1) { 734 this.ie = true; 735 } 736 } 737 /** 738 * Options defined by the user. They will be initialized from one of the 739 * public methods. See {@link Mark#mark}, {@link Mark#markRegExp}, 740 * {@link Mark#markRanges} and {@link Mark#unmark} for option properties. 741 * @type {object} 742 * @param {object} [val] - An object that will be merged with defaults 743 * @access protected 744 */ 745 746 747 var _proto2 = Mark$1.prototype; 748 749 /** 750 * Logs a message if log is enabled 751 * @param {string} msg - The message to log 752 * @param {string} [level="debug"] - The log level, e.g. <code>warn</code> 753 * <code>error</code>, <code>debug</code> 754 * @access protected 755 */ 756 _proto2.log = function log(msg, level) { 757 if (level === void 0) { 758 level = 'debug'; 759 } 760 761 var log = this.opt.log; 762 763 if (!this.opt.debug) { 764 return; 765 } 766 767 if (typeof log === 'object' && typeof log[level] === 'function') { 768 log[level]("mark.js: " + msg); 769 } 770 } 771 /** 772 * Escapes a string for usage within a regular expression 773 * @param {string} str - The string to escape 774 * @return {string} 775 * @access protected 776 */ 777 ; 778 779 _proto2.escapeStr = function escapeStr(str) { 780 // eslint-disable-next-line no-useless-escape 781 return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); 782 } 783 /** 784 * Creates a regular expression string to match the specified search 785 * term including synonyms, diacritics and accuracy if defined 786 * @param {string} str - The search term to be used 787 * @return {string} 788 * @access protected 789 */ 790 ; 791 792 _proto2.createRegExp = function createRegExp(str) { 793 if (this.opt.wildcards !== 'disabled') { 794 str = this.setupWildcardsRegExp(str); 795 } 796 797 str = this.escapeStr(str); 798 799 if (Object.keys(this.opt.synonyms).length) { 800 str = this.createSynonymsRegExp(str); 801 } 802 803 if (this.opt.ignoreJoiners || this.opt.ignorePunctuation.length) { 804 str = this.setupIgnoreJoinersRegExp(str); 805 } 806 807 if (this.opt.diacritics) { 808 str = this.createDiacriticsRegExp(str); 809 } 810 811 str = this.createMergedBlanksRegExp(str); 812 813 if (this.opt.ignoreJoiners || this.opt.ignorePunctuation.length) { 814 str = this.createJoinersRegExp(str); 815 } 816 817 if (this.opt.wildcards !== 'disabled') { 818 str = this.createWildcardsRegExp(str); 819 } 820 821 str = this.createAccuracyRegExp(str); 822 return str; 823 } 824 /** 825 * Creates a regular expression string to match the defined synonyms 826 * @param {string} str - The search term to be used 827 * @return {string} 828 * @access protected 829 */ 830 ; 831 832 _proto2.createSynonymsRegExp = function createSynonymsRegExp(str) { 833 var syn = this.opt.synonyms, 834 sens = this.opt.caseSensitive ? '' : 'i', 835 // add replacement character placeholder before and after the 836 // synonym group 837 joinerPlaceholder = this.opt.ignoreJoiners || this.opt.ignorePunctuation.length ? "\0" : ''; 838 839 for (var index in syn) { 840 if (syn.hasOwnProperty(index)) { 841 var value = syn[index], 842 k1 = this.opt.wildcards !== 'disabled' ? this.setupWildcardsRegExp(index) : this.escapeStr(index), 843 k2 = this.opt.wildcards !== 'disabled' ? this.setupWildcardsRegExp(value) : this.escapeStr(value); 844 845 if (k1 !== '' && k2 !== '') { 846 str = str.replace(new RegExp("(" + this.escapeStr(k1) + "|" + this.escapeStr(k2) + ")", "gm" + sens), joinerPlaceholder + ("(" + this.processSynomyms(k1) + "|") + (this.processSynomyms(k2) + ")") + joinerPlaceholder); 847 } 848 } 849 } 850 851 return str; 852 } 853 /** 854 * Setup synonyms to work with ignoreJoiners and or ignorePunctuation 855 * @param {string} str - synonym key or value to process 856 * @return {string} - processed synonym string 857 */ 858 ; 859 860 _proto2.processSynomyms = function processSynomyms(str) { 861 if (this.opt.ignoreJoiners || this.opt.ignorePunctuation.length) { 862 str = this.setupIgnoreJoinersRegExp(str); 863 } 864 865 return str; 866 } 867 /** 868 * Sets up the regular expression string to allow later insertion of 869 * wildcard regular expression matches 870 * @param {string} str - The search term to be used 871 * @return {string} 872 * @access protected 873 */ 874 ; 875 876 _proto2.setupWildcardsRegExp = function setupWildcardsRegExp(str) { 877 // replace single character wildcard with unicode 0001 878 str = str.replace(/(?:\\)*\?/g, function (val) { 879 return val.charAt(0) === '\\' ? '?' : "\x01"; 880 }); // replace multiple character wildcard with unicode 0002 881 882 return str.replace(/(?:\\)*\*/g, function (val) { 883 return val.charAt(0) === '\\' ? '*' : "\x02"; 884 }); 885 } 886 /** 887 * Sets up the regular expression string to allow later insertion of 888 * wildcard regular expression matches 889 * @param {string} str - The search term to be used 890 * @return {string} 891 * @access protected 892 */ 893 ; 894 895 _proto2.createWildcardsRegExp = function createWildcardsRegExp(str) { 896 // default to "enable" (i.e. to not include spaces) 897 // "withSpaces" uses `[\\S\\s]` instead of `.` because the latter 898 // does not match new line characters 899 var spaces = this.opt.wildcards === 'withSpaces'; 900 return str // replace unicode 0001 with a RegExp class to match any single 901 // character, or any single non-whitespace character depending 902 // on the setting 903 .replace(/\u0001/g, spaces ? '[\\S\\s]?' : '\\S?') // replace unicode 0002 with a RegExp class to match zero or 904 // more characters, or zero or more non-whitespace characters 905 // depending on the setting 906 .replace(/\u0002/g, spaces ? '[\\S\\s]*?' : '\\S*'); 907 } 908 /** 909 * Sets up the regular expression string to allow later insertion of 910 * designated characters (soft hyphens & zero width characters) 911 * @param {string} str - The search term to be used 912 * @return {string} 913 * @access protected 914 */ 915 ; 916 917 _proto2.setupIgnoreJoinersRegExp = function setupIgnoreJoinersRegExp(str) { 918 // adding a "null" unicode character as it will not be modified by the 919 // other "create" regular expression functions 920 return str.replace(/[^(|)\\]/g, function (val, indx, original) { 921 // don't add a null after an opening "(", around a "|" or before 922 // a closing "(", or between an escapement (e.g. \+) 923 var nextChar = original.charAt(indx + 1); 924 925 if (/[(|)\\]/.test(nextChar) || nextChar === '') { 926 return val; 927 } else { 928 return val + "\0"; 929 } 930 }); 931 } 932 /** 933 * Creates a regular expression string to allow ignoring of designated 934 * characters (soft hyphens, zero width characters & punctuation) based on 935 * the specified option values of <code>ignorePunctuation</code> and 936 * <code>ignoreJoiners</code> 937 * @param {string} str - The search term to be used 938 * @return {string} 939 * @access protected 940 */ 941 ; 942 943 _proto2.createJoinersRegExp = function createJoinersRegExp(str) { 944 var joiner = []; 945 var ignorePunctuation = this.opt.ignorePunctuation; 946 947 if (Array.isArray(ignorePunctuation) && ignorePunctuation.length) { 948 joiner.push(this.escapeStr(ignorePunctuation.join(''))); 949 } 950 951 if (this.opt.ignoreJoiners) { 952 // u+00ad = soft hyphen 953 // u+200b = zero-width space 954 // u+200c = zero-width non-joiner 955 // u+200d = zero-width joiner 956 joiner.push("\\u00ad\\u200b\\u200c\\u200d"); 957 } 958 959 return joiner.length ? str.split(/\u0000+/).join("[" + joiner.join('') + "]*") : str; 960 } 961 /** 962 * Creates a regular expression string to match diacritics 963 * @param {string} str - The search term to be used 964 * @return {string} 965 * @access protected 966 */ 967 ; 968 969 _proto2.createDiacriticsRegExp = function createDiacriticsRegExp(str) { 970 var sens = this.opt.caseSensitive ? '' : 'i', 971 dct = this.opt.caseSensitive ? ['aàáảãạăằắẳẵặâầấẩẫậäåāą', 'AÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬÄÅĀĄ', 'cçćč', 'CÇĆČ', 'dđď', 'DĐĎ', 'eèéẻẽẹêềếểễệëěēę', 'EÈÉẺẼẸÊỀẾỂỄỆËĚĒĘ', 'iìíỉĩịîïī', 'IÌÍỈĨỊÎÏĪ', 'lł', 'LŁ', 'nñňń', 'NÑŇŃ', 'oòóỏõọôồốổỗộơởỡớờợöøō', 'OÒÓỎÕỌÔỒỐỔỖỘƠỞỠỚỜỢÖØŌ', 'rř', 'RŘ', 'sšśșş', 'SŠŚȘŞ', 'tťțţ', 'TŤȚŢ', 'uùúủũụưừứửữựûüůū', 'UÙÚỦŨỤƯỪỨỬỮỰÛÜŮŪ', 'yýỳỷỹỵÿ', 'YÝỲỶỸỴŸ', 'zžżź', 'ZŽŻŹ'] : ['aàáảãạăằắẳẵặâầấẩẫậäåāąAÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬÄÅĀĄ', 'cçćčCÇĆČ', 'dđďDĐĎ', 'eèéẻẽẹêềếểễệëěēęEÈÉẺẼẸÊỀẾỂỄỆËĚĒĘ', 'iìíỉĩịîïīIÌÍỈĨỊÎÏĪ', 'lłLŁ', 'nñňńNÑŇŃ', 'oòóỏõọôồốổỗộơởỡớờợöøōOÒÓỎÕỌÔỒỐỔỖỘƠỞỠỚỜỢÖØŌ', 'rřRŘ', 'sšśșşSŠŚȘŞ', 'tťțţTŤȚŢ', 'uùúủũụưừứửữựûüůūUÙÚỦŨỤƯỪỨỬỮỰÛÜŮŪ', 'yýỳỷỹỵÿYÝỲỶỸỴŸ', 'zžżźZŽŻŹ']; 972 var handled = []; 973 str.split('').forEach(function (ch) { 974 dct.every(function (dct) { 975 // Check if the character is inside a diacritics list 976 if (dct.indexOf(ch) !== -1) { 977 // Check if the related diacritics list was not 978 // handled yet 979 if (handled.indexOf(dct) > -1) { 980 return false; 981 } // Make sure that the character OR any other 982 // character in the diacritics list will be matched 983 984 985 str = str.replace(new RegExp("[" + dct + "]", "gm" + sens), "[" + dct + "]"); 986 handled.push(dct); 987 } 988 989 return true; 990 }); 991 }); 992 return str; 993 } 994 /** 995 * Creates a regular expression string that merges whitespace characters 996 * including subsequent ones into a single pattern, one or multiple 997 * whitespaces 998 * @param {string} str - The search term to be used 999 * @return {string} 1000 * @access protected 1001 */ 1002 ; 1003 1004 _proto2.createMergedBlanksRegExp = function createMergedBlanksRegExp(str) { 1005 return str.replace(/[\s]+/gmi, '[\\s]+'); 1006 } 1007 /** 1008 * Creates a regular expression string to match the specified string with 1009 * the defined accuracy. As in the regular expression of "exactly" can be 1010 * a group containing a blank at the beginning, all regular expressions will 1011 * be created with two groups. The first group can be ignored (may contain 1012 * the said blank), the second contains the actual match 1013 * @param {string} str - The searm term to be used 1014 * @return {str} 1015 * @access protected 1016 */ 1017 ; 1018 1019 _proto2.createAccuracyRegExp = function createAccuracyRegExp(str) { 1020 var _this7 = this; 1021 1022 var chars = '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~¡¿'; 1023 var acc = this.opt.accuracy, 1024 val = typeof acc === 'string' ? acc : acc.value, 1025 ls = typeof acc === 'string' ? [] : acc.limiters, 1026 lsJoin = ''; 1027 ls.forEach(function (limiter) { 1028 lsJoin += "|" + _this7.escapeStr(limiter); 1029 }); 1030 1031 switch (val) { 1032 case 'partially': 1033 default: 1034 return "()(" + str + ")"; 1035 1036 case 'complementary': 1037 lsJoin = '\\s' + (lsJoin ? lsJoin : this.escapeStr(chars)); 1038 return "()([^" + lsJoin + "]*" + str + "[^" + lsJoin + "]*)"; 1039 1040 case 'exactly': 1041 return "(^|\\s" + lsJoin + ")(" + str + ")(?=$|\\s" + lsJoin + ")"; 1042 } 1043 } 1044 /** 1045 * @typedef Mark~separatedKeywords 1046 * @type {object.<string>} 1047 * @property {array.<string>} keywords - The list of keywords 1048 * @property {number} length - The length 1049 */ 1050 1051 /** 1052 * Returns a list of keywords dependent on whether separate word search 1053 * was defined. Also it filters empty keywords 1054 * @param {array} sv - The array of keywords 1055 * @return {Mark~separatedKeywords} 1056 * @access protected 1057 */ 1058 ; 1059 1060 _proto2.getSeparatedKeywords = function getSeparatedKeywords(sv) { 1061 var _this8 = this; 1062 1063 var stack = []; 1064 sv.forEach(function (kw) { 1065 if (!_this8.opt.separateWordSearch) { 1066 if (kw.trim() && stack.indexOf(kw) === -1) { 1067 stack.push(kw); 1068 } 1069 } else { 1070 kw.split(' ').forEach(function (kwSplitted) { 1071 if (kwSplitted.trim() && stack.indexOf(kwSplitted) === -1) { 1072 stack.push(kwSplitted); 1073 } 1074 }); 1075 } 1076 }); 1077 return { 1078 // sort because of https://git.io/v6USg 1079 'keywords': stack.sort(function (a, b) { 1080 return b.length - a.length; 1081 }), 1082 'length': stack.length 1083 }; 1084 } 1085 /** 1086 * Check if a value is a number 1087 * @param {number|string} value - the value to check; 1088 * numeric strings allowed 1089 * @return {boolean} 1090 * @access protected 1091 */ 1092 ; 1093 1094 _proto2.isNumeric = function isNumeric(value) { 1095 // http://stackoverflow.com/a/16655847/145346 1096 // eslint-disable-next-line eqeqeq 1097 return Number(parseFloat(value)) == value; 1098 } 1099 /** 1100 * @typedef Mark~rangeObject 1101 * @type {object} 1102 * @property {number} start - The start position within the composite value 1103 * @property {number} length - The length of the string to mark within the 1104 * composite value. 1105 */ 1106 1107 /** 1108 * @typedef Mark~setOfRanges 1109 * @type {object[]} 1110 * @property {Mark~rangeObject} 1111 */ 1112 1113 /** 1114 * Returns a processed list of integer offset indexes that do not overlap 1115 * each other, and remove any string values or additional elements 1116 * @param {Mark~setOfRanges} array - unprocessed raw array 1117 * @return {Mark~setOfRanges} - processed array with any invalid entries 1118 * removed 1119 * @throws Will throw an error if an array of objects is not passed 1120 * @access protected 1121 */ 1122 ; 1123 1124 _proto2.checkRanges = function checkRanges(array) { 1125 var _this9 = this; 1126 1127 // start and length indexes are included in an array of objects 1128 // [{start: 0, length: 1}, {start: 4, length: 5}] 1129 // quick validity check of the first entry only 1130 if (!Array.isArray(array) || Object.prototype.toString.call(array[0]) !== '[object Object]') { 1131 this.log('markRanges() will only accept an array of objects'); 1132 this.opt.noMatch(array); 1133 return []; 1134 } 1135 1136 var stack = []; 1137 var last = 0; 1138 array // acending sort to ensure there is no overlap in start & end 1139 // offsets 1140 .sort(function (a, b) { 1141 return a.start - b.start; 1142 }).forEach(function (item) { 1143 var _this9$callNoMatchOnI = _this9.callNoMatchOnInvalidRanges(item, last), 1144 start = _this9$callNoMatchOnI.start, 1145 end = _this9$callNoMatchOnI.end, 1146 valid = _this9$callNoMatchOnI.valid; 1147 1148 if (valid) { 1149 // preserve item in case there are extra key:values within 1150 item.start = start; 1151 item.length = end - start; 1152 stack.push(item); 1153 last = end; 1154 } 1155 }); 1156 return stack; 1157 } 1158 /** 1159 * @typedef Mark~validObject 1160 * @type {object} 1161 * @property {number} start - The start position within the composite value 1162 * @property {number} end - The calculated end position within the composite 1163 * value. 1164 * @property {boolean} valid - boolean value indicating that the start and 1165 * calculated end range is valid 1166 */ 1167 1168 /** 1169 * Initial validation of ranges for markRanges. Preliminary checks are done 1170 * to ensure the start and length values exist and are not zero or non- 1171 * numeric 1172 * @param {Mark~rangeObject} range - the current range object 1173 * @param {number} last - last index of range 1174 * @return {Mark~validObject} 1175 * @access protected 1176 */ 1177 ; 1178 1179 _proto2.callNoMatchOnInvalidRanges = function callNoMatchOnInvalidRanges(range, last) { 1180 var start, 1181 end, 1182 valid = false; 1183 1184 if (range && typeof range.start !== 'undefined') { 1185 start = parseInt(range.start, 10); 1186 end = start + parseInt(range.length, 10); // ignore overlapping values & non-numeric entries 1187 1188 if (this.isNumeric(range.start) && this.isNumeric(range.length) && end - last > 0 && end - start > 0) { 1189 valid = true; 1190 } else { 1191 this.log('Ignoring invalid or overlapping range: ' + ("" + JSON.stringify(range))); 1192 this.opt.noMatch(range); 1193 } 1194 } else { 1195 this.log("Ignoring invalid range: " + JSON.stringify(range)); 1196 this.opt.noMatch(range); 1197 } 1198 1199 return { 1200 start: start, 1201 end: end, 1202 valid: valid 1203 }; 1204 } 1205 /** 1206 * Check valid range for markRanges. Check ranges with access to the context 1207 * string. Range values are double checked, lengths that extend the mark 1208 * beyond the string length are limitied and ranges containing only 1209 * whitespace are ignored 1210 * @param {Mark~rangeObject} range - the current range object 1211 * @param {number} originalLength - original length of the context string 1212 * @param {string} string - current content string 1213 * @return {Mark~validObject} 1214 * @access protected 1215 */ 1216 ; 1217 1218 _proto2.checkWhitespaceRanges = function checkWhitespaceRanges(range, originalLength, string) { 1219 var end, 1220 valid = true, 1221 // the max value changes after the DOM is manipulated 1222 max = string.length, 1223 // adjust offset to account for wrapped text node 1224 offset = originalLength - max, 1225 start = parseInt(range.start, 10) - offset; // make sure to stop at max 1226 1227 start = start > max ? max : start; 1228 end = start + parseInt(range.length, 10); 1229 1230 if (end > max) { 1231 end = max; 1232 this.log("End range automatically set to the max value of " + max); 1233 } 1234 1235 if (start < 0 || end - start < 0 || start > max || end > max) { 1236 valid = false; 1237 this.log("Invalid range: " + JSON.stringify(range)); 1238 this.opt.noMatch(range); 1239 } else if (string.substring(start, end).replace(/\s+/g, '') === '') { 1240 valid = false; // whitespace only; even if wrapped it is not visible 1241 1242 this.log('Skipping whitespace only range: ' + JSON.stringify(range)); 1243 this.opt.noMatch(range); 1244 } 1245 1246 return { 1247 start: start, 1248 end: end, 1249 valid: valid 1250 }; 1251 } 1252 /** 1253 * @typedef Mark~getTextNodesDict 1254 * @type {object.<string>} 1255 * @property {string} value - The composite value of all text nodes 1256 * @property {object[]} nodes - An array of objects 1257 * @property {number} nodes.start - The start position within the composite 1258 * value 1259 * @property {number} nodes.end - The end position within the composite 1260 * value 1261 * @property {HTMLElement} nodes.node - The DOM text node element 1262 */ 1263 1264 /** 1265 * Callback 1266 * @callback Mark~getTextNodesCallback 1267 * @param {Mark~getTextNodesDict} 1268 */ 1269 1270 /** 1271 * Calls the callback with an object containing all text nodes (including 1272 * iframe text nodes) with start and end positions and the composite value 1273 * of them (string) 1274 * @param {Mark~getTextNodesCallback} cb - Callback 1275 * @access protected 1276 */ 1277 ; 1278 1279 _proto2.getTextNodes = function getTextNodes(cb) { 1280 var _this10 = this; 1281 1282 var val = '', 1283 nodes = []; 1284 this.iterator.forEachNode(NodeFilter.SHOW_TEXT, function (node) { 1285 nodes.push({ 1286 start: val.length, 1287 end: (val += node.textContent).length, 1288 node: node 1289 }); 1290 }, function (node) { 1291 if (_this10.matchesExclude(node.parentNode)) { 1292 return NodeFilter.FILTER_REJECT; 1293 } else { 1294 return NodeFilter.FILTER_ACCEPT; 1295 } 1296 }, function () { 1297 cb({ 1298 value: val, 1299 nodes: nodes 1300 }); 1301 }); 1302 } 1303 /** 1304 * Checks if an element matches any of the specified exclude selectors. Also 1305 * it checks for elements in which no marks should be performed (e.g. 1306 * script and style tags) and optionally already marked elements 1307 * @param {HTMLElement} el - The element to check 1308 * @return {boolean} 1309 * @access protected 1310 */ 1311 ; 1312 1313 _proto2.matchesExclude = function matchesExclude(el) { 1314 return DOMIterator.matches(el, this.opt.exclude.concat([// ignores the elements itself, not their childrens (selector *) 1315 'script', 'style', 'title', 'head', 'html'])); 1316 } 1317 /** 1318 * Wraps the instance element and class around matches that fit the start 1319 * and end positions within the node 1320 * @param {HTMLElement} node - The DOM text node 1321 * @param {number} start - The position where to start wrapping 1322 * @param {number} end - The position where to end wrapping 1323 * @return {HTMLElement} Returns the splitted text node that will appear 1324 * after the wrapped text node 1325 * @access protected 1326 */ 1327 ; 1328 1329 _proto2.wrapRangeInTextNode = function wrapRangeInTextNode(node, start, end) { 1330 var hEl = !this.opt.element ? 'mark' : this.opt.element, 1331 startNode = node.splitText(start), 1332 ret = startNode.splitText(end - start); 1333 var repl = document.createElement(hEl); 1334 repl.setAttribute('data-markjs', 'true'); 1335 1336 if (this.opt.className) { 1337 repl.setAttribute('class', this.opt.className); 1338 } 1339 1340 repl.textContent = startNode.textContent; 1341 startNode.parentNode.replaceChild(repl, startNode); 1342 return ret; 1343 } 1344 /** 1345 * @typedef Mark~wrapRangeInMappedTextNodeDict 1346 * @type {object.<string>} 1347 * @property {string} value - The composite value of all text nodes 1348 * @property {object[]} nodes - An array of objects 1349 * @property {number} nodes.start - The start position within the composite 1350 * value 1351 * @property {number} nodes.end - The end position within the composite 1352 * value 1353 * @property {HTMLElement} nodes.node - The DOM text node element 1354 */ 1355 1356 /** 1357 * Each callback 1358 * @callback Mark~wrapMatchesEachCallback 1359 * @param {HTMLElement} node - The wrapped DOM element 1360 * @param {number} lastIndex - The last matching position within the 1361 * composite value of text nodes 1362 */ 1363 1364 /** 1365 * Filter callback 1366 * @callback Mark~wrapMatchesFilterCallback 1367 * @param {HTMLElement} node - The matching text node DOM element 1368 */ 1369 1370 /** 1371 * Determines matches by start and end positions using the text node 1372 * dictionary even across text nodes and calls 1373 * {@link Mark#wrapRangeInTextNode} to wrap them 1374 * @param {Mark~wrapRangeInMappedTextNodeDict} dict - The dictionary 1375 * @param {number} start - The start position of the match 1376 * @param {number} end - The end position of the match 1377 * @param {Mark~wrapMatchesFilterCallback} filterCb - Filter callback 1378 * @param {Mark~wrapMatchesEachCallback} eachCb - Each callback 1379 * @access protected 1380 */ 1381 ; 1382 1383 _proto2.wrapRangeInMappedTextNode = function wrapRangeInMappedTextNode(dict, start, end, filterCb, eachCb) { 1384 var _this11 = this; 1385 1386 // iterate over all text nodes to find the one matching the positions 1387 dict.nodes.every(function (n, i) { 1388 var sibl = dict.nodes[i + 1]; 1389 1390 if (typeof sibl === 'undefined' || sibl.start > start) { 1391 if (!filterCb(n.node)) { 1392 return false; 1393 } // map range from dict.value to text node 1394 1395 1396 var s = start - n.start, 1397 e = (end > n.end ? n.end : end) - n.start, 1398 startStr = dict.value.substr(0, n.start), 1399 endStr = dict.value.substr(e + n.start); 1400 n.node = _this11.wrapRangeInTextNode(n.node, s, e); // recalculate positions to also find subsequent matches in the 1401 // same text node. Necessary as the text node in dict now only 1402 // contains the splitted part after the wrapped one 1403 1404 dict.value = startStr + endStr; 1405 dict.nodes.forEach(function (k, j) { 1406 if (j >= i) { 1407 if (dict.nodes[j].start > 0 && j !== i) { 1408 dict.nodes[j].start -= e; 1409 } 1410 1411 dict.nodes[j].end -= e; 1412 } 1413 }); 1414 end -= e; 1415 eachCb(n.node.previousSibling, n.start); 1416 1417 if (end > n.end) { 1418 start = n.end; 1419 } else { 1420 return false; 1421 } 1422 } 1423 1424 return true; 1425 }); 1426 } 1427 /** 1428 * Filter callback before each wrapping 1429 * @callback Mark~wrapMatchesFilterCallback 1430 * @param {string} match - The matching string 1431 * @param {HTMLElement} node - The text node where the match occurs 1432 */ 1433 1434 /** 1435 * Callback for each wrapped element 1436 * @callback Mark~wrapMatchesEachCallback 1437 * @param {HTMLElement} element - The marked DOM element 1438 */ 1439 1440 /** 1441 * Callback on end 1442 * @callback Mark~wrapMatchesEndCallback 1443 */ 1444 1445 /** 1446 * Wraps the instance element and class around matches within single HTML 1447 * elements in all contexts 1448 * @param {RegExp} regex - The regular expression to be searched for 1449 * @param {number} ignoreGroups - A number indicating the amount of RegExp 1450 * matching groups to ignore 1451 * @param {Mark~wrapMatchesFilterCallback} filterCb 1452 * @param {Mark~wrapMatchesEachCallback} eachCb 1453 * @param {Mark~wrapMatchesEndCallback} endCb 1454 * @access protected 1455 */ 1456 ; 1457 1458 _proto2.wrapMatches = function wrapMatches(regex, ignoreGroups, filterCb, eachCb, endCb) { 1459 var _this12 = this; 1460 1461 var matchIdx = ignoreGroups === 0 ? 0 : ignoreGroups + 1; 1462 this.getTextNodes(function (dict) { 1463 dict.nodes.forEach(function (node) { 1464 node = node.node; 1465 var match; 1466 1467 while ((match = regex.exec(node.textContent)) !== null && match[matchIdx] !== '') { 1468 if (!filterCb(match[matchIdx], node)) { 1469 continue; 1470 } 1471 1472 var pos = match.index; 1473 1474 if (matchIdx !== 0) { 1475 for (var i = 1; i < matchIdx; i++) { 1476 pos += match[i].length; 1477 } 1478 } 1479 1480 node = _this12.wrapRangeInTextNode(node, pos, pos + match[matchIdx].length); 1481 eachCb(node.previousSibling); // reset index of last match as the node changed and the 1482 // index isn't valid anymore http://tinyurl.com/htsudjd 1483 1484 regex.lastIndex = 0; 1485 } 1486 }); 1487 endCb(); 1488 }); 1489 } 1490 /** 1491 * Callback for each wrapped element 1492 * @callback Mark~wrapMatchesAcrossElementsEachCallback 1493 * @param {HTMLElement} element - The marked DOM element 1494 */ 1495 1496 /** 1497 * Filter callback before each wrapping 1498 * @callback Mark~wrapMatchesAcrossElementsFilterCallback 1499 * @param {string} match - The matching string 1500 * @param {HTMLElement} node - The text node where the match occurs 1501 */ 1502 1503 /** 1504 * Callback on end 1505 * @callback Mark~wrapMatchesAcrossElementsEndCallback 1506 */ 1507 1508 /** 1509 * Wraps the instance element and class around matches across all HTML 1510 * elements in all contexts 1511 * @param {RegExp} regex - The regular expression to be searched for 1512 * @param {number} ignoreGroups - A number indicating the amount of RegExp 1513 * matching groups to ignore 1514 * @param {Mark~wrapMatchesAcrossElementsFilterCallback} filterCb 1515 * @param {Mark~wrapMatchesAcrossElementsEachCallback} eachCb 1516 * @param {Mark~wrapMatchesAcrossElementsEndCallback} endCb 1517 * @access protected 1518 */ 1519 ; 1520 1521 _proto2.wrapMatchesAcrossElements = function wrapMatchesAcrossElements(regex, ignoreGroups, filterCb, eachCb, endCb) { 1522 var _this13 = this; 1523 1524 var matchIdx = ignoreGroups === 0 ? 0 : ignoreGroups + 1; 1525 this.getTextNodes(function (dict) { 1526 var match; 1527 1528 while ((match = regex.exec(dict.value)) !== null && match[matchIdx] !== '') { 1529 // calculate range inside dict.value 1530 var start = match.index; 1531 1532 if (matchIdx !== 0) { 1533 for (var i = 1; i < matchIdx; i++) { 1534 start += match[i].length; 1535 } 1536 } 1537 1538 var end = start + match[matchIdx].length; // note that dict will be updated automatically, as it'll change 1539 // in the wrapping process, due to the fact that text 1540 // nodes will be splitted 1541 1542 _this13.wrapRangeInMappedTextNode(dict, start, end, function (node) { 1543 return filterCb(match[matchIdx], node); 1544 }, function (node, lastIndex) { 1545 regex.lastIndex = lastIndex; 1546 eachCb(node); 1547 }); 1548 } 1549 1550 endCb(); 1551 }); 1552 } 1553 /** 1554 * Callback for each wrapped element 1555 * @callback Mark~wrapRangeFromIndexEachCallback 1556 * @param {HTMLElement} element - The marked DOM element 1557 * @param {Mark~rangeObject} range - the current range object; provided 1558 * start and length values will be numeric integers modified from the 1559 * provided original ranges. 1560 */ 1561 1562 /** 1563 * Filter callback before each wrapping 1564 * @callback Mark~wrapRangeFromIndexFilterCallback 1565 * @param {HTMLElement} node - The text node which includes the range 1566 * @param {Mark~rangeObject} range - the current range object 1567 * @param {string} match - string extracted from the matching range 1568 * @param {number} counter - A counter indicating the number of all marks 1569 */ 1570 1571 /** 1572 * Callback on end 1573 * @callback Mark~wrapRangeFromIndexEndCallback 1574 */ 1575 1576 /** 1577 * Wraps the indicated ranges across all HTML elements in all contexts 1578 * @param {Mark~setOfRanges} ranges 1579 * @param {Mark~wrapRangeFromIndexFilterCallback} filterCb 1580 * @param {Mark~wrapRangeFromIndexEachCallback} eachCb 1581 * @param {Mark~wrapRangeFromIndexEndCallback} endCb 1582 * @access protected 1583 */ 1584 ; 1585 1586 _proto2.wrapRangeFromIndex = function wrapRangeFromIndex(ranges, filterCb, eachCb, endCb) { 1587 var _this14 = this; 1588 1589 this.getTextNodes(function (dict) { 1590 var originalLength = dict.value.length; 1591 ranges.forEach(function (range, counter) { 1592 var _this14$checkWhitespa = _this14.checkWhitespaceRanges(range, originalLength, dict.value), 1593 start = _this14$checkWhitespa.start, 1594 end = _this14$checkWhitespa.end, 1595 valid = _this14$checkWhitespa.valid; 1596 1597 if (valid) { 1598 _this14.wrapRangeInMappedTextNode(dict, start, end, function (node) { 1599 return filterCb(node, range, dict.value.substring(start, end), counter); 1600 }, function (node) { 1601 eachCb(node, range); 1602 }); 1603 } 1604 }); 1605 endCb(); 1606 }); 1607 } 1608 /** 1609 * Unwraps the specified DOM node with its content (text nodes or HTML) 1610 * without destroying possibly present events (using innerHTML) and 1611 * normalizes the parent at the end (merge splitted text nodes) 1612 * @param {HTMLElement} node - The DOM node to unwrap 1613 * @access protected 1614 */ 1615 ; 1616 1617 _proto2.unwrapMatches = function unwrapMatches(node) { 1618 var parent = node.parentNode; 1619 var docFrag = document.createDocumentFragment(); 1620 1621 while (node.firstChild) { 1622 docFrag.appendChild(node.removeChild(node.firstChild)); 1623 } 1624 1625 parent.replaceChild(docFrag, node); 1626 1627 if (!this.ie) { 1628 // use browser's normalize method 1629 parent.normalize(); 1630 } else { 1631 // custom method (needs more time) 1632 this.normalizeTextNode(parent); 1633 } 1634 } 1635 /** 1636 * Normalizes text nodes. It's a workaround for the native normalize method 1637 * that has a bug in IE (see attached link). Should only be used in IE 1638 * browsers as it's slower than the native method. 1639 * @see {@link http://tinyurl.com/z5asa8c} 1640 * @param {HTMLElement} node - The DOM node to normalize 1641 * @access protected 1642 */ 1643 ; 1644 1645 _proto2.normalizeTextNode = function normalizeTextNode(node) { 1646 if (!node) { 1647 return; 1648 } 1649 1650 if (node.nodeType === 3) { 1651 while (node.nextSibling && node.nextSibling.nodeType === 3) { 1652 node.nodeValue += node.nextSibling.nodeValue; 1653 node.parentNode.removeChild(node.nextSibling); 1654 } 1655 } else { 1656 this.normalizeTextNode(node.firstChild); 1657 } 1658 1659 this.normalizeTextNode(node.nextSibling); 1660 } 1661 /** 1662 * Callback when finished 1663 * @callback Mark~commonDoneCallback 1664 * @param {number} totalMatches - The number of marked elements 1665 */ 1666 1667 /** 1668 * @typedef Mark~commonOptions 1669 * @type {object.<string>} 1670 * @property {string} [element="mark"] - HTML element tag name 1671 * @property {string} [className] - An optional class name 1672 * @property {string[]} [exclude] - An array with exclusion selectors. 1673 * Elements matching those selectors will be ignored 1674 * @property {boolean} [iframes=false] - Whether to search inside iframes 1675 * @property {Mark~commonDoneCallback} [done] 1676 * @property {boolean} [debug=false] - Wheter to log messages 1677 * @property {object} [log=window.console] - Where to log messages (only if 1678 * debug is true) 1679 */ 1680 1681 /** 1682 * Callback for each marked element 1683 * @callback Mark~markRegExpEachCallback 1684 * @param {HTMLElement} element - The marked DOM element 1685 */ 1686 1687 /** 1688 * Callback if there were no matches 1689 * @callback Mark~markRegExpNoMatchCallback 1690 * @param {RegExp} regexp - The regular expression 1691 */ 1692 1693 /** 1694 * Callback to filter matches 1695 * @callback Mark~markRegExpFilterCallback 1696 * @param {HTMLElement} textNode - The text node which includes the match 1697 * @param {string} match - The matching string for the RegExp 1698 * @param {number} counter - A counter indicating the number of all marks 1699 */ 1700 1701 /** 1702 * These options also include the common options from 1703 * {@link Mark~commonOptions} 1704 * @typedef Mark~markRegExpOptions 1705 * @type {object.<string>} 1706 * @property {Mark~markRegExpEachCallback} [each] 1707 * @property {Mark~markRegExpNoMatchCallback} [noMatch] 1708 * @property {Mark~markRegExpFilterCallback} [filter] 1709 */ 1710 1711 /** 1712 * Marks a custom regular expression 1713 * @param {RegExp} regexp - The regular expression 1714 * @param {Mark~markRegExpOptions} [opt] - Optional options object 1715 * @access public 1716 */ 1717 ; 1718 1719 _proto2.markRegExp = function markRegExp(regexp, opt) { 1720 var _this15 = this; 1721 1722 this.opt = opt; 1723 this.log("Searching with expression \"" + regexp + "\""); 1724 var totalMatches = 0, 1725 fn = 'wrapMatches'; 1726 1727 var eachCb = function eachCb(element) { 1728 totalMatches++; 1729 1730 _this15.opt.each(element); 1731 }; 1732 1733 if (this.opt.acrossElements) { 1734 fn = 'wrapMatchesAcrossElements'; 1735 } 1736 1737 this[fn](regexp, this.opt.ignoreGroups, function (match, node) { 1738 return _this15.opt.filter(node, match, totalMatches); 1739 }, eachCb, function () { 1740 if (totalMatches === 0) { 1741 _this15.opt.noMatch(regexp); 1742 } 1743 1744 _this15.opt.done(totalMatches); 1745 }); 1746 } 1747 /** 1748 * Callback for each marked element 1749 * @callback Mark~markEachCallback 1750 * @param {HTMLElement} element - The marked DOM element 1751 */ 1752 1753 /** 1754 * Callback if there were no matches 1755 * @callback Mark~markNoMatchCallback 1756 * @param {RegExp} term - The search term that was not found 1757 */ 1758 1759 /** 1760 * Callback to filter matches 1761 * @callback Mark~markFilterCallback 1762 * @param {HTMLElement} textNode - The text node which includes the match 1763 * @param {string} match - The matching term 1764 * @param {number} totalCounter - A counter indicating the number of all 1765 * marks 1766 * @param {number} termCounter - A counter indicating the number of marks 1767 * for the specific match 1768 */ 1769 1770 /** 1771 * @typedef Mark~markAccuracyObject 1772 * @type {object.<string>} 1773 * @property {string} value - A accuracy string value 1774 * @property {string[]} limiters - A custom array of limiters. For example 1775 * <code>["-", ","]</code> 1776 */ 1777 1778 /** 1779 * @typedef Mark~markAccuracySetting 1780 * @type {string} 1781 * @property {"partially"|"complementary"|"exactly"|Mark~markAccuracyObject} 1782 * [accuracy="partially"] - Either one of the following string values: 1783 * <ul> 1784 * <li><i>partially</i>: When searching for "lor" only "lor" inside 1785 * "lorem" will be marked</li> 1786 * <li><i>complementary</i>: When searching for "lor" the whole word 1787 * "lorem" will be marked</li> 1788 * <li><i>exactly</i>: When searching for "lor" only those exact words 1789 * will be marked. In this example nothing inside "lorem". This value 1790 * is equivalent to the previous option <i>wordBoundary</i></li> 1791 * </ul> 1792 * Or an object containing two properties: 1793 * <ul> 1794 * <li><i>value</i>: One of the above named string values</li> 1795 * <li><i>limiters</i>: A custom array of string limiters for accuracy 1796 * "exactly" or "complementary"</li> 1797 * </ul> 1798 */ 1799 1800 /** 1801 * @typedef Mark~markWildcardsSetting 1802 * @type {string} 1803 * @property {"disabled"|"enabled"|"withSpaces"} 1804 * [wildcards="disabled"] - Set to any of the following string values: 1805 * <ul> 1806 * <li><i>disabled</i>: Disable wildcard usage</li> 1807 * <li><i>enabled</i>: When searching for "lor?m", the "?" will match zero 1808 * or one non-space character (e.g. "lorm", "loram", "lor3m", etc). When 1809 * searching for "lor*m", the "*" will match zero or more non-space 1810 * characters (e.g. "lorm", "loram", "lor123m", etc).</li> 1811 * <li><i>withSpaces</i>: When searching for "lor?m", the "?" will 1812 * match zero or one space or non-space character (e.g. "lor m", "loram", 1813 * etc). When searching for "lor*m", the "*" will match zero or more space 1814 * or non-space characters (e.g. "lorm", "lore et dolor ipsum", "lor: m", 1815 * etc).</li> 1816 * </ul> 1817 */ 1818 1819 /** 1820 * @typedef Mark~markIgnorePunctuationSetting 1821 * @type {string[]} 1822 * @property {string} The strings in this setting will contain punctuation 1823 * marks that will be ignored: 1824 * <ul> 1825 * <li>These punctuation marks can be between any characters, e.g. setting 1826 * this option to <code>["'"]</code> would match "Worlds", "World's" and 1827 * "Wo'rlds"</li> 1828 * <li>One or more apostrophes between the letters would still produce a 1829 * match (e.g. "W'o''r'l'd's").</li> 1830 * <li>A typical setting for this option could be as follows: 1831 * <pre>ignorePunctuation: ":;.,-–—‒_(){}[]!'\"+=".split(""),</pre> This 1832 * setting includes common punctuation as well as a minus, en-dash, 1833 * em-dash and figure-dash 1834 * ({@link https://en.wikipedia.org/wiki/Dash#Figure_dash ref}), as well 1835 * as an underscore.</li> 1836 * </ul> 1837 */ 1838 1839 /** 1840 * These options also include the common options from 1841 * {@link Mark~commonOptions} 1842 * @typedef Mark~markOptions 1843 * @type {object.<string>} 1844 * @property {boolean} [separateWordSearch=true] - Whether to search for 1845 * each word separated by a blank instead of the complete term 1846 * @property {boolean} [diacritics=true] - If diacritic characters should be 1847 * matched. ({@link https://en.wikipedia.org/wiki/Diacritic Diacritics}) 1848 * @property {object} [synonyms] - An object with synonyms. The key will be 1849 * a synonym for the value and the value for the key 1850 * @property {Mark~markAccuracySetting} [accuracy] 1851 * @property {Mark~markWildcardsSetting} [wildcards] 1852 * @property {boolean} [acrossElements=false] - Whether to find matches 1853 * across HTML elements. By default, only matches within single HTML 1854 * elements will be found 1855 * @property {boolean} [ignoreJoiners=false] - Whether to ignore word 1856 * joiners inside of key words. These include soft-hyphens, zero-width 1857 * space, zero-width non-joiners and zero-width joiners. 1858 * @property {Mark~markIgnorePunctuationSetting} [ignorePunctuation] 1859 * @property {Mark~markEachCallback} [each] 1860 * @property {Mark~markNoMatchCallback} [noMatch] 1861 * @property {Mark~markFilterCallback} [filter] 1862 */ 1863 1864 /** 1865 * Marks the specified search terms 1866 * @param {string|string[]} [sv] - Search value, either a search string or 1867 * an array containing multiple search strings 1868 * @param {Mark~markOptions} [opt] - Optional options object 1869 * @access public 1870 */ 1871 ; 1872 1873 _proto2.mark = function mark(sv, opt) { 1874 var _this16 = this; 1875 1876 this.opt = opt; 1877 var totalMatches = 0, 1878 fn = 'wrapMatches'; 1879 1880 var _this$getSeparatedKey = this.getSeparatedKeywords(typeof sv === 'string' ? [sv] : sv), 1881 kwArr = _this$getSeparatedKey.keywords, 1882 kwArrLen = _this$getSeparatedKey.length, 1883 sens = this.opt.caseSensitive ? '' : 'i', 1884 handler = function handler(kw) { 1885 // async function calls as iframes are async too 1886 var regex = new RegExp(_this16.createRegExp(kw), "gm" + sens), 1887 matches = 0; 1888 1889 _this16.log("Searching with expression \"" + regex + "\""); 1890 1891 _this16[fn](regex, 1, function (term, node) { 1892 return _this16.opt.filter(node, kw, totalMatches, matches); 1893 }, function (element) { 1894 matches++; 1895 totalMatches++; 1896 1897 _this16.opt.each(element); 1898 }, function () { 1899 if (matches === 0) { 1900 _this16.opt.noMatch(kw); 1901 } 1902 1903 if (kwArr[kwArrLen - 1] === kw) { 1904 _this16.opt.done(totalMatches); 1905 } else { 1906 handler(kwArr[kwArr.indexOf(kw) + 1]); 1907 } 1908 }); 1909 }; 1910 1911 if (this.opt.acrossElements) { 1912 fn = 'wrapMatchesAcrossElements'; 1913 } 1914 1915 if (kwArrLen === 0) { 1916 this.opt.done(totalMatches); 1917 } else { 1918 handler(kwArr[0]); 1919 } 1920 } 1921 /** 1922 * Callback for each marked element 1923 * @callback Mark~markRangesEachCallback 1924 * @param {HTMLElement} element - The marked DOM element 1925 * @param {array} range - array of range start and end points 1926 */ 1927 1928 /** 1929 * Callback if a processed range is invalid, out-of-bounds, overlaps another 1930 * range, or only matches whitespace 1931 * @callback Mark~markRangesNoMatchCallback 1932 * @param {Mark~rangeObject} range - a range object 1933 */ 1934 1935 /** 1936 * Callback to filter matches 1937 * @callback Mark~markRangesFilterCallback 1938 * @param {HTMLElement} node - The text node which includes the range 1939 * @param {array} range - array of range start and end points 1940 * @param {string} match - string extracted from the matching range 1941 * @param {number} counter - A counter indicating the number of all marks 1942 */ 1943 1944 /** 1945 * These options also include the common options from 1946 * {@link Mark~commonOptions} 1947 * @typedef Mark~markRangesOptions 1948 * @type {object.<string>} 1949 * @property {Mark~markRangesEachCallback} [each] 1950 * @property {Mark~markRangesNoMatchCallback} [noMatch] 1951 * @property {Mark~markRangesFilterCallback} [filter] 1952 */ 1953 1954 /** 1955 * Marks an array of objects containing a start with an end or length of the 1956 * string to mark 1957 * @param {Mark~setOfRanges} rawRanges - The original (preprocessed) 1958 * array of objects 1959 * @param {Mark~markRangesOptions} [opt] - Optional options object 1960 * @access public 1961 */ 1962 ; 1963 1964 _proto2.markRanges = function markRanges(rawRanges, opt) { 1965 var _this17 = this; 1966 1967 this.opt = opt; 1968 var totalMatches = 0, 1969 ranges = this.checkRanges(rawRanges); 1970 1971 if (ranges && ranges.length) { 1972 this.log('Starting to mark with the following ranges: ' + JSON.stringify(ranges)); 1973 this.wrapRangeFromIndex(ranges, function (node, range, match, counter) { 1974 return _this17.opt.filter(node, range, match, counter); 1975 }, function (element, range) { 1976 totalMatches++; 1977 1978 _this17.opt.each(element, range); 1979 }, function () { 1980 _this17.opt.done(totalMatches); 1981 }); 1982 } else { 1983 this.opt.done(totalMatches); 1984 } 1985 } 1986 /** 1987 * Removes all marked elements inside the context with their HTML and 1988 * normalizes the parent at the end 1989 * @param {Mark~commonOptions} [opt] - Optional options object 1990 * @access public 1991 */ 1992 ; 1993 1994 _proto2.unmark = function unmark(opt) { 1995 var _this18 = this; 1996 1997 this.opt = opt; 1998 var sel = this.opt.element ? this.opt.element : '*'; 1999 sel += '[data-markjs]'; 2000 2001 if (this.opt.className) { 2002 sel += "." + this.opt.className; 2003 } 2004 2005 this.log("Removal selector \"" + sel + "\""); 2006 this.iterator.forEachNode(NodeFilter.SHOW_ELEMENT, function (node) { 2007 _this18.unwrapMatches(node); 2008 }, function (node) { 2009 var matchesSel = DOMIterator.matches(node, sel), 2010 matchesExclude = _this18.matchesExclude(node); 2011 2012 if (!matchesSel || matchesExclude) { 2013 return NodeFilter.FILTER_REJECT; 2014 } else { 2015 return NodeFilter.FILTER_ACCEPT; 2016 } 2017 }, this.opt.done); 2018 }; 2019 2020 _createClass(Mark$1, [{ 2021 key: "opt", 2022 get: function get() { 2023 return this._opt; 2024 } 2025 /** 2026 * An instance of DOMIterator 2027 * @type {DOMIterator} 2028 * @access protected 2029 */ 2030 , 2031 set: function set(val) { 2032 this._opt = Object.assign({}, { 2033 'element': '', 2034 'className': '', 2035 'exclude': [], 2036 'iframes': false, 2037 'iframesTimeout': 5000, 2038 'separateWordSearch': true, 2039 'diacritics': true, 2040 'synonyms': {}, 2041 'accuracy': 'partially', 2042 'acrossElements': false, 2043 'caseSensitive': false, 2044 'ignoreJoiners': false, 2045 'ignoreGroups': 0, 2046 'ignorePunctuation': [], 2047 'wildcards': 'disabled', 2048 'each': function each() {}, 2049 'noMatch': function noMatch() {}, 2050 'filter': function filter() { 2051 return true; 2052 }, 2053 'done': function done() {}, 2054 'debug': false, 2055 'log': window.console 2056 }, val); 2057 } 2058 }, { 2059 key: "iterator", 2060 get: function get() { 2061 // always return new instance in case there were option changes 2062 return new DOMIterator(this.ctx, this.opt.iframes, this.opt.exclude, this.opt.iframesTimeout); 2063 } 2064 }]); 2065 2066 return Mark$1; 2067 }(); 2068 2069 function Mark(ctx) { 2070 var _this19 = this; 2071 2072 var instance = new Mark$1(ctx); 2073 2074 this.mark = function (sv, opt) { 2075 instance.mark(sv, opt); 2076 return _this19; 2077 }; 2078 2079 this.markRegExp = function (sv, opt) { 2080 instance.markRegExp(sv, opt); 2081 return _this19; 2082 }; 2083 2084 this.markRanges = function (sv, opt) { 2085 instance.markRanges(sv, opt); 2086 return _this19; 2087 }; 2088 2089 this.unmark = function (opt) { 2090 instance.unmark(opt); 2091 return _this19; 2092 }; 2093 2094 return this; 2095 } 2096 2097 var defaultOptions = { 2098 exclude: [], 2099 separateWordSearch: true, 2100 accuracy: 'partially', 2101 diacritics: true, 2102 synonyms: {}, 2103 iframes: false, 2104 iframesTimeout: 5000, 2105 acrossElements: true, 2106 caseSensitive: false, 2107 ignoreJoiners: false, 2108 wildcards: 'disabled', 2109 compatibility: false 2110 }; 2111 2112 if (Joomla.getOptions && typeof Joomla.getOptions === 'function' && Joomla.getOptions('highlight')) { 2113 var scriptOptions = Joomla.getOptions('highlight'); 2114 scriptOptions.forEach(function (currentOpts) { 2115 var options = Object.assign({}, defaultOptions, currentOpts); // Continue only if the element exists 2116 2117 if (!options.compatibility) { 2118 var element = document.querySelector("." + options.class); 2119 2120 if (element) { 2121 var instance = new Mark(element); // Loop through the terms 2122 2123 options.highLight.forEach(function (term) { 2124 instance.mark(term, options); 2125 }); 2126 } 2127 } else { 2128 var start = document.querySelector("#" + options.start); 2129 document.querySelector("#" + options.end); 2130 var parent = start.parentNode; 2131 var targetNodes = []; 2132 var allElems = Array.from(parent.childNodes); 2133 allElems.forEach(function (element) { 2134 { 2135 return; 2136 } 2137 }); 2138 targetNodes.forEach(function (node) { 2139 var instance = new Mark(node); // Loop through the terms 2140 2141 options.highLight.map(function (term) { 2142 return instance.mark(term, options); 2143 }); 2144 }); 2145 } 2146 }); 2147 } 2148 2149 })();
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Sep 7 05:41:13 2022 | Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer |