diff options
Diffstat (limited to 'ext/bg/js/search-query-parser.js')
-rw-r--r-- | ext/bg/js/search-query-parser.js | 228 |
1 files changed, 228 insertions, 0 deletions
diff --git a/ext/bg/js/search-query-parser.js b/ext/bg/js/search-query-parser.js new file mode 100644 index 00000000..42e53989 --- /dev/null +++ b/ext/bg/js/search-query-parser.js @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2019 Alex Yatskov <alex@foosoft.net> + * Author: Alex Yatskov <alex@foosoft.net> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +class QueryParser { + constructor(search) { + this.search = search; + this.pendingLookup = false; + this.clickScanPrevent = false; + + this.parseResults = []; + this.selectedParser = null; + + this.queryParser = document.querySelector('#query-parser'); + this.queryParserSelect = document.querySelector('#query-parser-select'); + + this.queryParser.addEventListener('mousedown', (e) => this.onMouseDown(e)); + this.queryParser.addEventListener('mouseup', (e) => this.onMouseUp(e)); + } + + onError(error) { + logError(error, false); + } + + onMouseDown(e) { + if (Frontend.isMouseButton('primary', e)) { + this.clickScanPrevent = false; + } + } + + onMouseUp(e) { + if ( + this.search.options.scanning.clickGlossary && + !this.clickScanPrevent && + Frontend.isMouseButton('primary', e) + ) { + const selectText = this.search.options.scanning.selectText; + this.onTermLookup(e, {disableScroll: true, selectText}); + } + } + + onMouseMove(e) { + if (this.pendingLookup || Frontend.isMouseButton('primary', e)) { + return; + } + + const scanningOptions = this.search.options.scanning; + const scanningModifier = scanningOptions.modifier; + if (!( + Frontend.isScanningModifierPressed(scanningModifier, e) || + (scanningOptions.middleMouse && Frontend.isMouseButton('auxiliary', e)) + )) { + return; + } + + const selectText = this.search.options.scanning.selectText; + this.onTermLookup(e, {disableScroll: true, disableHistory: true, selectText}); + } + + onMouseLeave(e) { + this.clickScanPrevent = true; + clearTimeout(e.target.dataset.timer); + delete e.target.dataset.timer; + } + + onTermLookup(e, params) { + this.pendingLookup = true; + (async () => { + await this.search.onTermLookup(e, params); + this.pendingLookup = false; + })(); + } + + onParserChange(e) { + const selectedParser = e.target.value; + this.selectedParser = selectedParser; + apiOptionsSet({parsing: {selectedParser}}, this.search.getOptionsContext()); + this.renderParseResult(this.getParseResult()); + } + + refreshSelectedParser() { + if (this.parseResults.length > 0) { + if (this.selectedParser === null) { + this.selectedParser = this.search.options.parsing.selectedParser; + } + if (this.selectedParser === null || !this.getParseResult()) { + const selectedParser = this.parseResults[0].id; + this.selectedParser = selectedParser; + apiOptionsSet({parsing: {selectedParser}}, this.search.getOptionsContext()); + } + } + } + + getParseResult() { + return this.parseResults.find(r => r.id === this.selectedParser); + } + + async setText(text) { + this.search.setSpinnerVisible(true); + + await this.setPreview(text); + + this.parseResults = await this.parseText(text); + this.refreshSelectedParser(); + + this.renderParserSelect(); + await this.renderParseResult(); + + this.search.setSpinnerVisible(false); + } + + async parseText(text) { + const results = []; + if (this.search.options.parsing.enableScanningParser) { + results.push({ + name: 'Scanning parser', + id: 'scan', + parsedText: await apiTextParse(text, this.search.getOptionsContext()) + }); + } + if (this.search.options.parsing.enableMecabParser) { + let mecabResults = await apiTextParseMecab(text, this.search.getOptionsContext()); + for (const mecabDictName in mecabResults) { + results.push({ + name: `MeCab: ${mecabDictName}`, + id: `mecab-${mecabDictName}`, + parsedText: mecabResults[mecabDictName] + }); + } + } + return results; + } + + async setPreview(text) { + const previewTerms = []; + while (text.length > 0) { + const tempText = text.slice(0, 2); + previewTerms.push([{text: Array.from(tempText)}]); + text = text.slice(2); + } + this.queryParser.innerHTML = await apiTemplateRender('query-parser.html', { + terms: previewTerms, + preview: true + }); + + for (const charElement of this.queryParser.querySelectorAll('.query-parser-char')) { + this.activateScanning(charElement); + } + } + + renderParserSelect() { + this.queryParserSelect.innerHTML = ''; + if (this.parseResults.length > 1) { + const select = document.createElement('select'); + select.classList.add('form-control'); + for (const parseResult of this.parseResults) { + const option = document.createElement('option'); + option.value = parseResult.id; + option.innerText = parseResult.name; + option.defaultSelected = this.selectedParser === parseResult.id; + select.appendChild(option); + } + select.addEventListener('change', this.onParserChange.bind(this)); + this.queryParserSelect.appendChild(select); + } + } + + async renderParseResult() { + const parseResult = this.getParseResult(); + if (!parseResult) { + this.queryParser.innerHTML = ''; + return; + } + + this.queryParser.innerHTML = await apiTemplateRender( + 'query-parser.html', + {terms: QueryParser.processParseResultForDisplay(parseResult.parsedText)} + ); + + for (const charElement of this.queryParser.querySelectorAll('.query-parser-char')) { + this.activateScanning(charElement); + } + } + + activateScanning(element) { + element.addEventListener('mousemove', (e) => { + clearTimeout(e.target.dataset.timer); + if (this.search.options.scanning.modifier === 'none') { + e.target.dataset.timer = setTimeout(() => { + this.onMouseMove(e); + delete e.target.dataset.timer; + }, this.search.options.scanning.delay); + } else { + this.onMouseMove(e); + } + }); + element.addEventListener('mouseleave', (e) => { + this.onMouseLeave(e); + }); + } + + static processParseResultForDisplay(result) { + return result.map((term) => { + return term.filter(part => part.text.trim()).map((part) => { + return { + text: Array.from(part.text), + reading: part.reading, + raw: !part.reading || !part.reading.trim(), + }; + }); + }); + } +} |