diff options
Diffstat (limited to 'ext/js/display/query-parser.js')
-rw-r--r-- | ext/js/display/query-parser.js | 232 |
1 files changed, 232 insertions, 0 deletions
diff --git a/ext/js/display/query-parser.js b/ext/js/display/query-parser.js new file mode 100644 index 00000000..05ebfa27 --- /dev/null +++ b/ext/js/display/query-parser.js @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2019-2021 Yomichan Authors + * + * 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 <https://www.gnu.org/licenses/>. + */ + +/* global + * TextScanner + * api + */ + +class QueryParser extends EventDispatcher { + constructor({getSearchContext, documentUtil}) { + super(); + this._getSearchContext = getSearchContext; + this._documentUtil = documentUtil; + this._text = ''; + this._setTextToken = null; + this._selectedParser = null; + this._parseResults = []; + this._queryParser = document.querySelector('#query-parser-content'); + this._queryParserModeContainer = document.querySelector('#query-parser-mode-container'); + this._queryParserModeSelect = document.querySelector('#query-parser-mode-select'); + this._textScanner = new TextScanner({ + node: this._queryParser, + getSearchContext, + documentUtil, + searchTerms: true, + searchKanji: false, + searchOnClick: true + }); + } + + get text() { + return this._text; + } + + prepare() { + this._textScanner.prepare(); + this._textScanner.on('searched', this._onSearched.bind(this)); + this._queryParserModeSelect.addEventListener('change', this._onParserChange.bind(this), false); + } + + setOptions({selectedParser, termSpacing, scanning}) { + let selectedParserChanged = false; + if (selectedParser === null || typeof selectedParser === 'string') { + selectedParserChanged = (this._selectedParser !== selectedParser); + this._selectedParser = selectedParser; + } + if (typeof termSpacing === 'boolean') { + this._queryParser.dataset.termSpacing = `${termSpacing}`; + } + if (scanning !== null && typeof scanning === 'object') { + this._textScanner.setOptions(scanning); + } + this._textScanner.setEnabled(true); + if (selectedParserChanged && this._parseResults.length > 0) { + this._renderParseResult(); + } + } + + async setText(text) { + this._text = text; + this._setPreview(text); + + const token = {}; + this._setTextToken = token; + this._parseResults = await api.textParse(text, this._getOptionsContext()); + if (this._setTextToken !== token) { return; } + + this._refreshSelectedParser(); + + this._renderParserSelect(); + this._renderParseResult(); + } + + // Private + + _onSearched(e) { + const {error} = e; + if (error !== null) { + yomichan.logError(error); + return; + } + if (e.type === null) { return; } + + this.trigger('searched', e); + } + + _onParserChange(e) { + const value = e.currentTarget.value; + this._setSelectedParser(value); + } + + _getOptionsContext() { + return this._getSearchContext().optionsContext; + } + + _refreshSelectedParser() { + if (this._parseResults.length > 0 && !this._getParseResult()) { + const value = this._parseResults[0].id; + this._setSelectedParser(value); + } + } + + _setSelectedParser(value) { + const optionsContext = this._getOptionsContext(); + api.modifySettings([{ + action: 'set', + path: 'parsing.selectedParser', + value, + scope: 'profile', + optionsContext + }], 'search'); + } + + _getParseResult() { + const selectedParser = this._selectedParser; + return this._parseResults.find((r) => r.id === selectedParser); + } + + _setPreview(text) { + const terms = [[{text, reading: ''}]]; + this._queryParser.textContent = ''; + this._queryParser.appendChild(this._createParseResult(terms, true)); + } + + _renderParserSelect() { + const visible = (this._parseResults.length > 1); + if (visible) { + this._updateParserModeSelect(this._queryParserModeSelect, this._parseResults, this._selectedParser); + } + this._queryParserModeContainer.hidden = !visible; + } + + _renderParseResult() { + const parseResult = this._getParseResult(); + this._queryParser.textContent = ''; + if (!parseResult) { return; } + this._queryParser.appendChild(this._createParseResult(parseResult.content, false)); + } + + _updateParserModeSelect(select, parseResults, selectedParser) { + const fragment = document.createDocumentFragment(); + + let index = 0; + let selectedIndex = -1; + for (const parseResult of parseResults) { + const option = document.createElement('option'); + option.value = parseResult.id; + switch (parseResult.source) { + case 'scanning-parser': + option.textContent = 'Scanning parser'; + break; + case 'mecab': + option.textContent = `MeCab: ${parseResult.dictionary}`; + break; + default: + option.textContent = `Unknown source: ${parseResult.source}`; + break; + } + fragment.appendChild(option); + + if (selectedParser === parseResult.id) { + selectedIndex = index; + } + ++index; + } + + select.textContent = ''; + select.appendChild(fragment); + select.selectedIndex = selectedIndex; + } + + _createParseResult(terms, preview) { + const type = preview ? 'preview' : 'normal'; + const fragment = document.createDocumentFragment(); + for (const term of terms) { + const termNode = document.createElement('span'); + termNode.className = 'query-parser-term'; + termNode.dataset.type = type; + for (const segment of term) { + if (segment.reading.trim().length === 0) { + this._addSegmentText(segment.text, termNode); + } else { + termNode.appendChild(this._createSegment(segment)); + } + } + fragment.appendChild(termNode); + } + return fragment; + } + + _createSegment(segment) { + const segmentNode = document.createElement('ruby'); + segmentNode.className = 'query-parser-segment'; + + const textNode = document.createElement('span'); + textNode.className = 'query-parser-segment-text'; + + const readingNode = document.createElement('rt'); + readingNode.className = 'query-parser-segment-reading'; + + segmentNode.appendChild(textNode); + segmentNode.appendChild(readingNode); + + this._addSegmentText(segment.text, textNode); + readingNode.textContent = segment.reading; + + return segmentNode; + } + + _addSegmentText(text, container) { + for (const character of text) { + const node = document.createElement('span'); + node.className = 'query-parser-char'; + node.textContent = character; + container.appendChild(node); + } + } +} |