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); +        } +    } +} |