Merge pull request #350 from siikamiika/query-parser-html-templates
query parser html templates
This commit is contained in:
commit
9ffd0cb441
10
README.md
10
README.md
@ -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
|
||||
|
@ -1,2 +0,0 @@
|
||||
#!/bin/sh
|
||||
handlebars tmpl/*.html -f ext/bg/js/templates.js
|
@ -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
|
@ -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>
|
||||
|
@ -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) => {
|
||||
|
@ -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)]
|
||||
]);
|
||||
|
||||
|
77
ext/bg/js/search-query-parser-generator.js
Normal file
77
ext/bg/js/search-query-parser-generator.js
Normal file
@ -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;
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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});
|
||||
})();
|
11
ext/bg/query-parser-templates.html
Normal file
11
ext/bg/query-parser-templates.html
Normal file
@ -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>
|
@ -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>
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -105,6 +105,10 @@ function apiGetDisplayTemplatesHtml() {
|
||||
return _apiInvoke('getDisplayTemplatesHtml');
|
||||
}
|
||||
|
||||
function apiGetQueryParserTemplatesHtml() {
|
||||
return _apiInvoke('getQueryParserTemplatesHtml');
|
||||
}
|
||||
|
||||
function apiGetZoom() {
|
||||
return _apiInvoke('getZoom');
|
||||
}
|
||||
|
47
ext/mixed/js/template-handler.js
Normal file
47
ext/mixed/js/template-handler.js
Normal file
@ -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);
|
||||
}
|
||||
}
|
@ -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~}}
|
Loading…
Reference in New Issue
Block a user