app.views.SearchScope = class SearchScope extends app.View { static SEARCH_PARAM = app.config.search_param; static elements = { input: "._search-input", tag: "._search-tag", }; static events = { click: "onClick", keydown: "onKeydown", textInput: "onTextInput", }; static routes = { after: "afterRoute" }; static HASH_RGX = new RegExp(`^#${SearchScope.SEARCH_PARAM}=(.+?) .`); init() { this.placeholder = this.input.getAttribute("placeholder"); this.searcher = new app.SynchronousSearcher({ fuzzy_min_length: 2, max_results: 1, }); this.searcher.on("results", (results) => this.onResults(results)); } getScope() { return this.doc || app; } isActive() { return !!this.doc; } name() { return this.doc?.name; } search(value, searchDisabled) { if (searchDisabled == null) { searchDisabled = false; } if (this.doc) { return; } this.searcher.find(app.docs.all(), "text", value); if (!this.doc && searchDisabled) { this.searcher.find(app.disabledDocs.all(), "text", value); } } searchUrl() { let value; if ((value = this.extractHashValue())) { this.search(value, true); } } onResults(results) { let doc; if (!(doc = results[0])) { return; } if (app.docs.contains(doc)) { this.selectDoc(doc); } else { this.redirectToDoc(doc); } } selectDoc(doc) { const previousDoc = this.doc; if (doc === previousDoc) { return; } this.doc = doc; this.tag.textContent = doc.fullName; this.tag.style.display = "block"; this.input.removeAttribute("placeholder"); this.input.value = this.input.value.slice(this.input.selectionStart); this.input.style.paddingLeft = this.tag.offsetWidth + 10 + "px"; $.trigger(this.input, "input"); this.trigger("change", this.doc, previousDoc); } redirectToDoc(doc) { const { hash } = location; app.router.replaceHash(""); location.assign(doc.fullPath() + hash); } reset() { if (!this.doc) { return; } const previousDoc = this.doc; this.doc = null; this.tag.textContent = ""; this.tag.style.display = "none"; this.input.setAttribute("placeholder", this.placeholder); this.input.style.paddingLeft = ""; this.trigger("change", null, previousDoc); } doScopeSearch(event) { this.search(this.input.value.slice(0, this.input.selectionStart)); if (this.doc) { $.stopEvent(event); } } onClick(event) { if (event.target === this.tag) { this.reset(); $.stopEvent(event); } } onKeydown(event) { if (event.which === 8) { // backspace if (this.doc && this.input.selectionEnd === 0) { this.reset(); $.stopEvent(event); } } else if (!this.doc && this.input.value && !$.isChromeForAndroid()) { if (event.ctrlKey || event.metaKey || event.altKey || event.shiftKey) { return; } if ( event.which === 9 || // tab (event.which === 32 && app.isMobile()) ) { // space this.doScopeSearch(event); } } } onTextInput(event) { if (!$.isChromeForAndroid()) { return; } if (!this.doc && this.input.value && event.data === " ") { this.doScopeSearch(event); } } extractHashValue() { let value; if ((value = this.getHashValue())) { const newHash = $.urlDecode(location.hash).replace( `#${SearchScope.SEARCH_PARAM}=${value} `, `#${SearchScope.SEARCH_PARAM}=`, ); app.router.replaceHash(newHash); return value; } } getHashValue() { try { return SearchScope.HASH_RGX.exec($.urlDecode(location.hash))?.[1]; } catch (error) {} } afterRoute(name, context) { if (!app.isSingleDoc() && context.init && context.doc) { this.selectDoc(context.doc); } } };