diff --git a/libraries/jquery.mark.es6.min.js b/libraries/jquery.mark.es6.min.js new file mode 100644 index 000000000..75b82da87 --- /dev/null +++ b/libraries/jquery.mark.es6.min.js @@ -0,0 +1,7 @@ +/*!*************************************************** +* 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(require("jquery")):"function"==typeof define&&define.amd?define(["jquery"],t):e.Mark=t(e.jQuery)}(this,function(e){"use strict";class t{constructor(e,t=!0,r=[],s=5e3){this.ctx=e,this.iframes=t,this.exclude=r,this.iframesTimeout=s}static matches(e,t){const r="string"==typeof t?[t]:t,s=e.matches||e.matchesSelector||e.msMatchesSelector||e.mozMatchesSelector||e.oMatchesSelector||e.webkitMatchesSelector;if(s){let t=!1;return r.every(r=>!s.call(e,r)||(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 r=t.filter(t=>t.contains(e)).length>0;-1!==t.indexOf(e)||r||t.push(e)}),t}getIframeContents(e,t,r=(()=>{})){let s;try{const t=e.contentWindow;if(s=t.document,!t||!s)throw new Error("iframe inaccessible")}catch(e){r()}s&&t(s)}isIframeBlank(e){const t="about:blank",r=e.getAttribute("src").trim();return e.contentWindow.location.href===t&&r!==t&&r}observeIframeLoad(e,t,r){let s=!1,i=null;const n=()=>{if(!s){s=!0,clearTimeout(i);try{this.isIframeBlank(e)||(e.removeEventListener("load",n),this.getIframeContents(e,t,r))}catch(e){r()}}};e.addEventListener("load",n),i=setTimeout(n,this.iframesTimeout)}onIframeReady(e,t,r){try{"complete"===e.contentWindow.document.readyState?this.isIframeBlank(e)?this.observeIframeLoad(e,t,r):this.getIframeContents(e,t,r):this.observeIframeLoad(e,t,r)}catch(e){r()}}waitForIframes(e,t){let r=0;this.forEachIframe(e,()=>!0,e=>{r++,this.waitForIframes(e.querySelector("html"),()=>{--r||t()})},e=>{e||t()})}forEachIframe(e,r,s,i=(()=>{})){let n=e.querySelectorAll("iframe"),o=n.length,a=0;n=Array.prototype.slice.call(n);const h=()=>{--o<=0&&i(a)};o||h(),n.forEach(e=>{t.matches(e,this.exclude)?h():this.onIframeReady(e,t=>{r(e)&&(a++,s(t)),h()},h)})}createIterator(e,t,r){return document.createNodeIterator(e,t,r,!1)}createInstanceOnIframe(e){return new t(e.querySelector("html"),this.iframes)}compareNodeIframe(e,t,r){if(e.compareDocumentPosition(r)&Node.DOCUMENT_POSITION_PRECEDING){if(null===t)return!0;if(t.compareDocumentPosition(r)&Node.DOCUMENT_POSITION_FOLLOWING)return!0}return!1}getIteratorNode(e){const t=e.previousNode();let r;return{prevNode:t,node:r=null===t?e.nextNode():e.nextNode()&&e.nextNode()}}checkIframeFilter(e,t,r,s){let i=!1,n=!1;return s.forEach((e,t)=>{e.val===r&&(i=t,n=e.handled)}),this.compareNodeIframe(e,t,r)?(!1!==i||n?!1===i||n||(s[i].handled=!0):s.push({val:r,handled:!0}),!0):(!1===i&&s.push({val:r,handled:!1}),!1)}handleOpenIframes(e,t,r,s){e.forEach(e=>{e.handled||this.getIframeContents(e.val,e=>{this.createInstanceOnIframe(e).forEachNode(t,r,s)})})}iterateThroughNodes(e,t,r,s,i){const n=this.createIterator(t,e,s);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),s)}),c.push(o);c.forEach(e=>{r(e)}),this.iframes&&this.handleOpenIframes(h,e,r,s),i()}forEachNode(e,t,r,s=(()=>{})){const i=this.getContexts();let n=i.length;n||s(),i.forEach(i=>{const o=()=>{this.iterateThroughNodes(e,i,t,r,()=>{--n<=0&&s()})};this.iframes?this.waitForIframes(i,o):o()})}}class r{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,r=this.opt.caseSensitive?"":"i",s=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${r}`),s+`(${n.map(e=>this.processSynonyms(e)).join("|")})`+s))}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,r)=>{let s=r.charAt(t+1);return/[(|)\\]/.test(s)||""===s?e:e+"\0"})}createJoinersRegExp(e){let t=[];const r=this.opt.ignorePunctuation;return Array.isArray(r)&&r.length&&t.push(this.escapeStr(r.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",r=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 s=[];return e.split("").forEach(i=>{r.every(r=>{if(-1!==r.indexOf(i)){if(s.indexOf(r)>-1)return!1;e=e.replace(new RegExp(`[${r}]`,`gm${t}`),`[${r}]`),s.push(r)}return!0})}),e}createMergedBlanksRegExp(e){return e.replace(/[\s]+/gim,"[\\s]+")}createAccuracyRegExp(e){let t=this.opt.accuracy,r="string"==typeof t?t:t.value,s="string"==typeof t?[]:t.limiters,i="";switch(s.forEach(e=>{i+=`|${this.escapeStr(e)}`}),r){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 t(this.ctx,this.opt.iframes,this.opt.exclude,this.opt.iframesTimeout)}log(e,t="debug"){const r=this.opt.log;this.opt.debug&&"object"==typeof r&&"function"==typeof r[t]&&r[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 r=0;return e.sort((e,t)=>e.start-t.start).forEach(e=>{let{start:s,end:i,valid:n}=this.callNoMatchOnInvalidRanges(e,r);n&&(e.start=s,e.length=i-s,t.push(e),r=i)}),t}callNoMatchOnInvalidRanges(e,t){let r,s,i=!1;return e&&void 0!==e.start?(s=(r=parseInt(e.start,10))+parseInt(e.length,10),this.isNumeric(e.start)&&this.isNumeric(e.length)&&s-t>0&&s-r>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:r,end:s,valid:i}}checkWhitespaceRanges(e,t,r){let s,i=!0,n=r.length,o=t-n,a=parseInt(e.start,10)-o;return(s=(a=a>n?n:a)+parseInt(e.length,10))>n&&(s=n,this.log(`End range automatically set to the max value of ${n}`)),a<0||s-a<0||a>n||s>n?(i=!1,this.log(`Invalid range: ${JSON.stringify(e)}`),this.opt.noMatch(e)):""===r.substring(a,s).replace(/\s+/g,"")&&(i=!1,this.log("Skipping whitespace only range: "+JSON.stringify(e)),this.opt.noMatch(e)),{start:a,end:s,valid:i}}getTextNodes(e){let t="",r=[];this.iterator.forEachNode(NodeFilter.SHOW_TEXT,e=>{r.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:r})})}matchesExclude(e){return t.matches(e,this.opt.exclude.concat(["script","style","title","head","html"]))}wrapRangeInTextNode(e,t,r){const s=this.opt.element?this.opt.element:"mark",i=e.splitText(t),n=i.splitText(r-t);let o=document.createElement(s);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,r,s,i){e.nodes.every((n,o)=>{const a=e.nodes[o+1];if(void 0===a||a.start>t){if(!s(n.node))return!1;const a=t-n.start,h=(r>n.end?n.end:r)-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,r)=>{r>=o&&(e.nodes[r].start>0&&r!==o&&(e.nodes[r].start-=h),e.nodes[r].end-=h)}),r-=h,i(n.node.previousSibling,n.start),!(r>n.end))return!1;t=n.end}return!0})}wrapGroups(e,t,r,s){return s((e=this.wrapRangeInTextNode(e,t,t+r)).previousSibling),e}separateGroups(e,t,r,s,i){let n=t.length;for(let r=1;r-1&&s(t[r],e)&&(e=this.wrapGroups(e,n,t[r].length,i))}return e}wrapMatches(e,t,r,s,i){const n=0===t?0:t+1;this.getTextNodes(t=>{t.nodes.forEach(t=>{let i;for(t=t.node;null!==(i=e.exec(t.textContent))&&""!==i[n];){if(this.opt.separateGroups)t=this.separateGroups(t,i,n,r,s);else{if(!r(i[n],t))continue;let e=i.index;if(0!==n)for(let t=1;t{let o;for(;null!==(o=e.exec(t.value))&&""!==o[n];){let i=o.index;if(0!==n)for(let e=1;er(o[n],e),(t,r)=>{e.lastIndex=r,s(t)})}i()})}wrapRangeFromIndex(e,t,r,s){this.getTextNodes(i=>{const n=i.value.length;e.forEach((e,s)=>{let{start:o,end:a,valid:h}=this.checkWhitespaceRanges(e,n,i.value);h&&this.wrapRangeInMappedTextNode(i,o,a,r=>t(r,e,i.value.substring(o,a),s),t=>{r(t,e)})}),s()})}unwrapMatches(e){const t=e.parentNode;let r=document.createDocumentFragment();for(;e.firstChild;)r.appendChild(e.removeChild(e.firstChild));t.replaceChild(r,e),this.ie?this.normalizeTextNode(t):t.normalize()}normalizeTextNode(e){if(e){if(3===e.nodeType)for(;e.nextSibling&&3===e.nextSibling.nodeType;)e.nodeValue+=e.nextSibling.nodeValue,e.parentNode.removeChild(e.nextSibling);else this.normalizeTextNode(e.firstChild);this.normalizeTextNode(e.nextSibling)}}markRegExp(e,t){this.opt=t,this.log(`Searching with expression "${e}"`);let r=0,s="wrapMatches";this.opt.acrossElements&&(s="wrapMatchesAcrossElements"),this[s](e,this.opt.ignoreGroups,(e,t)=>this.opt.filter(t,e,r),e=>{r++,this.opt.each(e)},()=>{0===r&&this.opt.noMatch(e),this.opt.done(r)})}mark(e,t){this.opt=t;let s=0,i="wrapMatches";const{keywords:n,length:o}=this.getSeparatedKeywords("string"==typeof e?[e]:e),a=e=>{const t=new r(this.opt).create(e);let h=0;this.log(`Searching with expression "${t}"`),this[i](t,1,(t,r)=>this.opt.filter(r,e,s,h),e=>{h++,s++,this.opt.each(e)},()=>{0===h&&this.opt.noMatch(e),n[o-1]===e?this.opt.done(s):a(n[n.indexOf(e)+1])})};this.opt.acrossElements&&(i="wrapMatchesAcrossElements"),0===o?this.opt.done(s):a(n[0])}markRanges(e,t){this.opt=t;let r=0,s=this.checkRanges(e);s&&s.length?(this.log("Starting to mark with the following ranges: "+JSON.stringify(s)),this.wrapRangeFromIndex(s,(e,t,r,s)=>this.opt.filter(e,t,r,s),(e,t)=>{r++,this.opt.each(e,t)},()=>{this.opt.done(r)})):this.opt.done(r)}unmark(e){this.opt=e;let r=this.opt.element?this.opt.element:"*";r+="[data-markjs]",this.opt.className&&(r+=`.${this.opt.className}`),this.log(`Removal selector "${r}"`),this.iterator.forEachNode(NodeFilter.SHOW_ELEMENT,e=>{this.unwrapMatches(e)},e=>{const s=t.matches(e,r),i=this.matchesExclude(e);return!s||i?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT},this.opt.done)}}return(e=e&&e.hasOwnProperty("default")?e.default:e).fn.mark=function(e,t){return new s(this.get()).mark(e,t),this},e.fn.markRegExp=function(e,t){return new s(this.get()).markRegExp(e,t),this},e.fn.markRanges=function(e,t){return new s(this.get()).markRanges(e,t),this},e.fn.unmark=function(e){return new s(this.get()).unmark(e),this},e}); diff --git a/src/public/app/dialogs/markdown_import.js b/src/public/app/dialogs/markdown_import.js index 94712b28e..285f6808e 100644 --- a/src/public/app/dialogs/markdown_import.js +++ b/src/public/app/dialogs/markdown_import.js @@ -16,7 +16,7 @@ async function convertMarkdownToHtml(text) { const result = writer.render(parsed); - appContext.triggerCommand('executeInTextEditor', { + appContext.triggerCommand('executeWithTextEditor', { callback: textEditor => { const viewFragment = textEditor.data.processor.toView(result); const modelFragment = textEditor.data.toModel(viewFragment); diff --git a/src/public/app/services/library_loader.js b/src/public/app/services/library_loader.js index a80ac1cbf..7dbca8e18 100644 --- a/src/public/app/services/library_loader.js +++ b/src/public/app/services/library_loader.js @@ -61,10 +61,13 @@ const EXCALIDRAW = { "node_modules/react/umd/react.production.min.js", "node_modules/react-dom/umd/react-dom.production.min.js", "node_modules/@excalidraw/excalidraw/dist/excalidraw.production.min.js", - ], - // css: [ - // "stylesheets/somestyle.css" - // ] + ] +}; + +const MARKJS = { + js: [ + "libraries/jquery.mark.es6.min.js" + ] }; async function requireLibrary(library) { @@ -118,5 +121,6 @@ export default { WHEEL_ZOOM, FORCE_GRAPH, MERMAID, - EXCALIDRAW + EXCALIDRAW, + MARKJS } diff --git a/src/public/app/services/note_context.js b/src/public/app/services/note_context.js index 179fd1aea..fe78c0201 100644 --- a/src/public/app/services/note_context.js +++ b/src/public/app/services/note_context.js @@ -228,7 +228,7 @@ class NoteContext extends Component { } async getTextEditor(callback) { - return new Promise(resolve => appContext.triggerCommand('executeInTextEditor', { + return new Promise(resolve => appContext.triggerCommand('executeWithTextEditor', { callback, resolve, ntxId: this.ntxId @@ -236,7 +236,14 @@ class NoteContext extends Component { } async getCodeEditor() { - return new Promise(resolve => appContext.triggerCommand('executeInCodeEditor', { + return new Promise(resolve => appContext.triggerCommand('executeWithCodeEditor', { + resolve, + ntxId: this.ntxId + })); + } + + async getContentElement() { + return new Promise(resolve => appContext.triggerCommand('executeWithContentElement', { resolve, ntxId: this.ntxId })); diff --git a/src/public/app/services/utils.js b/src/public/app/services/utils.js index 9facea6f8..6c598e4fe 100644 --- a/src/public/app/services/utils.js +++ b/src/public/app/services/utils.js @@ -365,6 +365,10 @@ function sleep(time_ms) { }); } +function escapeRegExp(str) { + return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); +} + export default { reloadFrontendApp, parseDate, @@ -410,4 +414,5 @@ export default { filterAttributeName, isValidAttributeName, sleep, + escapeRegExp }; diff --git a/src/public/app/widgets/find.js b/src/public/app/widgets/find.js index c95db043c..aa873e4c5 100644 --- a/src/public/app/widgets/find.js +++ b/src/public/app/widgets/find.js @@ -6,6 +6,7 @@ import NoteContextAwareWidget from "./note_context_aware_widget.js"; import FindInText from "./find_in_text.js"; import FindInCode from "./find_in_code.js"; +import FindInHtml from "./find_in_html.js"; const findWidgetDelayMillis = 200; const waitForEnter = (findWidgetDelayMillis < 0); @@ -86,6 +87,13 @@ export default class FindWidget extends NoteContextAwareWidget { this.textHandler = new FindInText(this); this.codeHandler = new FindInCode(this); + this.htmlHandler = new FindInHtml(this); + } + + async noteSwitched() { + await super.noteSwitched(); + + await this.closeSearch(); } doRender() { @@ -125,6 +133,42 @@ export default class FindWidget extends NoteContextAwareWidget { return this.$widget; } + async findInTextEvent() { + if (!this.isActiveNoteContext()) { + return; + } + + if (!['text', 'code'].includes(this.note.type) || !this.$findBox.is(":hidden")) { + return; + } + + const readOnly = await this.noteContext.isReadOnly(); + + if (readOnly) { + this.handler = this.htmlHandler; + } else { + this.handler = this.note.type === "code" + ? this.codeHandler + : this.textHandler; + } + + this.$findBox.show(); + this.$input.focus(); + this.$totalFound.text(0); + this.$currentFound.text(0); + + const searchTerm = await this.handler.getInitialSearchTerm(); + + this.$input.val(searchTerm || ""); + + // Directly perform the search if there's some text to + // find, without delaying or waiting for enter + if (searchTerm !== "") { + this.$input.select(); + await this.performFind(); + } + } + startSearch() { // XXX This should clear the previous search immediately in all cases // (the search is stale when waitforenter but also while the @@ -172,35 +216,6 @@ export default class FindWidget extends NoteContextAwareWidget { } } - async findInTextEvent() { - if (!this.isActiveNoteContext()) { - return; - } - - // Only writeable text and code supported - const readOnly = await this.noteContext.isReadOnly(); - - if (readOnly || !['text', 'code'].includes(this.note.type) || !this.$findBox.is(":hidden")) { - return; - } - - this.$findBox.show(); - this.$input.focus(); - this.$totalFound.text(0); - this.$currentFound.text(0); - - const searchTerm = await this.handler.getInitialSearchTerm(); - - this.$input.val(searchTerm || ""); - - // Directly perform the search if there's some text to - // find, without delaying or waiting for enter - if (searchTerm !== "") { - this.$input.select(); - await this.performFind(); - } - } - /** Perform the find and highlight the find results. */ async performFind() { const searchTerm = this.$input.val(); @@ -216,33 +231,23 @@ export default class FindWidget extends NoteContextAwareWidget { } async closeSearch() { - this.$findBox.hide(); + if (this.$findBox.is(":visible")) { + this.$findBox.hide(); - // Restore any state, if there's a current occurrence clear markers - // and scroll to and select the last occurrence - const totalFound = parseInt(this.$totalFound.text()); - const currentFound = parseInt(this.$currentFound.text()) - 1; + // Restore any state, if there's a current occurrence clear markers + // and scroll to and select the last occurrence + const totalFound = parseInt(this.$totalFound.text()); + const currentFound = parseInt(this.$currentFound.text()) - 1; - if (totalFound > 0) { - await this.handler.cleanup(totalFound, currentFound); - } + if (totalFound > 0) { + await this.handler.cleanup(totalFound, currentFound); + } - this.searchTerm = null; - } - - async entitiesReloadedEvent({loadResults}) { - if (loadResults.isNoteContentReloaded(this.noteId)) { - this.refresh(); + this.searchTerm = null; } } isEnabled() { return super.isEnabled() && ['text', 'code'].includes(this.note.type); } - - get handler() { - return this.note.type === "code" - ? this.codeHandler - : this.textHandler; - } } diff --git a/src/public/app/widgets/find_in_code.js b/src/public/app/widgets/find_in_code.js index 7a6bcbced..f3fecb044 100644 --- a/src/public/app/widgets/find_in_code.js +++ b/src/public/app/widgets/find_in_code.js @@ -1,13 +1,14 @@ // ck-find-result and ck-find-result_selected are the styles ck-editor // uses for highlighting matches, use the same one on CodeMirror // for consistency +import utils from "../services/utils.js"; + const FIND_RESULT_SELECTED_CSS_CLASSNAME = "ck-find-result_selected"; const FIND_RESULT_CSS_CLASSNAME = "ck-find-result"; -const escapeRegExp = str => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - export default class FindInCode { constructor(parent) { + /** @property {FindWidget} */ this.parent = parent; } @@ -53,7 +54,7 @@ export default class FindInCode { } if (searchTerm !== "") { - searchTerm = escapeRegExp(searchTerm); + searchTerm = utils.escapeRegExp(searchTerm); // Find and highlight matches // Find and highlight matches diff --git a/src/public/app/widgets/find_in_html.js b/src/public/app/widgets/find_in_html.js new file mode 100644 index 000000000..fbcaeb7fd --- /dev/null +++ b/src/public/app/widgets/find_in_html.js @@ -0,0 +1,36 @@ +// ck-find-result and ck-find-result_selected are the styles ck-editor +// uses for highlighting matches, use the same one on CodeMirror +// for consistency +import libraryLoader from "../services/library_loader.js"; +import utils from "../services/utils.js"; + +const FIND_RESULT_SELECTED_CSS_CLASSNAME = "ck-find-result_selected"; +const FIND_RESULT_CSS_CLASSNAME = "ck-find-result"; + +export default class FindInHtml { + constructor(parent) { + /** @property {FindWidget} */ + this.parent = parent; + } + + async getInitialSearchTerm() { + return ""; // FIXME + } + + async performFind(searchTerm, matchCase, wholeWord) { + await libraryLoader.requireLibrary(libraryLoader.MARKJS); + + const $content = await this.parent.noteContext.getContentElement(); + + $content.markRegExp(new RegExp(utils.escapeRegExp(searchTerm), "gi")); + } + + async findNext(direction, currentFound, nextFound) { + } + + async cleanup(totalFound, currentFound) { + } + + async close() { + } +} diff --git a/src/public/app/widgets/find_in_text.js b/src/public/app/widgets/find_in_text.js index 54f3bd091..6de137e3a 100644 --- a/src/public/app/widgets/find_in_text.js +++ b/src/public/app/widgets/find_in_text.js @@ -1,5 +1,6 @@ export default class FindInText { constructor(parent) { + /** @property {FindWidget} */ this.parent = parent; } diff --git a/src/public/app/widgets/type_widgets/editable_code.js b/src/public/app/widgets/type_widgets/editable_code.js index 2a4097f1c..593654cf9 100644 --- a/src/public/app/widgets/type_widgets/editable_code.js +++ b/src/public/app/widgets/type_widgets/editable_code.js @@ -171,7 +171,7 @@ export default class EditableCodeTypeWidget extends TypeWidget { } } - async executeInCodeEditorEvent({resolve, ntxId}) { + async executeWithCodeEditorEvent({resolve, ntxId}) { if (!this.isNoteContext(ntxId)) { return; } diff --git a/src/public/app/widgets/type_widgets/editable_text.js b/src/public/app/widgets/type_widgets/editable_text.js index f51138289..d86beb5d3 100644 --- a/src/public/app/widgets/type_widgets/editable_text.js +++ b/src/public/app/widgets/type_widgets/editable_text.js @@ -229,7 +229,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget { return !selection.isCollapsed; } - async executeInTextEditorEvent({callback, resolve, ntxId}) { + async executeWithTextEditorEvent({callback, resolve, ntxId}) { if (!this.isNoteContext(ntxId)) { return; } diff --git a/src/public/app/widgets/type_widgets/read_only_code.js b/src/public/app/widgets/type_widgets/read_only_code.js index 3efcd4a69..c7530f6db 100644 --- a/src/public/app/widgets/type_widgets/read_only_code.js +++ b/src/public/app/widgets/type_widgets/read_only_code.js @@ -30,4 +30,14 @@ export default class ReadOnlyCodeTypeWidget extends TypeWidget { this.$content.text(noteComplement.content); } + + async executeWithContentElementEvent({resolve, ntxId}) { + if (!this.isNoteContext(ntxId)) { + return; + } + + await this.initialized; + + resolve(this.$content); + } } diff --git a/src/public/app/widgets/type_widgets/read_only_text.js b/src/public/app/widgets/type_widgets/read_only_text.js index 060410dbc..2fa0b5e81 100644 --- a/src/public/app/widgets/type_widgets/read_only_text.js +++ b/src/public/app/widgets/type_widgets/read_only_text.js @@ -114,4 +114,14 @@ export default class ReadOnlyTextTypeWidget extends AbstractTextTypeWidget { async refreshIncludedNoteEvent({noteId}) { this.refreshIncludedNote(this.$content, noteId); } + + async executeWithContentElementEvent({resolve, ntxId}) { + if (!this.isNoteContext(ntxId)) { + return; + } + + await this.initialized; + + resolve(this.$content); + } }