diff --git a/README.md b/README.md index 250b192..93a3b66 100644 --- a/README.md +++ b/README.md @@ -134,3 +134,7 @@ There are some FreshRSS extensions out there, developed by community members: ### By [@kalvn](https://github.com/kalvn) * [Mark Previous as Read](https://github.com/kalvn/freshrss-mark-previous-as-read): Adds a button in the footer of each entry. Clicking this button will mark all previous entries belonging to the current feed, as read. + +### By [@lukasMega](https://github.com/lukasMega) + +* [Word Highlighter](https://github.com/lukasMega/Extensions-FreshRSS-): Gives you ability to highlight user-defined words (using [mark.js](https://github.com/julkue/mark.js)) diff --git a/extensions.json b/extensions.json index db1129b..f4dda25 100644 --- a/extensions.json +++ b/extensions.json @@ -512,7 +512,7 @@ "name": "Word Highlighter", "author": "Lukas Melega", "description": "Highlighting user-defined words", - "version": "0.0.1", + "version": "0.0.2", "entrypoint": "WordHighlighter", "type": "user", "url": "https://github.com/FreshRSS/Extensions", diff --git a/xExtension-WordHighlighter/README.md b/xExtension-WordHighlighter/README.md index 6778de6..51a23f8 100644 --- a/xExtension-WordHighlighter/README.md +++ b/xExtension-WordHighlighter/README.md @@ -24,4 +24,7 @@ Light theme: ## Changelog +- 0.0.2 use `json` for storing configuration, add more configuration options + (enable_in_article, enable_logs, case_sensitive, separate_word_search) + and refactored & simplified code - 0.0.1 initial version (as a proper FreshRSS extension) diff --git a/xExtension-WordHighlighter/configure.phtml b/xExtension-WordHighlighter/configure.phtml index ad5607f..f9295f1 100644 --- a/xExtension-WordHighlighter/configure.phtml +++ b/xExtension-WordHighlighter/configure.phtml @@ -1,16 +1,79 @@
diff --git a/xExtension-WordHighlighter/extension.php b/xExtension-WordHighlighter/extension.php index 8c3af1d..5b09c33 100644 --- a/xExtension-WordHighlighter/extension.php +++ b/xExtension-WordHighlighter/extension.php @@ -4,8 +4,14 @@ declare(strict_types=1); final class WordHighlighterExtension extends Minz_Extension { - public string $word_highlighter_conf; + const JSON_ENCODE_CONF = JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR; + + public string $word_highlighter_conf = 'test'; public string $permission_problem = ''; + public bool $enable_in_article = false; + public bool $enable_logs = false; + public bool $case_sensitive = false; + public bool $separate_word_search = false; #[\Override] public function init(): void @@ -15,19 +21,18 @@ final class WordHighlighterExtension extends Minz_Extension // register CSS for WordHighlighter: Minz_View::appendStyle($this->getFileUrl('style.css', 'css')); - // register script for highlighting functionality: - Minz_View::appendScript($this->getFileUrl('word-highlighter.js', 'js')); + Minz_View::appendScript($this->getFileUrl('mark.min.js', 'js'), false, false, false); $current_user = Minz_Session::paramString('currentUser'); - // $filename = 'config-words.' . $current_user . '.js'; - // $configFileWithWords = join_path($this->getPath(), 'static', $filename); $staticPath = join_path($this->getPath(), 'static'); - $configFileJs = join_path($staticPath, ('config-words.' . $current_user . '.js')); + $configFileJs = join_path($staticPath, ('config.' . $current_user . '.js')); if (file_exists($configFileJs)) { - Minz_View::appendScript($this->getFileUrl(('config-words.' . $current_user . '.js'), 'js')); + Minz_View::appendScript($this->getFileUrl(('config.' . $current_user . '.js'), 'js')); } + + Minz_View::appendScript($this->getFileUrl('word-highlighter.js', 'js')); } #[\Override] @@ -36,43 +41,60 @@ final class WordHighlighterExtension extends Minz_Extension $this->registerTranslates(); $current_user = Minz_Session::paramString('currentUser'); - $filename = 'config-words.' . $current_user . '.txt'; $staticPath = join_path($this->getPath(), 'static'); - $configFileWithWords = join_path($staticPath, $filename); - if (!file_exists($configFileWithWords) && !is_writable($staticPath)) { + $configFileJson = join_path($staticPath, ('config.' . $current_user . '.json')); + + if (!file_exists($configFileJson) && !is_writable($staticPath)) { $tmpPath = explode(EXTENSIONS_PATH . '/', $staticPath); $this->permission_problem = $tmpPath[1] . '/'; - } elseif (file_exists($configFileWithWords) && !is_writable($configFileWithWords)) { - $tmpPath = explode(EXTENSIONS_PATH . '/', $configFileWithWords); + } elseif (file_exists($configFileJson) && !is_writable($configFileJson)) { + $tmpPath = explode(EXTENSIONS_PATH . '/', $configFileJson); $this->permission_problem = $tmpPath[1]; } elseif (Minz_Request::isPost()) { - $config = html_entity_decode(Minz_Request::paramString('word-highlighter-conf')); - file_put_contents($configFileWithWords, $config); - file_put_contents(join_path($staticPath, ('config-words.' . $current_user . '.js')), $this->toJSArray($config)); + $configWordList = html_entity_decode(Minz_Request::paramString('words_list')); + + $this->word_highlighter_conf = $configWordList; + $this->enable_in_article = (bool) Minz_Request::paramString('enable-in-article'); + $this->enable_logs = (bool) Minz_Request::paramString('enable_logs'); + $this->case_sensitive = (bool) Minz_Request::paramString('case_sensitive'); + $this->separate_word_search = (bool) Minz_Request::paramString('separate_word_search'); + + $lineSeparator = strpos($configWordList, "\r\n") ? "\r\n" : "\n"; + $configObj = [ + 'enable_in_article' => $this->enable_in_article, + 'enable_logs' => $this->enable_logs, + 'case_sensitive' => $this->case_sensitive, + 'separate_word_search' => $this->separate_word_search, + 'words' => explode($lineSeparator, $configWordList), + ]; + $configJson = json_encode($configObj, WordHighlighterExtension::JSON_ENCODE_CONF); + file_put_contents(join_path($staticPath, ('config.' . $current_user . '.json')), $configJson . PHP_EOL); + file_put_contents(join_path($staticPath, ('config.' . $current_user . '.js')), $this->jsonToJs($configJson) . PHP_EOL); } - $this->word_highlighter_conf = ''; - if (file_exists($configFileWithWords)) { - $this->word_highlighter_conf = htmlentities(file_get_contents($configFileWithWords)) ?: ''; + if (file_exists($configFileJson)) { + try { + $confJson = json_decode(file_get_contents($configFileJson) ?: '', true, 8, JSON_THROW_ON_ERROR); + $this->enable_in_article = $confJson['enable_in_article'] ?: false; + $this->enable_logs = $confJson['enable_logs'] ?: false; + $this->case_sensitive = $confJson['case_sensitive'] ?: false; + $this->separate_word_search = $confJson['separate_word_search'] ?: false; + $this->word_highlighter_conf = implode("\n", $confJson['words']); + + } catch (Exception $exception) { + // probably nothing to do needed + } } } - private function toJSArray($inputString) + private function jsonToJs($jsonStr) { - $array = explode("\n", $inputString); - $jsArray = array(); - foreach ($array as $item) { - $trimmedItem = trim($item); - if (strlen($trimmedItem) > 1) { - array_push($jsArray, "'" . addslashes(trim($item)) . "',"); - } - } - $js = "window.WordHighlighterConf = [\n" . - implode("\n", $jsArray) . - "\n]; console.log({ wordsLoaded: window.WHConfig })"; + $js = "window.WordHighlighterConf = " . + $jsonStr . ";\n" . + "window.WordHighlighterConf.enable_logs && console.log('WordHighlighter: loaded user config:', window.WordHighlighterConf);"; return $js; } } diff --git a/xExtension-WordHighlighter/i18n/de/ext.php b/xExtension-WordHighlighter/i18n/de/ext.php index c65a248..9954541 100644 --- a/xExtension-WordHighlighter/i18n/de/ext.php +++ b/xExtension-WordHighlighter/i18n/de/ext.php @@ -2,7 +2,14 @@ return array( 'word_highlighter' => array( - 'write_words' => 'Words to highlight (separated by newline)', + 'write_words' => 'Words to highlight', + 'write_words_more' => '(separated by newline)', + 'enable_in_article' => 'Enable highlighting also in article', + 'enable_in_article_more' => '(⚠️ may be slower with a lot of words)', + 'enable_logs' => 'Enable logs', + 'case_sensitive' => 'Case sensitive', + 'separate_word_search' => 'Separate word search', + 'test_highlighting_word' => 'highlight', 'permission_problem' => 'Your config file is not writable, please change the file permissions for %s', ), ); diff --git a/xExtension-WordHighlighter/i18n/en/ext.php b/xExtension-WordHighlighter/i18n/en/ext.php index c65a248..9954541 100644 --- a/xExtension-WordHighlighter/i18n/en/ext.php +++ b/xExtension-WordHighlighter/i18n/en/ext.php @@ -2,7 +2,14 @@ return array( 'word_highlighter' => array( - 'write_words' => 'Words to highlight (separated by newline)', + 'write_words' => 'Words to highlight', + 'write_words_more' => '(separated by newline)', + 'enable_in_article' => 'Enable highlighting also in article', + 'enable_in_article_more' => '(⚠️ may be slower with a lot of words)', + 'enable_logs' => 'Enable logs', + 'case_sensitive' => 'Case sensitive', + 'separate_word_search' => 'Separate word search', + 'test_highlighting_word' => 'highlight', 'permission_problem' => 'Your config file is not writable, please change the file permissions for %s', ), ); diff --git a/xExtension-WordHighlighter/i18n/fr/ext.php b/xExtension-WordHighlighter/i18n/fr/ext.php index c65a248..9954541 100644 --- a/xExtension-WordHighlighter/i18n/fr/ext.php +++ b/xExtension-WordHighlighter/i18n/fr/ext.php @@ -2,7 +2,14 @@ return array( 'word_highlighter' => array( - 'write_words' => 'Words to highlight (separated by newline)', + 'write_words' => 'Words to highlight', + 'write_words_more' => '(separated by newline)', + 'enable_in_article' => 'Enable highlighting also in article', + 'enable_in_article_more' => '(⚠️ may be slower with a lot of words)', + 'enable_logs' => 'Enable logs', + 'case_sensitive' => 'Case sensitive', + 'separate_word_search' => 'Separate word search', + 'test_highlighting_word' => 'highlight', 'permission_problem' => 'Your config file is not writable, please change the file permissions for %s', ), ); diff --git a/xExtension-WordHighlighter/i18n/tr/ext.php b/xExtension-WordHighlighter/i18n/tr/ext.php index c65a248..9954541 100644 --- a/xExtension-WordHighlighter/i18n/tr/ext.php +++ b/xExtension-WordHighlighter/i18n/tr/ext.php @@ -2,7 +2,14 @@ return array( 'word_highlighter' => array( - 'write_words' => 'Words to highlight (separated by newline)', + 'write_words' => 'Words to highlight', + 'write_words_more' => '(separated by newline)', + 'enable_in_article' => 'Enable highlighting also in article', + 'enable_in_article_more' => '(⚠️ may be slower with a lot of words)', + 'enable_logs' => 'Enable logs', + 'case_sensitive' => 'Case sensitive', + 'separate_word_search' => 'Separate word search', + 'test_highlighting_word' => 'highlight', 'permission_problem' => 'Your config file is not writable, please change the file permissions for %s', ), ); diff --git a/xExtension-WordHighlighter/static/mark.min.js b/xExtension-WordHighlighter/static/mark.min.js new file mode 100644 index 0000000..c725bc4 --- /dev/null +++ b/xExtension-WordHighlighter/static/mark.min.js @@ -0,0 +1,13 @@ +/** + * mark.js (Library for highlighting words) + * https://github.com/julkue/mark.js/blob/master/dist/mark.es6.min.js + */ +/* eslint-disable */ + +/*!*************************************************** +* mark.js v9.0.0 +* https://markjs.io/ +* Copyright (c) 2014–2018, Julian Kühnel +* Released under the MIT license https://git.io/vwTVl +*****************************************************/ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.Mark=t()}(this,function(){"use strict";class e{constructor(e,t=!0,s=[],r=5e3){this.ctx=e,this.iframes=t,this.exclude=s,this.iframesTimeout=r}static matches(e,t){const s="string"==typeof t?[t]:t,r=e.matches||e.matchesSelector||e.msMatchesSelector||e.mozMatchesSelector||e.oMatchesSelector||e.webkitMatchesSelector;if(r){let t=!1;return s.every(s=>!r.call(e,s)||(t=!0,!1)),t}return!1}getContexts(){let e,t=[];return(e=void 0!==this.ctx&&this.ctx?NodeList.prototype.isPrototypeOf(this.ctx)?Array.prototype.slice.call(this.ctx):Array.isArray(this.ctx)?this.ctx:"string"==typeof this.ctx?Array.prototype.slice.call(document.querySelectorAll(this.ctx)):[this.ctx]:[]).forEach(e=>{const s=t.filter(t=>t.contains(e)).length>0;-1!==t.indexOf(e)||s||t.push(e)}),t}getIframeContents(e,t,s=(()=>{})){let r;try{const t=e.contentWindow;if(r=t.document,!t||!r)throw new Error("iframe inaccessible")}catch(e){s()}r&&t(r)}isIframeBlank(e){const t="about:blank",s=e.getAttribute("src").trim();return e.contentWindow.location.href===t&&s!==t&&s}observeIframeLoad(e,t,s){let r=!1,i=null;const n=()=>{if(!r){r=!0,clearTimeout(i);try{this.isIframeBlank(e)||(e.removeEventListener("load",n),this.getIframeContents(e,t,s))}catch(e){s()}}};e.addEventListener("load",n),i=setTimeout(n,this.iframesTimeout)}onIframeReady(e,t,s){try{"complete"===e.contentWindow.document.readyState?this.isIframeBlank(e)?this.observeIframeLoad(e,t,s):this.getIframeContents(e,t,s):this.observeIframeLoad(e,t,s)}catch(e){s()}}waitForIframes(e,t){let s=0;this.forEachIframe(e,()=>!0,e=>{s++,this.waitForIframes(e.querySelector("html"),()=>{--s||t()})},e=>{e||t()})}forEachIframe(t,s,r,i=(()=>{})){let n=t.querySelectorAll("iframe"),o=n.length,a=0;n=Array.prototype.slice.call(n);const h=()=>{--o<=0&&i(a)};o||h(),n.forEach(t=>{e.matches(t,this.exclude)?h():this.onIframeReady(t,e=>{s(t)&&(a++,r(e)),h()},h)})}createIterator(e,t,s){return document.createNodeIterator(e,t,s,!1)}createInstanceOnIframe(t){return new e(t.querySelector("html"),this.iframes)}compareNodeIframe(e,t,s){if(e.compareDocumentPosition(s)&Node.DOCUMENT_POSITION_PRECEDING){if(null===t)return!0;if(t.compareDocumentPosition(s)&Node.DOCUMENT_POSITION_FOLLOWING)return!0}return!1}getIteratorNode(e){const t=e.previousNode();let s;return{prevNode:t,node:s=null===t?e.nextNode():e.nextNode()&&e.nextNode()}}checkIframeFilter(e,t,s,r){let i=!1,n=!1;return r.forEach((e,t)=>{e.val===s&&(i=t,n=e.handled)}),this.compareNodeIframe(e,t,s)?(!1!==i||n?!1===i||n||(r[i].handled=!0):r.push({val:s,handled:!0}),!0):(!1===i&&r.push({val:s,handled:!1}),!1)}handleOpenIframes(e,t,s,r){e.forEach(e=>{e.handled||this.getIframeContents(e.val,e=>{this.createInstanceOnIframe(e).forEachNode(t,s,r)})})}iterateThroughNodes(e,t,s,r,i){const n=this.createIterator(t,e,r);let o,a,h=[],c=[],l=()=>(({prevNode:a,node:o}=this.getIteratorNode(n)),o);for(;l();)this.iframes&&this.forEachIframe(t,e=>this.checkIframeFilter(o,a,e,h),t=>{this.createInstanceOnIframe(t).forEachNode(e,e=>c.push(e),r)}),c.push(o);c.forEach(e=>{s(e)}),this.iframes&&this.handleOpenIframes(h,e,s,r),i()}forEachNode(e,t,s,r=(()=>{})){const i=this.getContexts();let n=i.length;n||r(),i.forEach(i=>{const o=()=>{this.iterateThroughNodes(e,i,t,s,()=>{--n<=0&&r()})};this.iframes?this.waitForIframes(i,o):o()})}}class t{constructor(e){this.opt=Object.assign({},{diacritics:!0,synonyms:{},accuracy:"partially",caseSensitive:!1,ignoreJoiners:!1,ignorePunctuation:[],wildcards:"disabled"},e)}create(e){return"disabled"!==this.opt.wildcards&&(e=this.setupWildcardsRegExp(e)),e=this.escapeStr(e),Object.keys(this.opt.synonyms).length&&(e=this.createSynonymsRegExp(e)),(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.setupIgnoreJoinersRegExp(e)),this.opt.diacritics&&(e=this.createDiacriticsRegExp(e)),e=this.createMergedBlanksRegExp(e),(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.createJoinersRegExp(e)),"disabled"!==this.opt.wildcards&&(e=this.createWildcardsRegExp(e)),e=this.createAccuracyRegExp(e),new RegExp(e,`gm${this.opt.caseSensitive?"":"i"}`)}sortByLength(e){return e.sort((e,t)=>e.length===t.length?e>t?1:-1:t.length-e.length)}escapeStr(e){return e.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")}createSynonymsRegExp(e){const t=this.opt.synonyms,s=this.opt.caseSensitive?"":"i",r=this.opt.ignoreJoiners||this.opt.ignorePunctuation.length?"\0":"";for(let i in t)if(t.hasOwnProperty(i)){let n=Array.isArray(t[i])?t[i]:[t[i]];n.unshift(i),(n=this.sortByLength(n).map(e=>("disabled"!==this.opt.wildcards&&(e=this.setupWildcardsRegExp(e)),e=this.escapeStr(e))).filter(e=>""!==e)).length>1&&(e=e.replace(new RegExp(`(${n.map(e=>this.escapeStr(e)).join("|")})`,`gm${s}`),r+`(${n.map(e=>this.processSynonyms(e)).join("|")})`+r))}return e}processSynonyms(e){return(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.setupIgnoreJoinersRegExp(e)),e}setupWildcardsRegExp(e){return(e=e.replace(/(?:\\)*\?/g,e=>"\\"===e.charAt(0)?"?":"")).replace(/(?:\\)*\*/g,e=>"\\"===e.charAt(0)?"*":"")}createWildcardsRegExp(e){let t="withSpaces"===this.opt.wildcards;return e.replace(/\u0001/g,t?"[\\S\\s]?":"\\S?").replace(/\u0002/g,t?"[\\S\\s]*?":"\\S*")}setupIgnoreJoinersRegExp(e){return e.replace(/[^(|)\\]/g,(e,t,s)=>{let r=s.charAt(t+1);return/[(|)\\]/.test(r)||""===r?e:e+"\0"})}createJoinersRegExp(e){let t=[];const s=this.opt.ignorePunctuation;return Array.isArray(s)&&s.length&&t.push(this.escapeStr(s.join(""))),this.opt.ignoreJoiners&&t.push("\\u00ad\\u200b\\u200c\\u200d"),t.length?e.split(/\u0000+/).join(`[${t.join("")}]*`):e}createDiacriticsRegExp(e){const t=this.opt.caseSensitive?"":"i",s=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 r=[];return e.split("").forEach(i=>{s.every(s=>{if(-1!==s.indexOf(i)){if(r.indexOf(s)>-1)return!1;e=e.replace(new RegExp(`[${s}]`,`gm${t}`),`[${s}]`),r.push(s)}return!0})}),e}createMergedBlanksRegExp(e){return e.replace(/[\s]+/gim,"[\\s]+")}createAccuracyRegExp(e){let t=this.opt.accuracy,s="string"==typeof t?t:t.value,r="string"==typeof t?[]:t.limiters,i="";switch(r.forEach(e=>{i+=`|${this.escapeStr(e)}`}),s){case"partially":default:return`()(${e})`;case"complementary":return`()([^${i="\\s"+(i||this.escapeStr("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~¡¿"))}]*${e}[^${i}]*)`;case"exactly":return`(^|\\s${i})(${e})(?=$|\\s${i})`}}}class s{constructor(e){this.ctx=e,this.ie=!1;const t=window.navigator.userAgent;(t.indexOf("MSIE")>-1||t.indexOf("Trident")>-1)&&(this.ie=!0)}set opt(e){this._opt=Object.assign({},{element:"",className:"",exclude:[],iframes:!1,iframesTimeout:5e3,separateWordSearch:!0,acrossElements:!1,ignoreGroups:0,each:()=>{},noMatch:()=>{},filter:()=>!0,done:()=>{},debug:!1,log:window.console},e)}get opt(){return this._opt}get iterator(){return new e(this.ctx,this.opt.iframes,this.opt.exclude,this.opt.iframesTimeout)}log(e,t="debug"){const s=this.opt.log;this.opt.debug&&"object"==typeof s&&"function"==typeof s[t]&&s[t](`mark.js: ${e}`)}getSeparatedKeywords(e){let t=[];return e.forEach(e=>{this.opt.separateWordSearch?e.split(" ").forEach(e=>{e.trim()&&-1===t.indexOf(e)&&t.push(e)}):e.trim()&&-1===t.indexOf(e)&&t.push(e)}),{keywords:t.sort((e,t)=>t.length-e.length),length:t.length}}isNumeric(e){return Number(parseFloat(e))==e}checkRanges(e){if(!Array.isArray(e)||"[object Object]"!==Object.prototype.toString.call(e[0]))return this.log("markRanges() will only accept an array of objects"),this.opt.noMatch(e),[];const t=[];let s=0;return e.sort((e,t)=>e.start-t.start).forEach(e=>{let{start:r,end:i,valid:n}=this.callNoMatchOnInvalidRanges(e,s);n&&(e.start=r,e.length=i-r,t.push(e),s=i)}),t}callNoMatchOnInvalidRanges(e,t){let s,r,i=!1;return e&&void 0!==e.start?(r=(s=parseInt(e.start,10))+parseInt(e.length,10),this.isNumeric(e.start)&&this.isNumeric(e.length)&&r-t>0&&r-s>0?i=!0:(this.log("Ignoring invalid or overlapping range: "+`${JSON.stringify(e)}`),this.opt.noMatch(e))):(this.log(`Ignoring invalid range: ${JSON.stringify(e)}`),this.opt.noMatch(e)),{start:s,end:r,valid:i}}checkWhitespaceRanges(e,t,s){let r,i=!0,n=s.length,o=t-n,a=parseInt(e.start,10)-o;return(r=(a=a>n?n:a)+parseInt(e.length,10))>n&&(r=n,this.log(`End range automatically set to the max value of ${n}`)),a<0||r-a<0||a>n||r>n?(i=!1,this.log(`Invalid range: ${JSON.stringify(e)}`),this.opt.noMatch(e)):""===s.substring(a,r).replace(/\s+/g,"")&&(i=!1,this.log("Skipping whitespace only range: "+JSON.stringify(e)),this.opt.noMatch(e)),{start:a,end:r,valid:i}}getTextNodes(e){let t="",s=[];this.iterator.forEachNode(NodeFilter.SHOW_TEXT,e=>{s.push({start:t.length,end:(t+=e.textContent).length,node:e})},e=>this.matchesExclude(e.parentNode)?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT,()=>{e({value:t,nodes:s})})}matchesExclude(t){return e.matches(t,this.opt.exclude.concat(["script","style","title","head","html"]))}wrapRangeInTextNode(e,t,s){const r=this.opt.element?this.opt.element:"mark",i=e.splitText(t),n=i.splitText(s-t);let o=document.createElement(r);return o.setAttribute("data-markjs","true"),this.opt.className&&o.setAttribute("class",this.opt.className),o.textContent=i.textContent,i.parentNode.replaceChild(o,i),n}wrapRangeInMappedTextNode(e,t,s,r,i){e.nodes.every((n,o)=>{const a=e.nodes[o+1];if(void 0===a||a.start>t){if(!r(n.node))return!1;const a=t-n.start,h=(s>n.end?n.end:s)-n.start,c=e.value.substr(0,n.start),l=e.value.substr(h+n.start);if(n.node=this.wrapRangeInTextNode(n.node,a,h),e.value=c+l,e.nodes.forEach((t,s)=>{s>=o&&(e.nodes[s].start>0&&s!==o&&(e.nodes[s].start-=h),e.nodes[s].end-=h)}),s-=h,i(n.node.previousSibling,n.start),!(s>n.end))return!1;t=n.end}return!0})}wrapGroups(e,t,s,r){return r((e=this.wrapRangeInTextNode(e,t,t+s)).previousSibling),e}separateGroups(e,t,s,r,i){let n=t.length;for(let s=1;s