diff options
author | siikamiika <siikamiika@users.noreply.github.com> | 2020-02-11 11:07:05 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-02-11 11:07:05 +0200 |
commit | 9ffd0cb441f9773651ed734d8bccce3667700ceb (patch) | |
tree | 1edfdff45596a70a0e117559fac391c83b96ca9a | |
parent | 17181eb97efcef6a4221397cfe6030b880792e32 (diff) | |
parent | 9c98c631afde843f8b5ed4c54fc1f4ff0ec3a00a (diff) |
Merge pull request #350 from siikamiika/query-parser-html-templates
query parser html templates
-rw-r--r-- | README.md | 10 | ||||
-rwxr-xr-x | build_tmpl.sh | 2 | ||||
-rwxr-xr-x | build_tmpl_auto.sh | 16 | ||||
-rw-r--r-- | ext/bg/background.html | 1 | ||||
-rw-r--r-- | ext/bg/js/api.js | 4 | ||||
-rw-r--r-- | ext/bg/js/backend.js | 6 | ||||
-rw-r--r-- | ext/bg/js/search-query-parser-generator.js | 77 | ||||
-rw-r--r-- | ext/bg/js/search-query-parser.js | 64 | ||||
-rw-r--r-- | ext/bg/js/search.js | 2 | ||||
-rw-r--r-- | ext/bg/js/templates.js | 55 | ||||
-rw-r--r-- | ext/bg/query-parser-templates.html | 11 | ||||
-rw-r--r-- | ext/bg/search.html | 7 | ||||
-rw-r--r-- | ext/bg/settings.html | 1 | ||||
-rw-r--r-- | ext/mixed/css/display.css | 4 | ||||
-rw-r--r-- | ext/mixed/js/api.js | 4 | ||||
-rw-r--r-- | ext/mixed/js/template-handler.js | 47 | ||||
-rw-r--r-- | tmpl/query-parser.html | 27 |
17 files changed, 178 insertions, 160 deletions
@@ -27,7 +27,6 @@ Yomichan provides advanced features not available in other browser-based diction * [Flashcard Creation](https://foosoft.net/projects/yomichan/#flashcard-creation) * [Keyboard Shortcuts](https://foosoft.net/projects/yomichan/#keyboard-shortcuts) * [Development](https://foosoft.net/projects/yomichan/#development) - * [Templates](https://foosoft.net/projects/yomichan/#templates) * [Dependencies](https://foosoft.net/projects/yomichan/#dependencies) * [Frequently Asked Questions](https://foosoft.net/projects/yomichan/#frequently-asked-questions) * [Screenshots](https://foosoft.net/projects/yomichan/#screenshots) @@ -241,15 +240,6 @@ following basic guidelines when creating pull requests: * Large pull requests without a clear scope will not be merged. * Incomplete or non-standalone features will not be merged. -### Templates ### - -Yomichan uses [Handlebars](https://handlebarsjs.com/) templates for user interface generation. The source templates are -found in the `tmpl` directory and the compiled version is stored in the `ext/bg/js/templates.js` file. If you modify the -source templates, you will need to also recompile them. If you are developing on Linux or Mac OS X, you can use the -included `build_tmpl.sh` and `build_tmpl_auto.sh` shell scripts to do this for you -([inotify-tools](https://github.com/rvoicilas/inotify-tools/wiki) required). Otherwise, simply execute `handlebars -tmpl/*.html -f ext/bg/js/templates.js` from the project's base directory to compile all the templates. - ### Dependencies ### Yomichan uses several third-party libraries to function. Below are links to homepages, snapshots, and licenses of the exact diff --git a/build_tmpl.sh b/build_tmpl.sh deleted file mode 100755 index e91f8de8..00000000 --- a/build_tmpl.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -handlebars tmpl/*.html -f ext/bg/js/templates.js diff --git a/build_tmpl_auto.sh b/build_tmpl_auto.sh deleted file mode 100755 index 98065cb7..00000000 --- a/build_tmpl_auto.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -DIRECTORY_TO_OBSERVE="tmpl" -BUILD_SCRIPT="build_tmpl.sh" - -function block_for_change { - inotifywait -e modify,move,create,delete $DIRECTORY_TO_OBSERVE -} - -function build { - bash $BUILD_SCRIPT -} - -build -while block_for_change; do - build -done diff --git a/ext/bg/background.html b/ext/bg/background.html index 11023221..7fd1c477 100644 --- a/ext/bg/background.html +++ b/ext/bg/background.html @@ -37,7 +37,6 @@ <script src="/bg/js/options.js"></script> <script src="/bg/js/profile-conditions.js"></script> <script src="/bg/js/request.js"></script> - <script src="/bg/js/templates.js"></script> <script src="/bg/js/translator.js"></script> <script src="/bg/js/util.js"></script> <script src="/mixed/js/audio.js"></script> diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js index f4be7a0c..cd6a9d18 100644 --- a/ext/bg/js/api.js +++ b/ext/bg/js/api.js @@ -33,6 +33,10 @@ function apiClipboardGet() { return _apiInvoke('clipboardGet'); } +function apiGetQueryParserTemplatesHtml() { + return _apiInvoke('getQueryParserTemplatesHtml'); +} + function _apiInvoke(action, params={}) { const data = {action, params}; return new Promise((resolve, reject) => { diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 668d1fb7..529055d2 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -567,6 +567,11 @@ class Backend { return await requestText(url, 'GET'); } + async _onApiGetQueryParserTemplatesHtml() { + const url = chrome.runtime.getURL('/bg/query-parser-templates.html'); + return await requestText(url, 'GET'); + } + _onApiGetZoom(params, sender) { if (!sender || !sender.tab) { return Promise.reject(new Error('Invalid tab')); @@ -854,6 +859,7 @@ Backend._messageHandlers = new Map([ ['getEnvironmentInfo', (self, ...args) => self._onApiGetEnvironmentInfo(...args)], ['clipboardGet', (self, ...args) => self._onApiClipboardGet(...args)], ['getDisplayTemplatesHtml', (self, ...args) => self._onApiGetDisplayTemplatesHtml(...args)], + ['getQueryParserTemplatesHtml', (self, ...args) => self._onApiGetQueryParserTemplatesHtml(...args)], ['getZoom', (self, ...args) => self._onApiGetZoom(...args)] ]); diff --git a/ext/bg/js/search-query-parser-generator.js b/ext/bg/js/search-query-parser-generator.js new file mode 100644 index 00000000..8d71890b --- /dev/null +++ b/ext/bg/js/search-query-parser-generator.js @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2020 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 <https://www.gnu.org/licenses/>. + */ + + +class QueryParserGenerator { + constructor() { + this._templateHandler = null; + } + + async prepare() { + const html = await apiGetQueryParserTemplatesHtml(); + this._templateHandler = new TemplateHandler(html); + } + + createParseResult(terms, preview=false) { + const fragment = document.createDocumentFragment(); + for (const term of terms) { + const termContainer = this._templateHandler.instantiate(preview ? 'term-preview' : 'term'); + for (const segment of term) { + if (!segment.text.trim()) { continue; } + if (!segment.reading || !segment.reading.trim()) { + termContainer.appendChild(this.createSegmentText(segment.text)); + } else { + termContainer.appendChild(this.createSegment(segment)); + } + } + fragment.appendChild(termContainer); + } + return fragment; + } + + createSegment(segment) { + const segmentContainer = this._templateHandler.instantiate('segment'); + const segmentTextContainer = segmentContainer.querySelector('.query-parser-segment-text'); + const segmentReadingContainer = segmentContainer.querySelector('.query-parser-segment-reading'); + segmentTextContainer.appendChild(this.createSegmentText(segment.text)); + segmentReadingContainer.innerText = segment.reading; + return segmentContainer; + } + + createSegmentText(text) { + const fragment = document.createDocumentFragment(); + for (const chr of text) { + const charContainer = this._templateHandler.instantiate('char'); + charContainer.innerText = chr; + fragment.appendChild(charContainer); + } + return fragment; + } + + createParserSelect(parseResults, selectedParser) { + const selectContainer = this._templateHandler.instantiate('select'); + for (const parseResult of parseResults) { + const optionContainer = this._templateHandler.instantiate('select-option'); + optionContainer.value = parseResult.id; + optionContainer.innerText = parseResult.name; + optionContainer.defaultSelected = selectedParser === parseResult.id; + selectContainer.appendChild(optionContainer); + } + return selectContainer; + } +} diff --git a/ext/bg/js/search-query-parser.js b/ext/bg/js/search-query-parser.js index e8e6d11f..f648fdd4 100644 --- a/ext/bg/js/search-query-parser.js +++ b/ext/bg/js/search-query-parser.js @@ -19,14 +19,20 @@ class QueryParser extends TextScanner { constructor(search) { - super(document.querySelector('#query-parser'), [], [], []); + super(document.querySelector('#query-parser-content'), [], [], []); this.search = search; this.parseResults = []; this.selectedParser = null; - this.queryParser = document.querySelector('#query-parser'); - this.queryParserSelect = document.querySelector('#query-parser-select'); + this.queryParser = document.querySelector('#query-parser-content'); + this.queryParserSelect = document.querySelector('#query-parser-select-container'); + + this.queryParserGenerator = new QueryParserGenerator(); + } + + async prepare() { + await this.queryParserGenerator.prepare(); } onError(error) { @@ -64,7 +70,7 @@ class QueryParser extends TextScanner { const selectedParser = e.target.value; this.selectedParser = selectedParser; apiOptionsSet({parsing: {selectedParser}}, this.search.getOptionsContext()); - this.renderParseResult(this.getParseResult()); + this.renderParseResult(); } getMouseEventListeners() { @@ -113,13 +119,13 @@ class QueryParser extends TextScanner { async setText(text) { this.search.setSpinnerVisible(true); - await this.setPreview(text); + this.setPreview(text); this.parseResults = await this.parseText(text); this.refreshSelectedParser(); this.renderParserSelect(); - await this.renderParseResult(); + this.renderParseResult(); this.search.setSpinnerVisible(false); } @@ -146,57 +152,29 @@ class QueryParser extends TextScanner { return results; } - async setPreview(text) { + setPreview(text) { const previewTerms = []; for (let i = 0, ii = text.length; i < ii; i += 2) { const tempText = text.substring(i, i + 2); - previewTerms.push([{text: tempText.split('')}]); + previewTerms.push([{text: tempText}]); } - this.queryParser.innerHTML = await apiTemplateRender('query-parser.html', { - terms: previewTerms, - preview: true - }); + this.queryParser.textContent = ''; + this.queryParser.appendChild(this.queryParserGenerator.createParseResult(previewTerms, true)); } 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); - } + const select = this.queryParserGenerator.createParserSelect(this.parseResults, this.selectedParser); select.addEventListener('change', this.onParserChange.bind(this)); this.queryParserSelect.appendChild(select); } } - async renderParseResult() { + renderParseResult() { const parseResult = this.getParseResult(); - if (!parseResult) { - this.queryParser.innerHTML = ''; - return; - } - - this.queryParser.innerHTML = await apiTemplateRender( - 'query-parser.html', - {terms: QueryParser.processParseResultForDisplay(parseResult.parsedText)} - ); - } - - static processParseResultForDisplay(result) { - return result.map((term) => { - return term.filter((part) => part.text.trim()).map((part) => { - return { - text: part.text.split(''), - reading: part.reading, - raw: !part.reading || !part.reading.trim() - }; - }); - }); + this.queryParser.textContent = ''; + if (!parseResult) { return; } + this.queryParser.appendChild(this.queryParserGenerator.createParseResult(parseResult.parsedText)); } } diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index 4da27513..6641255f 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -49,6 +49,8 @@ class DisplaySearch extends Display { try { await this.initialize(); + await this.queryParser.prepare(); + const {queryParams: {query='', mode=''}} = parseUrl(window.location.href); if (this.search !== null) { diff --git a/ext/bg/js/templates.js b/ext/bg/js/templates.js deleted file mode 100644 index 2f65be31..00000000 --- a/ext/bg/js/templates.js +++ /dev/null @@ -1,55 +0,0 @@ -(function() { - var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {}; -templates['query-parser.html'] = template({"1":function(container,depth0,helpers,partials,data) { - var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {}); - - return ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.preview : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.program(4, data, 0),"data":data})) != null ? stack1 : "") - + ((stack1 = helpers.each.call(alias1,depth0,{"name":"each","hash":{},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") - + "</span>"; -},"2":function(container,depth0,helpers,partials,data) { - return "<span class=\"query-parser-term-preview\">"; -},"4":function(container,depth0,helpers,partials,data) { - return "<span class=\"query-parser-term\">"; -},"6":function(container,depth0,helpers,partials,data) { - var stack1; - - return ((stack1 = container.invokePartial(partials.part,depth0,{"name":"part","data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : ""); -},"8":function(container,depth0,helpers,partials,data) { - var stack1; - - return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.raw : depth0),{"name":"if","hash":{},"fn":container.program(9, data, 0),"inverse":container.program(12, data, 0),"data":data})) != null ? stack1 : ""); -},"9":function(container,depth0,helpers,partials,data) { - var stack1; - - return ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.text : depth0),{"name":"each","hash":{},"fn":container.program(10, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : ""); -},"10":function(container,depth0,helpers,partials,data) { - return "<span class=\"query-parser-char\">" - + container.escapeExpression(container.lambda(depth0, depth0)) - + "</span>"; -},"12":function(container,depth0,helpers,partials,data) { - var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}); - - return "<ruby>" - + ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.text : depth0),{"name":"each","hash":{},"fn":container.program(10, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") - + "<rt>" - + container.escapeExpression(((helper = (helper = helpers.reading || (depth0 != null ? depth0.reading : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(alias1,{"name":"reading","hash":{},"data":data}) : helper))) - + "</rt></ruby>"; -},"14":function(container,depth0,helpers,partials,data,blockParams,depths) { - var stack1; - - return ((stack1 = container.invokePartial(partials.term,depth0,{"name":"term","hash":{"preview":(depths[1] != null ? depths[1].preview : depths[1])},"data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : ""); -},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data,blockParams,depths) { - var stack1; - - return ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.terms : depth0),{"name":"each","hash":{},"fn":container.program(14, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : ""); -},"main_d": function(fn, props, container, depth0, data, blockParams, depths) { - - var decorators = container.decorators; - - fn = decorators.inline(fn,props,container,{"name":"inline","hash":{},"fn":container.program(1, data, 0, blockParams, depths),"inverse":container.noop,"args":["term"],"data":data}) || fn; - fn = decorators.inline(fn,props,container,{"name":"inline","hash":{},"fn":container.program(8, data, 0, blockParams, depths),"inverse":container.noop,"args":["part"],"data":data}) || fn; - return fn; - } - -,"useDecorators":true,"usePartial":true,"useData":true,"useDepths":true}); -})();
\ No newline at end of file diff --git a/ext/bg/query-parser-templates.html b/ext/bg/query-parser-templates.html new file mode 100644 index 00000000..7cab16a9 --- /dev/null +++ b/ext/bg/query-parser-templates.html @@ -0,0 +1,11 @@ +<!DOCTYPE html><html><head></head><body> + +<template id="term-template"><span class="query-parser-term" data-type="normal"></span></template> +<template id="term-preview-template"><span class="query-parser-term" data-type="preview"></span></template> +<template id="segment-template"><ruby class="query-parser-segment"><span class="query-parser-segment-text"></span><rt class="query-parser-segment-reading"></rt></ruby></template> +<template id="char-template"><span class="query-parser-char"></span></template> + +<template id="select-template"><select class="query-parser-select form-control"></select></template> +<template id="select-option-template"><option class="query-parser-select-option"></option></template> + +</body></html> diff --git a/ext/bg/search.html b/ext/bg/search.html index 10e5aa8e..d6336826 100644 --- a/ext/bg/search.html +++ b/ext/bg/search.html @@ -48,8 +48,8 @@ <div id="spinner" hidden><img src="/mixed/img/spinner.gif"></div> <div class="scan-disable"> - <div id="query-parser-select" class="input-group"></div> - <div id="query-parser"></div> + <div id="query-parser-select-container" class="input-group"></div> + <div id="query-parser-content"></div> </div> <hr> @@ -78,7 +78,6 @@ <script src="/bg/js/dictionary.js"></script> <script src="/bg/js/handlebars.js"></script> <script src="/bg/js/japanese.js"></script> - <script src="/bg/js/templates.js"></script> <script src="/fg/js/document.js"></script> <script src="/fg/js/source.js"></script> <script src="/mixed/js/audio.js"></script> @@ -87,7 +86,9 @@ <script src="/mixed/js/display-generator.js"></script> <script src="/mixed/js/scroll.js"></script> <script src="/mixed/js/text-scanner.js"></script> + <script src="/mixed/js/template-handler.js"></script> + <script src="/bg/js/search-query-parser-generator.js"></script> <script src="/bg/js/search-query-parser.js"></script> <script src="/bg/js/clipboard-monitor.js"></script> <script src="/bg/js/search.js"></script> diff --git a/ext/bg/settings.html b/ext/bg/settings.html index 57616873..b048a36c 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -1097,7 +1097,6 @@ <script src="/bg/js/options.js"></script> <script src="/bg/js/page-exit-prevention.js"></script> <script src="/bg/js/profile-conditions.js"></script> - <script src="/bg/js/templates.js"></script> <script src="/bg/js/util.js"></script> <script src="/mixed/js/audio.js"></script> diff --git a/ext/mixed/css/display.css b/ext/mixed/css/display.css index f131bda3..6a5383bc 100644 --- a/ext/mixed/css/display.css +++ b/ext/mixed/css/display.css @@ -127,12 +127,12 @@ html:root[data-yomichan-page=float] .navigation-header:not([hidden])~.navigation user-select: none; } -#query-parser { +#query-parser-content { margin-top: 0.5em; font-size: 2em; } -#query-parser[data-term-spacing=true] .query-parser-term { +#query-parser-content[data-term-spacing=true] .query-parser-term { margin-right: 0.2em; } diff --git a/ext/mixed/js/api.js b/ext/mixed/js/api.js index 5ec93b01..0b1e7e4f 100644 --- a/ext/mixed/js/api.js +++ b/ext/mixed/js/api.js @@ -105,6 +105,10 @@ function apiGetDisplayTemplatesHtml() { return _apiInvoke('getDisplayTemplatesHtml'); } +function apiGetQueryParserTemplatesHtml() { + return _apiInvoke('getQueryParserTemplatesHtml'); +} + function apiGetZoom() { return _apiInvoke('getZoom'); } diff --git a/ext/mixed/js/template-handler.js b/ext/mixed/js/template-handler.js new file mode 100644 index 00000000..a5a62937 --- /dev/null +++ b/ext/mixed/js/template-handler.js @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2020 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 TemplateHandler { + constructor(html) { + this._templates = new Map(); + + const doc = new DOMParser().parseFromString(html, 'text/html'); + for (const template of doc.querySelectorAll('template')) { + this._setTemplate(template); + } + } + + _setTemplate(template) { + const idMatch = template.id.match(/^([a-z-]+)-template$/); + if (!idMatch) { + throw new Error(`Invalid template ID: ${template.id}`); + } + this._templates.set(idMatch[1], template); + } + + instantiate(name) { + const template = this._templates.get(name); + return document.importNode(template.content.firstChild, true); + } + + instantiateFragment(name) { + const template = this._templates.get(name); + return document.importNode(template.content, true); + } +} diff --git a/tmpl/query-parser.html b/tmpl/query-parser.html deleted file mode 100644 index db98b5ff..00000000 --- a/tmpl/query-parser.html +++ /dev/null @@ -1,27 +0,0 @@ -{{~#*inline "term"~}} -{{~#if preview~}} -<span class="query-parser-term-preview"> -{{~else~}} -<span class="query-parser-term"> -{{~/if~}} -{{~#each this~}} -{{> part }} -{{~/each~}} -</span> -{{~/inline~}} - -{{~#*inline "part"~}} -{{~#if raw~}} -{{~#each text~}} -<span class="query-parser-char">{{this}}</span> -{{~/each~}} -{{~else~}} -<ruby>{{~#each text~}} -<span class="query-parser-char">{{this}}</span> -{{~/each~}}<rt>{{reading}}</rt></ruby> -{{~/if~}} -{{~/inline~}} - -{{~#each terms~}} -{{> term preview=../preview }} -{{~/each~}} |