diff options
| -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~}} |