mirror of
				https://github.com/imezx/Warp.git
				synced 2025-10-31 18:29:18 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			925 lines
		
	
	
	
		
			26 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			925 lines
		
	
	
	
		
			26 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /*!***************************************************
 | ||
| * mark.js v8.11.1
 | ||
| * https://markjs.io/
 | ||
| * Copyright (c) 2014–2018, Julian Kühnel
 | ||
| * Released under the MIT license https://git.io/vwTVl
 | ||
| *****************************************************/
 | ||
| 
 | ||
| (function (global, factory) {
 | ||
| 	typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('jquery')) :
 | ||
| 	typeof define === 'function' && define.amd ? define(['jquery'], factory) :
 | ||
| 	(global.Mark = factory(global.jQuery));
 | ||
| }(this, (function ($) { 'use strict';
 | ||
| 
 | ||
| $ = $ && $.hasOwnProperty('default') ? $['default'] : $;
 | ||
| 
 | ||
| class DOMIterator {
 | ||
|   constructor(ctx, iframes = true, exclude = [], iframesTimeout = 5000) {
 | ||
|     this.ctx = ctx;
 | ||
|     this.iframes = iframes;
 | ||
|     this.exclude = exclude;
 | ||
|     this.iframesTimeout = iframesTimeout;
 | ||
|   }
 | ||
|   static matches(element, selector) {
 | ||
|     const selectors = typeof selector === 'string' ? [selector] : selector,
 | ||
|       fn = (
 | ||
|         element.matches ||
 | ||
|         element.matchesSelector ||
 | ||
|         element.msMatchesSelector ||
 | ||
|         element.mozMatchesSelector ||
 | ||
|         element.oMatchesSelector ||
 | ||
|         element.webkitMatchesSelector
 | ||
|       );
 | ||
|     if (fn) {
 | ||
|       let match = false;
 | ||
|       selectors.every(sel => {
 | ||
|         if (fn.call(element, sel)) {
 | ||
|           match = true;
 | ||
|           return false;
 | ||
|         }
 | ||
|         return true;
 | ||
|       });
 | ||
|       return match;
 | ||
|     } else {
 | ||
|       return false;
 | ||
|     }
 | ||
|   }
 | ||
|   getContexts() {
 | ||
|     let ctx,
 | ||
|       filteredCtx = [];
 | ||
|     if (typeof this.ctx === 'undefined' || !this.ctx) {
 | ||
|       ctx = [];
 | ||
|     } else if (NodeList.prototype.isPrototypeOf(this.ctx)) {
 | ||
|       ctx = Array.prototype.slice.call(this.ctx);
 | ||
|     } else if (Array.isArray(this.ctx)) {
 | ||
|       ctx = this.ctx;
 | ||
|     } else if (typeof this.ctx === 'string') {
 | ||
|       ctx = Array.prototype.slice.call(
 | ||
|         document.querySelectorAll(this.ctx)
 | ||
|       );
 | ||
|     } else {
 | ||
|       ctx = [this.ctx];
 | ||
|     }
 | ||
|     ctx.forEach(ctx => {
 | ||
|       const isDescendant = filteredCtx.filter(contexts => {
 | ||
|         return contexts.contains(ctx);
 | ||
|       }).length > 0;
 | ||
|       if (filteredCtx.indexOf(ctx) === -1 && !isDescendant) {
 | ||
|         filteredCtx.push(ctx);
 | ||
|       }
 | ||
|     });
 | ||
|     return filteredCtx;
 | ||
|   }
 | ||
|   getIframeContents(ifr, successFn, errorFn = () => {}) {
 | ||
|     let doc;
 | ||
|     try {
 | ||
|       const ifrWin = ifr.contentWindow;
 | ||
|       doc = ifrWin.document;
 | ||
|       if (!ifrWin || !doc) {
 | ||
|         throw new Error('iframe inaccessible');
 | ||
|       }
 | ||
|     } catch (e) {
 | ||
|       errorFn();
 | ||
|     }
 | ||
|     if (doc) {
 | ||
|       successFn(doc);
 | ||
|     }
 | ||
|   }
 | ||
|   isIframeBlank(ifr) {
 | ||
|     const bl = 'about:blank',
 | ||
|       src = ifr.getAttribute('src').trim(),
 | ||
|       href = ifr.contentWindow.location.href;
 | ||
|     return href === bl && src !== bl && src;
 | ||
|   }
 | ||
|   observeIframeLoad(ifr, successFn, errorFn) {
 | ||
|     let called = false,
 | ||
|       tout = null;
 | ||
|     const listener = () => {
 | ||
|       if (called) {
 | ||
|         return;
 | ||
|       }
 | ||
|       called = true;
 | ||
|       clearTimeout(tout);
 | ||
|       try {
 | ||
|         if (!this.isIframeBlank(ifr)) {
 | ||
|           ifr.removeEventListener('load', listener);
 | ||
|           this.getIframeContents(ifr, successFn, errorFn);
 | ||
|         }
 | ||
|       } catch (e) {
 | ||
|         errorFn();
 | ||
|       }
 | ||
|     };
 | ||
|     ifr.addEventListener('load', listener);
 | ||
|     tout = setTimeout(listener, this.iframesTimeout);
 | ||
|   }
 | ||
|   onIframeReady(ifr, successFn, errorFn) {
 | ||
|     try {
 | ||
|       if (ifr.contentWindow.document.readyState === 'complete') {
 | ||
|         if (this.isIframeBlank(ifr)) {
 | ||
|           this.observeIframeLoad(ifr, successFn, errorFn);
 | ||
|         } else {
 | ||
|           this.getIframeContents(ifr, successFn, errorFn);
 | ||
|         }
 | ||
|       } else {
 | ||
|         this.observeIframeLoad(ifr, successFn, errorFn);
 | ||
|       }
 | ||
|     } catch (e) {
 | ||
|       errorFn();
 | ||
|     }
 | ||
|   }
 | ||
|   waitForIframes(ctx, done) {
 | ||
|     let eachCalled = 0;
 | ||
|     this.forEachIframe(ctx, () => true, ifr => {
 | ||
|       eachCalled++;
 | ||
|       this.waitForIframes(ifr.querySelector('html'), () => {
 | ||
|         if (!(--eachCalled)) {
 | ||
|           done();
 | ||
|         }
 | ||
|       });
 | ||
|     }, handled => {
 | ||
|       if (!handled) {
 | ||
|         done();
 | ||
|       }
 | ||
|     });
 | ||
|   }
 | ||
|   forEachIframe(ctx, filter, each, end = () => {}) {
 | ||
|     let ifr = ctx.querySelectorAll('iframe'),
 | ||
|       open = ifr.length,
 | ||
|       handled = 0;
 | ||
|     ifr = Array.prototype.slice.call(ifr);
 | ||
|     const checkEnd = () => {
 | ||
|       if (--open <= 0) {
 | ||
|         end(handled);
 | ||
|       }
 | ||
|     };
 | ||
|     if (!open) {
 | ||
|       checkEnd();
 | ||
|     }
 | ||
|     ifr.forEach(ifr => {
 | ||
|       if (DOMIterator.matches(ifr, this.exclude)) {
 | ||
|         checkEnd();
 | ||
|       } else {
 | ||
|         this.onIframeReady(ifr, con => {
 | ||
|           if (filter(ifr)) {
 | ||
|             handled++;
 | ||
|             each(con);
 | ||
|           }
 | ||
|           checkEnd();
 | ||
|         }, checkEnd);
 | ||
|       }
 | ||
|     });
 | ||
|   }
 | ||
|   createIterator(ctx, whatToShow, filter) {
 | ||
|     return document.createNodeIterator(ctx, whatToShow, filter, false);
 | ||
|   }
 | ||
|   createInstanceOnIframe(contents) {
 | ||
|     return new DOMIterator(contents.querySelector('html'), this.iframes);
 | ||
|   }
 | ||
|   compareNodeIframe(node, prevNode, ifr) {
 | ||
|     const compCurr = node.compareDocumentPosition(ifr),
 | ||
|       prev = Node.DOCUMENT_POSITION_PRECEDING;
 | ||
|     if (compCurr & prev) {
 | ||
|       if (prevNode !== null) {
 | ||
|         const compPrev = prevNode.compareDocumentPosition(ifr),
 | ||
|           after = Node.DOCUMENT_POSITION_FOLLOWING;
 | ||
|         if (compPrev & after) {
 | ||
|           return true;
 | ||
|         }
 | ||
|       } else {
 | ||
|         return true;
 | ||
|       }
 | ||
|     }
 | ||
|     return false;
 | ||
|   }
 | ||
|   getIteratorNode(itr) {
 | ||
|     const prevNode = itr.previousNode();
 | ||
|     let node;
 | ||
|     if (prevNode === null) {
 | ||
|       node = itr.nextNode();
 | ||
|     } else {
 | ||
|       node = itr.nextNode() && itr.nextNode();
 | ||
|     }
 | ||
|     return {
 | ||
|       prevNode,
 | ||
|       node
 | ||
|     };
 | ||
|   }
 | ||
|   checkIframeFilter(node, prevNode, currIfr, ifr) {
 | ||
|     let key = false,
 | ||
|       handled = false;
 | ||
|     ifr.forEach((ifrDict, i) => {
 | ||
|       if (ifrDict.val === currIfr) {
 | ||
|         key = i;
 | ||
|         handled = ifrDict.handled;
 | ||
|       }
 | ||
|     });
 | ||
|     if (this.compareNodeIframe(node, prevNode, currIfr)) {
 | ||
|       if (key === false && !handled) {
 | ||
|         ifr.push({
 | ||
|           val: currIfr,
 | ||
|           handled: true
 | ||
|         });
 | ||
|       } else if (key !== false && !handled) {
 | ||
|         ifr[key].handled = true;
 | ||
|       }
 | ||
|       return true;
 | ||
|     }
 | ||
|     if (key === false) {
 | ||
|       ifr.push({
 | ||
|         val: currIfr,
 | ||
|         handled: false
 | ||
|       });
 | ||
|     }
 | ||
|     return false;
 | ||
|   }
 | ||
|   handleOpenIframes(ifr, whatToShow, eCb, fCb) {
 | ||
|     ifr.forEach(ifrDict => {
 | ||
|       if (!ifrDict.handled) {
 | ||
|         this.getIframeContents(ifrDict.val, con => {
 | ||
|           this.createInstanceOnIframe(con).forEachNode(
 | ||
|             whatToShow, eCb, fCb
 | ||
|           );
 | ||
|         });
 | ||
|       }
 | ||
|     });
 | ||
|   }
 | ||
|   iterateThroughNodes(whatToShow, ctx, eachCb, filterCb, doneCb) {
 | ||
|     const itr = this.createIterator(ctx, whatToShow, filterCb);
 | ||
|     let ifr = [],
 | ||
|       elements = [],
 | ||
|       node, prevNode, retrieveNodes = () => {
 | ||
|         ({
 | ||
|           prevNode,
 | ||
|           node
 | ||
|         } = this.getIteratorNode(itr));
 | ||
|         return node;
 | ||
|       };
 | ||
|     while (retrieveNodes()) {
 | ||
|       if (this.iframes) {
 | ||
|         this.forEachIframe(ctx, currIfr => {
 | ||
|           return this.checkIframeFilter(node, prevNode, currIfr, ifr);
 | ||
|         }, con => {
 | ||
|           this.createInstanceOnIframe(con).forEachNode(
 | ||
|             whatToShow, ifrNode => elements.push(ifrNode), filterCb
 | ||
|           );
 | ||
|         });
 | ||
|       }
 | ||
|       elements.push(node);
 | ||
|     }
 | ||
|     elements.forEach(node => {
 | ||
|       eachCb(node);
 | ||
|     });
 | ||
|     if (this.iframes) {
 | ||
|       this.handleOpenIframes(ifr, whatToShow, eachCb, filterCb);
 | ||
|     }
 | ||
|     doneCb();
 | ||
|   }
 | ||
|   forEachNode(whatToShow, each, filter, done = () => {}) {
 | ||
|     const contexts = this.getContexts();
 | ||
|     let open = contexts.length;
 | ||
|     if (!open) {
 | ||
|       done();
 | ||
|     }
 | ||
|     contexts.forEach(ctx => {
 | ||
|       const ready = () => {
 | ||
|         this.iterateThroughNodes(whatToShow, ctx, each, filter, () => {
 | ||
|           if (--open <= 0) {
 | ||
|             done();
 | ||
|           }
 | ||
|         });
 | ||
|       };
 | ||
|       if (this.iframes) {
 | ||
|         this.waitForIframes(ctx, ready);
 | ||
|       } else {
 | ||
|         ready();
 | ||
|       }
 | ||
|     });
 | ||
|   }
 | ||
| }
 | ||
| 
 | ||
| class Mark {
 | ||
|   constructor(ctx) {
 | ||
|     this.ctx = ctx;
 | ||
|     this.ie = false;
 | ||
|     const ua = window.navigator.userAgent;
 | ||
|     if (ua.indexOf('MSIE') > -1 || ua.indexOf('Trident') > -1) {
 | ||
|       this.ie = true;
 | ||
|     }
 | ||
|   }
 | ||
|   set opt(val) {
 | ||
|     this._opt = Object.assign({}, {
 | ||
|       'element': '',
 | ||
|       'className': '',
 | ||
|       'exclude': [],
 | ||
|       'iframes': false,
 | ||
|       'iframesTimeout': 5000,
 | ||
|       'separateWordSearch': true,
 | ||
|       'diacritics': true,
 | ||
|       'synonyms': {},
 | ||
|       'accuracy': 'partially',
 | ||
|       'acrossElements': false,
 | ||
|       'caseSensitive': false,
 | ||
|       'ignoreJoiners': false,
 | ||
|       'ignoreGroups': 0,
 | ||
|       'ignorePunctuation': [],
 | ||
|       'wildcards': 'disabled',
 | ||
|       'each': () => {},
 | ||
|       'noMatch': () => {},
 | ||
|       'filter': () => true,
 | ||
|       'done': () => {},
 | ||
|       'debug': false,
 | ||
|       'log': window.console
 | ||
|     }, val);
 | ||
|   }
 | ||
|   get opt() {
 | ||
|     return this._opt;
 | ||
|   }
 | ||
|   get iterator() {
 | ||
|     return new DOMIterator(
 | ||
|       this.ctx,
 | ||
|       this.opt.iframes,
 | ||
|       this.opt.exclude,
 | ||
|       this.opt.iframesTimeout
 | ||
|     );
 | ||
|   }
 | ||
|   log(msg, level = 'debug') {
 | ||
|     const log = this.opt.log;
 | ||
|     if (!this.opt.debug) {
 | ||
|       return;
 | ||
|     }
 | ||
|     if (typeof log === 'object' && typeof log[level] === 'function') {
 | ||
|       log[level](`mark.js: ${msg}`);
 | ||
|     }
 | ||
|   }
 | ||
|   escapeStr(str) {
 | ||
|     return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
 | ||
|   }
 | ||
|   createRegExp(str) {
 | ||
|     if (this.opt.wildcards !== 'disabled') {
 | ||
|       str = this.setupWildcardsRegExp(str);
 | ||
|     }
 | ||
|     str = this.escapeStr(str);
 | ||
|     if (Object.keys(this.opt.synonyms).length) {
 | ||
|       str = this.createSynonymsRegExp(str);
 | ||
|     }
 | ||
|     if (this.opt.ignoreJoiners || this.opt.ignorePunctuation.length) {
 | ||
|       str = this.setupIgnoreJoinersRegExp(str);
 | ||
|     }
 | ||
|     if (this.opt.diacritics) {
 | ||
|       str = this.createDiacriticsRegExp(str);
 | ||
|     }
 | ||
|     str = this.createMergedBlanksRegExp(str);
 | ||
|     if (this.opt.ignoreJoiners || this.opt.ignorePunctuation.length) {
 | ||
|       str = this.createJoinersRegExp(str);
 | ||
|     }
 | ||
|     if (this.opt.wildcards !== 'disabled') {
 | ||
|       str = this.createWildcardsRegExp(str);
 | ||
|     }
 | ||
|     str = this.createAccuracyRegExp(str);
 | ||
|     return str;
 | ||
|   }
 | ||
|   createSynonymsRegExp(str) {
 | ||
|     const syn = this.opt.synonyms,
 | ||
|       sens = this.opt.caseSensitive ? '' : 'i',
 | ||
|       joinerPlaceholder = this.opt.ignoreJoiners ||
 | ||
|                 this.opt.ignorePunctuation.length ? '\u0000' : '';
 | ||
|     for (let index in syn) {
 | ||
|       if (syn.hasOwnProperty(index)) {
 | ||
|         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);
 | ||
|         if (k1 !== '' && k2 !== '') {
 | ||
|           str = str.replace(
 | ||
|             new RegExp(
 | ||
|               `(${this.escapeStr(k1)}|${this.escapeStr(k2)})`,
 | ||
|               `gm${sens}`
 | ||
|             ),
 | ||
|             joinerPlaceholder +
 | ||
|             `(${this.processSynomyms(k1)}|` +
 | ||
|             `${this.processSynomyms(k2)})` +
 | ||
|             joinerPlaceholder
 | ||
|           );
 | ||
|         }
 | ||
|       }
 | ||
|     }
 | ||
|     return str;
 | ||
|   }
 | ||
|   processSynomyms(str) {
 | ||
|     if (this.opt.ignoreJoiners || this.opt.ignorePunctuation.length) {
 | ||
|       str = this.setupIgnoreJoinersRegExp(str);
 | ||
|     }
 | ||
|     return str;
 | ||
|   }
 | ||
|   setupWildcardsRegExp(str) {
 | ||
|     str = str.replace(/(?:\\)*\?/g, val => {
 | ||
|       return val.charAt(0) === '\\' ? '?' : '\u0001';
 | ||
|     });
 | ||
|     return str.replace(/(?:\\)*\*/g, val => {
 | ||
|       return val.charAt(0) === '\\' ? '*' : '\u0002';
 | ||
|     });
 | ||
|   }
 | ||
|   createWildcardsRegExp(str) {
 | ||
|     let spaces = this.opt.wildcards === 'withSpaces';
 | ||
|     return str
 | ||
|       .replace(/\u0001/g, spaces ? '[\\S\\s]?' : '\\S?')
 | ||
|       .replace(/\u0002/g, spaces ? '[\\S\\s]*?' : '\\S*');
 | ||
|   }
 | ||
|   setupIgnoreJoinersRegExp(str) {
 | ||
|     return str.replace(/[^(|)\\]/g, (val, indx, original) => {
 | ||
|       let nextChar = original.charAt(indx + 1);
 | ||
|       if (/[(|)\\]/.test(nextChar) || nextChar === '') {
 | ||
|         return val;
 | ||
|       } else {
 | ||
|         return val + '\u0000';
 | ||
|       }
 | ||
|     });
 | ||
|   }
 | ||
|   createJoinersRegExp(str) {
 | ||
|     let joiner = [];
 | ||
|     const ignorePunctuation = this.opt.ignorePunctuation;
 | ||
|     if (Array.isArray(ignorePunctuation) && ignorePunctuation.length) {
 | ||
|       joiner.push(this.escapeStr(ignorePunctuation.join('')));
 | ||
|     }
 | ||
|     if (this.opt.ignoreJoiners) {
 | ||
|       joiner.push('\\u00ad\\u200b\\u200c\\u200d');
 | ||
|     }
 | ||
|     return joiner.length ?
 | ||
|       str.split(/\u0000+/).join(`[${joiner.join('')}]*`) :
 | ||
|       str;
 | ||
|   }
 | ||
|   createDiacriticsRegExp(str) {
 | ||
|     const sens = this.opt.caseSensitive ? '' : 'i',
 | ||
|       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ŽŻŹ'
 | ||
|       ];
 | ||
|     let handled = [];
 | ||
|     str.split('').forEach(ch => {
 | ||
|       dct.every(dct => {
 | ||
|         if (dct.indexOf(ch) !== -1) {
 | ||
|           if (handled.indexOf(dct) > -1) {
 | ||
|             return false;
 | ||
|           }
 | ||
|           str = str.replace(
 | ||
|             new RegExp(`[${dct}]`, `gm${sens}`), `[${dct}]`
 | ||
|           );
 | ||
|           handled.push(dct);
 | ||
|         }
 | ||
|         return true;
 | ||
|       });
 | ||
|     });
 | ||
|     return str;
 | ||
|   }
 | ||
|   createMergedBlanksRegExp(str) {
 | ||
|     return str.replace(/[\s]+/gmi, '[\\s]+');
 | ||
|   }
 | ||
|   createAccuracyRegExp(str) {
 | ||
|     const chars = '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~¡¿';
 | ||
|     let acc = this.opt.accuracy,
 | ||
|       val = typeof acc === 'string' ? acc : acc.value,
 | ||
|       ls = typeof acc === 'string' ? [] : acc.limiters,
 | ||
|       lsJoin = '';
 | ||
|     ls.forEach(limiter => {
 | ||
|       lsJoin += `|${this.escapeStr(limiter)}`;
 | ||
|     });
 | ||
|     switch (val) {
 | ||
|     case 'partially':
 | ||
|     default:
 | ||
|       return `()(${str})`;
 | ||
|     case 'complementary':
 | ||
|       lsJoin = '\\s' + (lsJoin ? lsJoin : this.escapeStr(chars));
 | ||
|       return `()([^${lsJoin}]*${str}[^${lsJoin}]*)`;
 | ||
|     case 'exactly':
 | ||
|       return `(^|\\s${lsJoin})(${str})(?=$|\\s${lsJoin})`;
 | ||
|     }
 | ||
|   }
 | ||
|   getSeparatedKeywords(sv) {
 | ||
|     let stack = [];
 | ||
|     sv.forEach(kw => {
 | ||
|       if (!this.opt.separateWordSearch) {
 | ||
|         if (kw.trim() && stack.indexOf(kw) === -1) {
 | ||
|           stack.push(kw);
 | ||
|         }
 | ||
|       } else {
 | ||
|         kw.split(' ').forEach(kwSplitted => {
 | ||
|           if (kwSplitted.trim() && stack.indexOf(kwSplitted) === -1) {
 | ||
|             stack.push(kwSplitted);
 | ||
|           }
 | ||
|         });
 | ||
|       }
 | ||
|     });
 | ||
|     return {
 | ||
|       'keywords': stack.sort((a, b) => {
 | ||
|         return b.length - a.length;
 | ||
|       }),
 | ||
|       'length': stack.length
 | ||
|     };
 | ||
|   }
 | ||
|   isNumeric(value) {
 | ||
|     return Number(parseFloat(value)) == value;
 | ||
|   }
 | ||
|   checkRanges(array) {
 | ||
|     if (
 | ||
|       !Array.isArray(array) ||
 | ||
|       Object.prototype.toString.call( array[0] ) !== '[object Object]'
 | ||
|     ) {
 | ||
|       this.log('markRanges() will only accept an array of objects');
 | ||
|       this.opt.noMatch(array);
 | ||
|       return [];
 | ||
|     }
 | ||
|     const stack = [];
 | ||
|     let last = 0;
 | ||
|     array
 | ||
|       .sort((a, b) => {
 | ||
|         return a.start - b.start;
 | ||
|       })
 | ||
|       .forEach(item => {
 | ||
|         let {start, end, valid} = this.callNoMatchOnInvalidRanges(item, last);
 | ||
|         if (valid) {
 | ||
|           item.start = start;
 | ||
|           item.length = end - start;
 | ||
|           stack.push(item);
 | ||
|           last = end;
 | ||
|         }
 | ||
|       });
 | ||
|     return stack;
 | ||
|   }
 | ||
|   callNoMatchOnInvalidRanges(range, last) {
 | ||
|     let start, end,
 | ||
|       valid = false;
 | ||
|     if (range && typeof range.start !== 'undefined') {
 | ||
|       start = parseInt(range.start, 10);
 | ||
|       end = start + parseInt(range.length, 10);
 | ||
|       if (
 | ||
|         this.isNumeric(range.start) &&
 | ||
|         this.isNumeric(range.length) &&
 | ||
|         end - last > 0 &&
 | ||
|         end - start > 0
 | ||
|       ) {
 | ||
|         valid = true;
 | ||
|       } else {
 | ||
|         this.log(
 | ||
|           'Ignoring invalid or overlapping range: ' +
 | ||
|                     `${JSON.stringify(range)}`
 | ||
|         );
 | ||
|         this.opt.noMatch(range);
 | ||
|       }
 | ||
|     } else {
 | ||
|       this.log(`Ignoring invalid range: ${JSON.stringify(range)}`);
 | ||
|       this.opt.noMatch(range);
 | ||
|     }
 | ||
|     return {
 | ||
|       start: start,
 | ||
|       end: end,
 | ||
|       valid: valid
 | ||
|     };
 | ||
|   }
 | ||
|   checkWhitespaceRanges(range, originalLength, string) {
 | ||
|     let end,
 | ||
|       valid = true,
 | ||
|       max = string.length,
 | ||
|       offset = originalLength - max,
 | ||
|       start = parseInt(range.start, 10) - offset;
 | ||
|     start = start > max ? max : start;
 | ||
|     end = start + parseInt(range.length, 10);
 | ||
|     if (end > max) {
 | ||
|       end = max;
 | ||
|       this.log(`End range automatically set to the max value of ${max}`);
 | ||
|     }
 | ||
|     if (start < 0 || end - start < 0 || start > max || end > max) {
 | ||
|       valid = false;
 | ||
|       this.log(`Invalid range: ${JSON.stringify(range)}`);
 | ||
|       this.opt.noMatch(range);
 | ||
|     } else if (string.substring(start, end).replace(/\s+/g, '') === '') {
 | ||
|       valid = false;
 | ||
|       this.log('Skipping whitespace only range: ' +JSON.stringify(range));
 | ||
|       this.opt.noMatch(range);
 | ||
|     }
 | ||
|     return {
 | ||
|       start: start,
 | ||
|       end: end,
 | ||
|       valid: valid
 | ||
|     };
 | ||
|   }
 | ||
|   getTextNodes(cb) {
 | ||
|     let val = '',
 | ||
|       nodes = [];
 | ||
|     this.iterator.forEachNode(NodeFilter.SHOW_TEXT, node => {
 | ||
|       nodes.push({
 | ||
|         start: val.length,
 | ||
|         end: (val += node.textContent).length,
 | ||
|         node
 | ||
|       });
 | ||
|     }, node => {
 | ||
|       if (this.matchesExclude(node.parentNode)) {
 | ||
|         return NodeFilter.FILTER_REJECT;
 | ||
|       } else {
 | ||
|         return NodeFilter.FILTER_ACCEPT;
 | ||
|       }
 | ||
|     }, () => {
 | ||
|       cb({
 | ||
|         value: val,
 | ||
|         nodes: nodes
 | ||
|       });
 | ||
|     });
 | ||
|   }
 | ||
|   matchesExclude(el) {
 | ||
|     return DOMIterator.matches(el, this.opt.exclude.concat([
 | ||
|       'script', 'style', 'title', 'head', 'html'
 | ||
|     ]));
 | ||
|   }
 | ||
|   wrapRangeInTextNode(node, start, end) {
 | ||
|     const hEl = !this.opt.element ? 'mark' : this.opt.element,
 | ||
|       startNode = node.splitText(start),
 | ||
|       ret = startNode.splitText(end - start);
 | ||
|     let repl = document.createElement(hEl);
 | ||
|     repl.setAttribute('data-markjs', 'true');
 | ||
|     if (this.opt.className) {
 | ||
|       repl.setAttribute('class', this.opt.className);
 | ||
|     }
 | ||
|     repl.textContent = startNode.textContent;
 | ||
|     startNode.parentNode.replaceChild(repl, startNode);
 | ||
|     return ret;
 | ||
|   }
 | ||
|   wrapRangeInMappedTextNode(dict, start, end, filterCb, eachCb) {
 | ||
|     dict.nodes.every((n, i) => {
 | ||
|       const sibl = dict.nodes[i + 1];
 | ||
|       if (typeof sibl === 'undefined' || sibl.start > start) {
 | ||
|         if (!filterCb(n.node)) {
 | ||
|           return false;
 | ||
|         }
 | ||
|         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);
 | ||
|         n.node = this.wrapRangeInTextNode(n.node, s, e);
 | ||
|         dict.value = startStr + endStr;
 | ||
|         dict.nodes.forEach((k, j) => {
 | ||
|           if (j >= i) {
 | ||
|             if (dict.nodes[j].start > 0 && j !== i) {
 | ||
|               dict.nodes[j].start -= e;
 | ||
|             }
 | ||
|             dict.nodes[j].end -= e;
 | ||
|           }
 | ||
|         });
 | ||
|         end -= e;
 | ||
|         eachCb(n.node.previousSibling, n.start);
 | ||
|         if (end > n.end) {
 | ||
|           start = n.end;
 | ||
|         } else {
 | ||
|           return false;
 | ||
|         }
 | ||
|       }
 | ||
|       return true;
 | ||
|     });
 | ||
|   }
 | ||
|   wrapMatches(regex, ignoreGroups, filterCb, eachCb, endCb) {
 | ||
|     const matchIdx = ignoreGroups === 0 ? 0 : ignoreGroups + 1;
 | ||
|     this.getTextNodes(dict => {
 | ||
|       dict.nodes.forEach(node => {
 | ||
|         node = node.node;
 | ||
|         let match;
 | ||
|         while (
 | ||
|           (match = regex.exec(node.textContent)) !== null &&
 | ||
|           match[matchIdx] !== ''
 | ||
|         ) {
 | ||
|           if (!filterCb(match[matchIdx], node)) {
 | ||
|             continue;
 | ||
|           }
 | ||
|           let pos = match.index;
 | ||
|           if (matchIdx !== 0) {
 | ||
|             for (let i = 1; i < matchIdx; i++) {
 | ||
|               pos += match[i].length;
 | ||
|             }
 | ||
|           }
 | ||
|           node = this.wrapRangeInTextNode(
 | ||
|             node,
 | ||
|             pos,
 | ||
|             pos + match[matchIdx].length
 | ||
|           );
 | ||
|           eachCb(node.previousSibling);
 | ||
|           regex.lastIndex = 0;
 | ||
|         }
 | ||
|       });
 | ||
|       endCb();
 | ||
|     });
 | ||
|   }
 | ||
|   wrapMatchesAcrossElements(regex, ignoreGroups, filterCb, eachCb, endCb) {
 | ||
|     const matchIdx = ignoreGroups === 0 ? 0 : ignoreGroups + 1;
 | ||
|     this.getTextNodes(dict => {
 | ||
|       let match;
 | ||
|       while (
 | ||
|         (match = regex.exec(dict.value)) !== null &&
 | ||
|         match[matchIdx] !== ''
 | ||
|       ) {
 | ||
|         let start = match.index;
 | ||
|         if (matchIdx !== 0) {
 | ||
|           for (let i = 1; i < matchIdx; i++) {
 | ||
|             start += match[i].length;
 | ||
|           }
 | ||
|         }
 | ||
|         const end = start + match[matchIdx].length;
 | ||
|         this.wrapRangeInMappedTextNode(dict, start, end, node => {
 | ||
|           return filterCb(match[matchIdx], node);
 | ||
|         }, (node, lastIndex) => {
 | ||
|           regex.lastIndex = lastIndex;
 | ||
|           eachCb(node);
 | ||
|         });
 | ||
|       }
 | ||
|       endCb();
 | ||
|     });
 | ||
|   }
 | ||
|   wrapRangeFromIndex(ranges, filterCb, eachCb, endCb) {
 | ||
|     this.getTextNodes(dict => {
 | ||
|       const originalLength = dict.value.length;
 | ||
|       ranges.forEach((range, counter) => {
 | ||
|         let {start, end, valid} = this.checkWhitespaceRanges(
 | ||
|           range,
 | ||
|           originalLength,
 | ||
|           dict.value
 | ||
|         );
 | ||
|         if (valid) {
 | ||
|           this.wrapRangeInMappedTextNode(dict, start, end, node => {
 | ||
|             return filterCb(
 | ||
|               node,
 | ||
|               range,
 | ||
|               dict.value.substring(start, end),
 | ||
|               counter
 | ||
|             );
 | ||
|           }, node => {
 | ||
|             eachCb(node, range);
 | ||
|           });
 | ||
|         }
 | ||
|       });
 | ||
|       endCb();
 | ||
|     });
 | ||
|   }
 | ||
|   unwrapMatches(node) {
 | ||
|     const parent = node.parentNode;
 | ||
|     let docFrag = document.createDocumentFragment();
 | ||
|     while (node.firstChild) {
 | ||
|       docFrag.appendChild(node.removeChild(node.firstChild));
 | ||
|     }
 | ||
|     parent.replaceChild(docFrag, node);
 | ||
|     if (!this.ie) {
 | ||
|       parent.normalize();
 | ||
|     } else {
 | ||
|       this.normalizeTextNode(parent);
 | ||
|     }
 | ||
|   }
 | ||
|   normalizeTextNode(node) {
 | ||
|     if (!node) {
 | ||
|       return;
 | ||
|     }
 | ||
|     if (node.nodeType === 3) {
 | ||
|       while (node.nextSibling && node.nextSibling.nodeType === 3) {
 | ||
|         node.nodeValue += node.nextSibling.nodeValue;
 | ||
|         node.parentNode.removeChild(node.nextSibling);
 | ||
|       }
 | ||
|     } else {
 | ||
|       this.normalizeTextNode(node.firstChild);
 | ||
|     }
 | ||
|     this.normalizeTextNode(node.nextSibling);
 | ||
|   }
 | ||
|   markRegExp(regexp, opt) {
 | ||
|     this.opt = opt;
 | ||
|     this.log(`Searching with expression "${regexp}"`);
 | ||
|     let totalMatches = 0,
 | ||
|       fn = 'wrapMatches';
 | ||
|     const eachCb = element => {
 | ||
|       totalMatches++;
 | ||
|       this.opt.each(element);
 | ||
|     };
 | ||
|     if (this.opt.acrossElements) {
 | ||
|       fn = 'wrapMatchesAcrossElements';
 | ||
|     }
 | ||
|     this[fn](regexp, this.opt.ignoreGroups, (match, node) => {
 | ||
|       return this.opt.filter(node, match, totalMatches);
 | ||
|     }, eachCb, () => {
 | ||
|       if (totalMatches === 0) {
 | ||
|         this.opt.noMatch(regexp);
 | ||
|       }
 | ||
|       this.opt.done(totalMatches);
 | ||
|     });
 | ||
|   }
 | ||
|   mark(sv, opt) {
 | ||
|     this.opt = opt;
 | ||
|     let totalMatches = 0,
 | ||
|       fn = 'wrapMatches';
 | ||
|     const {
 | ||
|         keywords: kwArr,
 | ||
|         length: kwArrLen
 | ||
|       } = this.getSeparatedKeywords(typeof sv === 'string' ? [sv] : sv),
 | ||
|       sens = this.opt.caseSensitive ? '' : 'i',
 | ||
|       handler = kw => {
 | ||
|         let regex = new RegExp(this.createRegExp(kw), `gm${sens}`),
 | ||
|           matches = 0;
 | ||
|         this.log(`Searching with expression "${regex}"`);
 | ||
|         this[fn](regex, 1, (term, node) => {
 | ||
|           return this.opt.filter(node, kw, totalMatches, matches);
 | ||
|         }, element => {
 | ||
|           matches++;
 | ||
|           totalMatches++;
 | ||
|           this.opt.each(element);
 | ||
|         }, () => {
 | ||
|           if (matches === 0) {
 | ||
|             this.opt.noMatch(kw);
 | ||
|           }
 | ||
|           if (kwArr[kwArrLen - 1] === kw) {
 | ||
|             this.opt.done(totalMatches);
 | ||
|           } else {
 | ||
|             handler(kwArr[kwArr.indexOf(kw) + 1]);
 | ||
|           }
 | ||
|         });
 | ||
|       };
 | ||
|     if (this.opt.acrossElements) {
 | ||
|       fn = 'wrapMatchesAcrossElements';
 | ||
|     }
 | ||
|     if (kwArrLen === 0) {
 | ||
|       this.opt.done(totalMatches);
 | ||
|     } else {
 | ||
|       handler(kwArr[0]);
 | ||
|     }
 | ||
|   }
 | ||
|   markRanges(rawRanges, opt) {
 | ||
|     this.opt = opt;
 | ||
|     let totalMatches = 0,
 | ||
|       ranges = this.checkRanges(rawRanges);
 | ||
|     if (ranges && ranges.length) {
 | ||
|       this.log(
 | ||
|         'Starting to mark with the following ranges: ' +
 | ||
|         JSON.stringify(ranges)
 | ||
|       );
 | ||
|       this.wrapRangeFromIndex(
 | ||
|         ranges, (node, range, match, counter) => {
 | ||
|           return this.opt.filter(node, range, match, counter);
 | ||
|         }, (element, range) => {
 | ||
|           totalMatches++;
 | ||
|           this.opt.each(element, range);
 | ||
|         }, () => {
 | ||
|           this.opt.done(totalMatches);
 | ||
|         }
 | ||
|       );
 | ||
|     } else {
 | ||
|       this.opt.done(totalMatches);
 | ||
|     }
 | ||
|   }
 | ||
|   unmark(opt) {
 | ||
|     this.opt = opt;
 | ||
|     let sel = this.opt.element ? this.opt.element : '*';
 | ||
|     sel += '[data-markjs]';
 | ||
|     if (this.opt.className) {
 | ||
|       sel += `.${this.opt.className}`;
 | ||
|     }
 | ||
|     this.log(`Removal selector "${sel}"`);
 | ||
|     this.iterator.forEachNode(NodeFilter.SHOW_ELEMENT, node => {
 | ||
|       this.unwrapMatches(node);
 | ||
|     }, node => {
 | ||
|       const matchesSel = DOMIterator.matches(node, sel),
 | ||
|         matchesExclude = this.matchesExclude(node);
 | ||
|       if (!matchesSel || matchesExclude) {
 | ||
|         return NodeFilter.FILTER_REJECT;
 | ||
|       } else {
 | ||
|         return NodeFilter.FILTER_ACCEPT;
 | ||
|       }
 | ||
|     }, this.opt.done);
 | ||
|   }
 | ||
| }
 | ||
| 
 | ||
| $.fn.mark = function(sv, opt) {
 | ||
|   new Mark(this.get()).mark(sv, opt);
 | ||
|   return this;
 | ||
| };
 | ||
| $.fn.markRegExp = function(regexp, opt) {
 | ||
|   new Mark(this.get()).markRegExp(regexp, opt);
 | ||
|   return this;
 | ||
| };
 | ||
| $.fn.markRanges = function(ranges, opt) {
 | ||
|   new Mark(this.get()).markRanges(ranges, opt);
 | ||
|   return this;
 | ||
| };
 | ||
| $.fn.unmark = function(opt) {
 | ||
|   new Mark(this.get()).unmark(opt);
 | ||
|   return this;
 | ||
| };
 | ||
| 
 | ||
| return $;
 | ||
| 
 | ||
| })));
 |