aboutsummaryrefslogtreecommitdiff
path: root/ext/bg
diff options
context:
space:
mode:
authorAlex Yatskov <alex@foosoft.net>2020-01-26 11:29:30 -0800
committerAlex Yatskov <alex@foosoft.net>2020-01-26 11:29:30 -0800
commit0c5b9b1fa1599cbf769d96cdebc226310f9dd8bc (patch)
treee734e2c3005078dbc248b541d357a934baa8a116 /ext/bg
parent2a12036ca305044291f1f4105d6a8d249848b210 (diff)
parent0cf1cf3aa094585bd6db8db2c1f229ba0ea37b6e (diff)
Merge branch 'master' into testing
Diffstat (limited to 'ext/bg')
-rw-r--r--ext/bg/css/settings.css17
-rw-r--r--ext/bg/data/options-schema.json60
-rw-r--r--ext/bg/js/api.js4
-rw-r--r--ext/bg/js/backend.js50
-rw-r--r--ext/bg/js/database.js17
-rw-r--r--ext/bg/js/dictionary.js5
-rw-r--r--ext/bg/js/handlebars.js2
-rw-r--r--ext/bg/js/mecab.js23
-rw-r--r--ext/bg/js/options.js12
-rw-r--r--ext/bg/js/request.js22
-rw-r--r--ext/bg/js/search-frontend.js2
-rw-r--r--ext/bg/js/search-query-parser.js117
-rw-r--r--ext/bg/js/search.js11
-rw-r--r--ext/bg/js/settings/anki.js26
-rw-r--r--ext/bg/js/settings/main.js20
-rw-r--r--ext/bg/js/settings/popup-preview-frame.js14
-rw-r--r--ext/bg/js/templates.js469
-rw-r--r--ext/bg/js/translator.js255
-rw-r--r--ext/bg/search.html16
-rw-r--r--ext/bg/settings.html93
20 files changed, 556 insertions, 679 deletions
diff --git a/ext/bg/css/settings.css b/ext/bg/css/settings.css
index 63cead6b..815a88fa 100644
--- a/ext/bg/css/settings.css
+++ b/ext/bg/css/settings.css
@@ -187,6 +187,23 @@ input[type=checkbox].storage-button-checkbox {
margin: 0;
}
+.error-data-show-button {
+ display: inline-block;
+ margin-left: 0.5em;
+ cursor: pointer;
+}
+.error-data-show-button:after {
+ content: "\2026";
+ font-weight: bold;
+}
+
+.error-data-container {
+ margin-top: 0.25em;
+ font-family: 'Courier New', Courier, monospace;
+ white-space: pre;
+ overflow-x: auto;
+}
+
[data-show-for-browser],
[data-show-for-operating-system] {
display: none;
diff --git a/ext/bg/data/options-schema.json b/ext/bg/data/options-schema.json
index c086052b..a20a0619 100644
--- a/ext/bg/data/options-schema.json
+++ b/ext/bg/data/options-schema.json
@@ -65,6 +65,7 @@
"general",
"audio",
"scanning",
+ "translation",
"dictionaries",
"parsing",
"anki"
@@ -91,6 +92,9 @@
"popupVerticalOffset2",
"popupHorizontalTextPosition",
"popupVerticalTextPosition",
+ "popupScalingFactor",
+ "popupScaleRelativeToPageZoom",
+ "popupScaleRelativeToVisualViewport",
"showGuide",
"compactTags",
"compactGlossaries",
@@ -166,6 +170,18 @@
"enum": ["default", "before", "after", "left", "right"],
"default": "before"
},
+ "popupScalingFactor": {
+ "type": "number",
+ "default": 1
+ },
+ "popupScaleRelativeToPageZoom": {
+ "type": "boolean",
+ "default": false
+ },
+ "popupScaleRelativeToVisualViewport": {
+ "type": "boolean",
+ "default": true
+ },
"showGuide": {
"type": "boolean",
"default": true
@@ -335,6 +351,43 @@
}
}
},
+ "translation": {
+ "type": "object",
+ "required": [
+ "convertHalfWidthCharacters",
+ "convertNumericCharacters",
+ "convertAlphabeticCharacters",
+ "convertHiraganaToKatakana",
+ "convertKatakanaToHiragana"
+ ],
+ "properties": {
+ "convertHalfWidthCharacters": {
+ "type": "string",
+ "enum": ["false", "true", "variant"],
+ "default": "false"
+ },
+ "convertNumericCharacters": {
+ "type": "string",
+ "enum": ["false", "true", "variant"],
+ "default": "false"
+ },
+ "convertAlphabeticCharacters": {
+ "type": "string",
+ "enum": ["false", "true", "variant"],
+ "default": "false"
+ },
+ "convertHiraganaToKatakana": {
+ "type": "string",
+ "enum": ["false", "true", "variant"],
+ "default": "false"
+ },
+ "convertKatakanaToHiragana": {
+ "type": "string",
+ "enum": ["false", "true", "variant"],
+ "default": "variant"
+ }
+ }
+ },
"dictionaries": {
"type": "object",
"additionalProperties": {
@@ -366,6 +419,7 @@
"enableScanningParser",
"enableMecabParser",
"selectedParser",
+ "termSpacing",
"readingMode"
],
"properties": {
@@ -381,9 +435,13 @@
"type": ["string", "null"],
"default": null
},
+ "termSpacing": {
+ "type": "boolean",
+ "default": true
+ },
"readingMode": {
"type": "string",
- "enum": ["hiragana", "katakana", "romaji"],
+ "enum": ["hiragana", "katakana", "romaji", "none"],
"default": "hiragana"
}
}
diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js
index 906aaa30..285b8016 100644
--- a/ext/bg/js/api.js
+++ b/ext/bg/js/api.js
@@ -25,6 +25,10 @@ function apiAudioGetUrl(definition, source, optionsContext) {
return _apiInvoke('audioGetUrl', {definition, source, optionsContext});
}
+function apiGetDisplayTemplatesHtml() {
+ return _apiInvoke('getDisplayTemplatesHtml');
+}
+
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 28b0201e..eeab68a5 100644
--- a/ext/bg/js/backend.js
+++ b/ext/bg/js/backend.js
@@ -51,9 +51,12 @@ class Backend {
this.onOptionsUpdated('background');
- if (chrome.commands !== null && typeof chrome.commands === 'object') {
+ if (isObject(chrome.commands) && isObject(chrome.commands.onCommand)) {
chrome.commands.onCommand.addListener((command) => this._runCommand(command));
}
+ if (isObject(chrome.tabs) && isObject(chrome.tabs.onZoomChange)) {
+ chrome.tabs.onZoomChange.addListener((info) => this._onZoomChange(info));
+ }
chrome.runtime.onMessage.addListener(this.onMessage.bind(this));
const options = this.getOptionsSync(this.optionsContext);
@@ -94,6 +97,11 @@ class Backend {
}
}
+ _onZoomChange({tabId, oldZoomFactor, newZoomFactor}) {
+ const callback = () => this.checkLastError(chrome.runtime.lastError);
+ chrome.tabs.sendMessage(tabId, {action: 'zoomChanged', params: {oldZoomFactor, newZoomFactor}}, callback);
+ }
+
applyOptions() {
const options = this.getOptionsSync(this.optionsContext);
if (!options.general.enable) {
@@ -299,8 +307,8 @@ class Backend {
const [definitions, sourceLength] = await this.translator.findTermsInternal(
text.substring(0, options.scanning.length),
dictEnabledSet(options),
- options.scanning.alphanumeric,
- {}
+ {},
+ options
);
if (definitions.length > 0) {
dictTermsSort(definitions);
@@ -522,6 +530,38 @@ class Backend {
return result;
}
+ async _onApiGetDisplayTemplatesHtml() {
+ const url = chrome.runtime.getURL('/mixed/display-templates.html');
+ return await requestText(url, 'GET');
+ }
+
+ _onApiGetZoom(params, sender) {
+ if (!sender || !sender.tab) {
+ return Promise.reject(new Error('Invalid tab'));
+ }
+
+ return new Promise((resolve, reject) => {
+ const tabId = sender.tab.id;
+ if (!(
+ chrome.tabs !== null &&
+ typeof chrome.tabs === 'object' &&
+ typeof chrome.tabs.getZoom === 'function'
+ )) {
+ // Not supported
+ resolve({zoomFactor: 1.0});
+ return;
+ }
+ chrome.tabs.getZoom(tabId, (zoomFactor) => {
+ const e = chrome.runtime.lastError;
+ if (e) {
+ reject(new Error(e.message));
+ } else {
+ resolve({zoomFactor});
+ }
+ });
+ });
+ }
+
// Command handlers
async _onCommandSearch(params) {
@@ -735,7 +775,9 @@ Backend._messageHandlers = new Map([
['frameInformationGet', (self, ...args) => self._onApiFrameInformationGet(...args)],
['injectStylesheet', (self, ...args) => self._onApiInjectStylesheet(...args)],
['getEnvironmentInfo', (self, ...args) => self._onApiGetEnvironmentInfo(...args)],
- ['clipboardGet', (self, ...args) => self._onApiClipboardGet(...args)]
+ ['clipboardGet', (self, ...args) => self._onApiClipboardGet(...args)],
+ ['getDisplayTemplatesHtml', (self, ...args) => self._onApiGetDisplayTemplatesHtml(...args)],
+ ['getZoom', (self, ...args) => self._onApiGetZoom(...args)]
]);
Backend._commandHandlers = new Map([
diff --git a/ext/bg/js/database.js b/ext/bg/js/database.js
index 42a143f3..e87cc64b 100644
--- a/ext/bg/js/database.js
+++ b/ext/bg/js/database.js
@@ -225,7 +225,7 @@ class Database {
}
async findTermMetaBulk(termList, titles) {
- return this.findGenericBulk('termMeta', 'expression', termList, titles, Database.createMeta);
+ return this.findGenericBulk('termMeta', 'expression', termList, titles, Database.createTermMeta);
}
async findKanjiBulk(kanjiList, titles) {
@@ -233,7 +233,7 @@ class Database {
}
async findKanjiMetaBulk(kanjiList, titles) {
- return this.findGenericBulk('kanjiMeta', 'character', kanjiList, titles, Database.createMeta);
+ return this.findGenericBulk('kanjiMeta', 'character', kanjiList, titles, Database.createKanjiMeta);
}
async findGenericBulk(tableName, indexName, indexValueList, titles, createResult) {
@@ -632,13 +632,12 @@ class Database {
};
}
- static createMeta(row, index) {
- return {
- index,
- mode: row.mode,
- data: row.data,
- dictionary: row.dictionary
- };
+ static createTermMeta({expression, mode, data, dictionary}, index) {
+ return {expression, mode, data, dictionary, index};
+ }
+
+ static createKanjiMeta({character, mode, data, dictionary}, index) {
+ return {character, mode, data, dictionary, index};
}
static getAll(dbIndex, query, context, processRow) {
diff --git a/ext/bg/js/dictionary.js b/ext/bg/js/dictionary.js
index 92adc532..67128725 100644
--- a/ext/bg/js/dictionary.js
+++ b/ext/bg/js/dictionary.js
@@ -67,7 +67,7 @@ function dictTermsSort(definitions, dictionaries=null) {
i = v2.source.length - v1.source.length;
if (i !== 0) { return i; }
- i = v2.reasons.length - v1.reasons.length;
+ i = v1.reasons.length - v2.reasons.length;
if (i !== 0) { return i; }
i = v2.score - v1.score;
@@ -147,8 +147,9 @@ function dictTermsGroup(definitions, dictionaries) {
definitions: groupDefs,
expression: firstDef.expression,
reading: firstDef.reading,
+ furiganaSegments: firstDef.furiganaSegments,
reasons: firstDef.reasons,
- termTags: groupDefs[0].termTags,
+ termTags: firstDef.termTags,
score: groupDefs.reduce((p, v) => v.score > p ? v.score : p, Number.MIN_SAFE_INTEGER),
source: firstDef.source
});
diff --git a/ext/bg/js/handlebars.js b/ext/bg/js/handlebars.js
index 6d1581be..62f89ee4 100644
--- a/ext/bg/js/handlebars.js
+++ b/ext/bg/js/handlebars.js
@@ -61,7 +61,7 @@ function handlebarsFuriganaPlain(options) {
function handlebarsKanjiLinks(options) {
let result = '';
for (const c of options.fn(this)) {
- if (jpIsKanji(c)) {
+ if (jpIsCharCodeKanji(c.charCodeAt(0))) {
result += `<a href="#" class="kanji-link">${c}</a>`;
} else {
result += c;
diff --git a/ext/bg/js/mecab.js b/ext/bg/js/mecab.js
index 8bcbb91c..34ecd728 100644
--- a/ext/bg/js/mecab.js
+++ b/ext/bg/js/mecab.js
@@ -20,7 +20,7 @@
class Mecab {
constructor() {
this.port = null;
- this.listeners = {};
+ this.listeners = new Map();
this.sequence = 0;
}
@@ -55,17 +55,18 @@ class Mecab {
if (this.port === null) { return; }
this.port.disconnect();
this.port = null;
- this.listeners = {};
+ this.listeners.clear();
this.sequence = 0;
}
onNativeMessage({sequence, data}) {
- if (hasOwn(this.listeners, sequence)) {
- const {callback, timer} = this.listeners[sequence];
- clearTimeout(timer);
- callback(data);
- delete this.listeners[sequence];
- }
+ const listener = this.listeners.get(sequence);
+ if (typeof listener === 'undefined') { return; }
+
+ const {callback, timer} = listener;
+ clearTimeout(timer);
+ callback(data);
+ this.listeners.delete(sequence);
}
invoke(action, params) {
@@ -75,13 +76,13 @@ class Mecab {
return new Promise((resolve, reject) => {
const sequence = this.sequence++;
- this.listeners[sequence] = {
+ this.listeners.set(sequence, {
callback: resolve,
timer: setTimeout(() => {
- delete this.listeners[sequence];
+ this.listeners.delete(sequence);
reject(new Error(`Mecab invoke timed out in ${Mecab.timeout} ms`));
}, Mecab.timeout)
- };
+ });
this.port.postMessage({action, params, sequence});
});
diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js
index 8021672b..d93862bf 100644
--- a/ext/bg/js/options.js
+++ b/ext/bg/js/options.js
@@ -279,6 +279,9 @@ function profileOptionsCreateDefaults() {
popupVerticalOffset2: 0,
popupHorizontalTextPosition: 'below',
popupVerticalTextPosition: 'before',
+ popupScalingFactor: 1,
+ popupScaleRelativeToPageZoom: false,
+ popupScaleRelativeToVisualViewport: true,
showGuide: true,
compactTags: false,
compactGlossaries: false,
@@ -316,12 +319,21 @@ function profileOptionsCreateDefaults() {
enableOnSearchPage: true
},
+ translation: {
+ convertHalfWidthCharacters: 'false',
+ convertNumericCharacters: 'false',
+ convertAlphabeticCharacters: 'false',
+ convertHiraganaToKatakana: 'false',
+ convertKatakanaToHiragana: 'variant'
+ },
+
dictionaries: {},
parsing: {
enableScanningParser: true,
enableMecabParser: false,
selectedParser: null,
+ termSpacing: true,
readingMode: 'hiragana'
},
diff --git a/ext/bg/js/request.js b/ext/bg/js/request.js
index b584c9a9..02eed6fb 100644
--- a/ext/bg/js/request.js
+++ b/ext/bg/js/request.js
@@ -17,10 +17,10 @@
*/
-function requestJson(url, action, params) {
+function requestText(url, action, params) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
- xhr.overrideMimeType('application/json');
+ xhr.overrideMimeType('text/plain');
xhr.addEventListener('load', () => resolve(xhr.responseText));
xhr.addEventListener('error', () => reject(new Error('Failed to connect')));
xhr.open(action, url);
@@ -29,12 +29,16 @@ function requestJson(url, action, params) {
} else {
xhr.send();
}
- }).then((responseText) => {
- try {
- return JSON.parse(responseText);
- }
- catch (e) {
- return Promise.reject(new Error('Invalid response'));
- }
});
}
+
+async function requestJson(url, action, params) {
+ const responseText = await requestText(url, action, params);
+ try {
+ return JSON.parse(responseText);
+ } catch (e) {
+ const error = new Error(`Invalid response (${e.message || e})`);
+ error.data = {url, action, params, responseText};
+ throw error;
+ }
+}
diff --git a/ext/bg/js/search-frontend.js b/ext/bg/js/search-frontend.js
index 2fe50a13..e453ccef 100644
--- a/ext/bg/js/search-frontend.js
+++ b/ext/bg/js/search-frontend.js
@@ -27,7 +27,7 @@ async function searchFrontendSetup() {
const ignoreNodes = ['.scan-disable', '.scan-disable *'];
if (!options.scanning.enableOnPopupExpressions) {
- ignoreNodes.push('.expression-scan-toggle', '.expression-scan-toggle *');
+ ignoreNodes.push('.source-text', '.source-text *');
}
window.frontendInitializationData = {depth: 1, ignoreNodes, proxy: false};
diff --git a/ext/bg/js/search-query-parser.js b/ext/bg/js/search-query-parser.js
index 0b3eccbd..e8e6d11f 100644
--- a/ext/bg/js/search-query-parser.js
+++ b/ext/bg/js/search-query-parser.js
@@ -17,73 +17,47 @@
*/
-class QueryParser {
+class QueryParser extends TextScanner {
constructor(search) {
+ super(document.querySelector('#query-parser'), [], [], []);
this.search = search;
- this.pendingLookup = false;
- this.clickScanPrevent = false;
this.parseResults = [];
this.selectedParser = null;
this.queryParser = document.querySelector('#query-parser');
this.queryParserSelect = document.querySelector('#query-parser-select');
-
- this.queryParser.addEventListener('mousedown', (e) => this.onMouseDown(e));
- this.queryParser.addEventListener('mouseup', (e) => this.onMouseUp(e));
}
onError(error) {
logError(error, false);
}
- onMouseDown(e) {
- if (DOM.isMouseButtonPressed(e, 'primary')) {
- this.clickScanPrevent = false;
- }
+ onClick(e) {
+ super.onClick(e);
+ this.searchAt(e.clientX, e.clientY, 'click');
}
- onMouseUp(e) {
- if (
- this.search.options.scanning.enablePopupSearch &&
- !this.clickScanPrevent &&
- DOM.isMouseButtonPressed(e, 'primary')
- ) {
- const selectText = this.search.options.scanning.selectText;
- this.onTermLookup(e, {disableScroll: true, selectText});
- }
- }
+ async onSearchSource(textSource, cause) {
+ if (textSource === null) { return null; }
- onMouseMove(e) {
- if (this.pendingLookup || DOM.isMouseButtonDown(e, 'primary')) {
- return;
- }
+ this.setTextSourceScanLength(textSource, this.search.options.scanning.length);
+ const searchText = textSource.text();
+ if (searchText.length === 0) { return; }
- const scanningOptions = this.search.options.scanning;
- const scanningModifier = scanningOptions.modifier;
- if (!(
- TextScanner.isScanningModifierPressed(scanningModifier, e) ||
- (scanningOptions.middleMouse && DOM.isMouseButtonDown(e, 'auxiliary'))
- )) {
- return;
- }
+ const {definitions, length} = await apiTermsFind(searchText, {}, this.search.getOptionsContext());
+ if (definitions.length === 0) { return null; }
- const selectText = this.search.options.scanning.selectText;
- this.onTermLookup(e, {disableScroll: true, disableHistory: true, selectText});
- }
+ textSource.setEndOffset(length);
- onMouseLeave(e) {
- this.clickScanPrevent = true;
- clearTimeout(e.target.dataset.timer);
- delete e.target.dataset.timer;
- }
+ this.search.setContent('terms', {definitions, context: {
+ focus: false,
+ disableHistory: cause === 'mouse' ? true : false,
+ sentence: {text: searchText, offset: 0},
+ url: window.location.href
+ }});
- onTermLookup(e, params) {
- this.pendingLookup = true;
- (async () => {
- await this.search.onTermLookup(e, params);
- this.pendingLookup = false;
- })();
+ return {definitions, type: 'terms'};
}
onParserChange(e) {
@@ -93,6 +67,32 @@ class QueryParser {
this.renderParseResult(this.getParseResult());
}
+ getMouseEventListeners() {
+ return [
+ [this.node, 'click', this.onClick.bind(this)],
+ [this.node, 'mousedown', this.onMouseDown.bind(this)],
+ [this.node, 'mousemove', this.onMouseMove.bind(this)],
+ [this.node, 'mouseover', this.onMouseOver.bind(this)],
+ [this.node, 'mouseout', this.onMouseOut.bind(this)]
+ ];
+ }
+
+ getTouchEventListeners() {
+ return [
+ [this.node, 'auxclick', this.onAuxClick.bind(this)],
+ [this.node, 'touchstart', this.onTouchStart.bind(this)],
+ [this.node, 'touchend', this.onTouchEnd.bind(this)],
+ [this.node, 'touchcancel', this.onTouchCancel.bind(this)],
+ [this.node, 'touchmove', this.onTouchMove.bind(this), {passive: false}],
+ [this.node, 'contextmenu', this.onContextMenu.bind(this)]
+ ];
+ }
+
+ setOptions(options) {
+ super.setOptions(options);
+ this.queryParser.dataset.termSpacing = `${options.parsing.termSpacing}`;
+ }
+
refreshSelectedParser() {
if (this.parseResults.length > 0) {
if (this.selectedParser === null) {
@@ -156,10 +156,6 @@ class QueryParser {
terms: previewTerms,
preview: true
});
-
- for (const charElement of this.queryParser.querySelectorAll('.query-parser-char')) {
- this.activateScanning(charElement);
- }
}
renderParserSelect() {
@@ -190,27 +186,6 @@ class QueryParser {
'query-parser.html',
{terms: QueryParser.processParseResultForDisplay(parseResult.parsedText)}
);
-
- for (const charElement of this.queryParser.querySelectorAll('.query-parser-char')) {
- this.activateScanning(charElement);
- }
- }
-
- activateScanning(element) {
- element.addEventListener('mousemove', (e) => {
- clearTimeout(e.target.dataset.timer);
- if (this.search.options.scanning.modifier === 'none') {
- e.target.dataset.timer = setTimeout(() => {
- this.onMouseMove(e);
- delete e.target.dataset.timer;
- }, this.search.options.scanning.delay);
- } else {
- this.onMouseMove(e);
- }
- });
- element.addEventListener('mouseleave', (e) => {
- this.onMouseLeave(e);
- });
}
static processParseResultForDisplay(result) {
diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js
index a4103ef2..f5c641a8 100644
--- a/ext/bg/js/search.js
+++ b/ext/bg/js/search.js
@@ -220,12 +220,12 @@ class DisplaySearch extends Display {
this.updateSearchButton();
if (valid) {
const {definitions} = await apiTermsFind(query, details, this.optionsContext);
- this.setContentTerms(definitions, {
+ this.setContent('terms', {definitions, context: {
focus: false,
disableHistory: true,
sentence: {text: query, offset: 0},
url: window.location.href
- });
+ }});
} else {
this.container.textContent = '';
}
@@ -236,6 +236,11 @@ class DisplaySearch extends Display {
}
}
+ async updateOptions(options) {
+ await super.updateOptions(options);
+ this.queryParser.setOptions(this.options);
+ }
+
initClipboardMonitor() {
// ignore copy from search page
window.addEventListener('copy', () => {
@@ -260,7 +265,7 @@ class DisplaySearch extends Display {
text !== this.clipboardPreviousText
) {
this.clipboardPreviousText = text;
- if (jpIsJapaneseText(text)) {
+ if (jpIsStringPartiallyJapanese(text)) {
this.setQuery(this.isWanakanaEnabled() ? window.wanakana.toKana(text) : text);
window.history.pushState(null, '', `${window.location.pathname}?query=${encodeURIComponent(text)}`);
this.onSearchQueryUpdated(this.query.value, true);
diff --git a/ext/bg/js/settings/anki.js b/ext/bg/js/settings/anki.js
index 5f7989b8..9adb2f2a 100644
--- a/ext/bg/js/settings/anki.js
+++ b/ext/bg/js/settings/anki.js
@@ -37,13 +37,35 @@ function _ankiSetError(error) {
if (error) {
node.hidden = false;
node.textContent = `${error}`;
- }
- else {
+ _ankiSetErrorData(node, error);
+ } else {
node.hidden = true;
node.textContent = '';
}
}
+function _ankiSetErrorData(node, error) {
+ const data = error.data;
+ let message = '';
+ if (typeof data !== 'undefined') {
+ message += `${JSON.stringify(data, null, 4)}\n\n`;
+ }
+ message += `${error.stack}`.trimRight();
+
+ const button = document.createElement('a');
+ button.className = 'error-data-show-button';
+
+ const content = document.createElement('div');
+ content.className = 'error-data-container';
+ content.textContent = message;
+ content.hidden = true;
+
+ button.addEventListener('click', () => content.hidden = !content.hidden, false);
+
+ node.appendChild(button);
+ node.appendChild(content);
+}
+
function _ankiSetDropdownOptions(dropdown, optionValues) {
const fragment = document.createDocumentFragment();
for (const optionValue of optionValues) {
diff --git a/ext/bg/js/settings/main.js b/ext/bg/js/settings/main.js
index 56828a15..3bf65eda 100644
--- a/ext/bg/js/settings/main.js
+++ b/ext/bg/js/settings/main.js
@@ -44,6 +44,9 @@ async function formRead(options) {
options.general.popupVerticalOffset = parseInt($('#popup-vertical-offset').val(), 10);
options.general.popupHorizontalOffset2 = parseInt($('#popup-horizontal-offset2').val(), 0);
options.general.popupVerticalOffset2 = parseInt($('#popup-vertical-offset2').val(), 10);
+ options.general.popupScalingFactor = parseInt($('#popup-scaling-factor').val(), 10);
+ options.general.popupScaleRelativeToPageZoom = $('#popup-scale-relative-to-page-zoom').prop('checked');
+ options.general.popupScaleRelativeToVisualViewport = $('#popup-scale-relative-to-visual-viewport').prop('checked');
options.general.popupTheme = $('#popup-theme').val();
options.general.popupOuterTheme = $('#popup-outer-theme').val();
options.general.customPopupCss = $('#custom-popup-css').val();
@@ -69,8 +72,15 @@ async function formRead(options) {
options.scanning.modifier = $('#scan-modifier-key').val();
options.scanning.popupNestingMaxDepth = parseInt($('#popup-nesting-max-depth').val(), 10);
+ options.translation.convertHalfWidthCharacters = $('#translation-convert-half-width-characters').val();
+ options.translation.convertNumericCharacters = $('#translation-convert-numeric-characters').val();
+ options.translation.convertAlphabeticCharacters = $('#translation-convert-alphabetic-characters').val();
+ options.translation.convertHiraganaToKatakana = $('#translation-convert-hiragana-to-katakana').val();
+ options.translation.convertKatakanaToHiragana = $('#translation-convert-katakana-to-hiragana').val();
+
options.parsing.enableScanningParser = $('#parsing-scan-enable').prop('checked');
options.parsing.enableMecabParser = $('#parsing-mecab-enable').prop('checked');
+ options.parsing.termSpacing = $('#parsing-term-spacing').prop('checked');
options.parsing.readingMode = $('#parsing-reading-mode').val();
const optionsAnkiEnableOld = options.anki.enable;
@@ -109,6 +119,9 @@ async function formWrite(options) {
$('#popup-vertical-offset').val(options.general.popupVerticalOffset);
$('#popup-horizontal-offset2').val(options.general.popupHorizontalOffset2);
$('#popup-vertical-offset2').val(options.general.popupVerticalOffset2);
+ $('#popup-scaling-factor').val(options.general.popupScalingFactor);
+ $('#popup-scale-relative-to-page-zoom').prop('checked', options.general.popupScaleRelativeToPageZoom);
+ $('#popup-scale-relative-to-visual-viewport').prop('checked', options.general.popupScaleRelativeToVisualViewport);
$('#popup-theme').val(options.general.popupTheme);
$('#popup-outer-theme').val(options.general.popupOuterTheme);
$('#custom-popup-css').val(options.general.customPopupCss);
@@ -134,8 +147,15 @@ async function formWrite(options) {
$('#scan-modifier-key').val(options.scanning.modifier);
$('#popup-nesting-max-depth').val(options.scanning.popupNestingMaxDepth);
+ $('#translation-convert-half-width-characters').val(options.translation.convertHalfWidthCharacters);
+ $('#translation-convert-numeric-characters').val(options.translation.convertNumericCharacters);
+ $('#translation-convert-alphabetic-characters').val(options.translation.convertAlphabeticCharacters);
+ $('#translation-convert-hiragana-to-katakana').val(options.translation.convertHiraganaToKatakana);
+ $('#translation-convert-katakana-to-hiragana').val(options.translation.convertKatakanaToHiragana);
+
$('#parsing-scan-enable').prop('checked', options.parsing.enableScanningParser);
$('#parsing-mecab-enable').prop('checked', options.parsing.enableMecabParser);
+ $('#parsing-term-spacing').prop('checked', options.parsing.termSpacing);
$('#parsing-reading-mode').val(options.parsing.readingMode);
$('#anki-enable').prop('checked', options.anki.enable);
diff --git a/ext/bg/js/settings/popup-preview-frame.js b/ext/bg/js/settings/popup-preview-frame.js
index 2b727cbd..37a4b416 100644
--- a/ext/bg/js/settings/popup-preview-frame.js
+++ b/ext/bg/js/settings/popup-preview-frame.js
@@ -35,7 +35,6 @@ class SettingsPopupPreview {
async prepare() {
// Setup events
- window.addEventListener('resize', (e) => this.onWindowResize(e), false);
window.addEventListener('message', (e) => this.onMessage(e), false);
const themeDarkCheckbox = document.querySelector('#theme-dark-checkbox');
@@ -96,16 +95,6 @@ class SettingsPopupPreview {
return result;
}
- onWindowResize() {
- if (this.frontend === null) { return; }
- const textSource = this.textSource;
- if (textSource === null) { return; }
-
- const elementRect = textSource.getRect();
- const writingMode = textSource.getWritingMode();
- this.frontend.popup.showContent(elementRect, writingMode);
- }
-
onMessage(e) {
const {action, params} = e.data;
const handler = SettingsPopupPreview._messageHandlers.get(action);
@@ -159,10 +148,11 @@ class SettingsPopupPreview {
const range = document.createRange();
range.selectNode(textNode);
- const source = new TextSourceRange(range, range.toString(), null);
+ const source = new TextSourceRange(range, range.toString(), null, null);
try {
await this.frontend.onSearchSource(source, 'script');
+ this.frontend.setCurrentTextSource(source);
} finally {
source.cleanup();
}
diff --git a/ext/bg/js/templates.js b/ext/bg/js/templates.js
index eae4e014..2f65be31 100644
--- a/ext/bg/js/templates.js
+++ b/ext/bg/js/templates.js
@@ -1,178 +1,5 @@
(function() {
var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
-templates['kanji.html'] = template({"1":function(container,depth0,helpers,partials,data) {
- var stack1;
-
- return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.data : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.program(8, data, 0),"data":data})) != null ? stack1 : "");
-},"2":function(container,depth0,helpers,partials,data) {
- var stack1;
-
- return "<table class=\"info-output\">\n"
- + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.data : depth0),{"name":"each","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + "</table>\n";
-},"3":function(container,depth0,helpers,partials,data) {
- var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {});
-
- return " <tr>\n <th>"
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.notes : depth0),{"name":"if","hash":{},"fn":container.program(4, data, 0),"inverse":container.program(6, data, 0),"data":data})) != null ? stack1 : "")
- + "</th>\n <td>"
- + container.escapeExpression(((helper = (helper = helpers.value || (depth0 != null ? depth0.value : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(alias1,{"name":"value","hash":{},"data":data}) : helper)))
- + "</td>\n </tr>\n";
-},"4":function(container,depth0,helpers,partials,data) {
- var helper;
-
- return container.escapeExpression(((helper = (helper = helpers.notes || (depth0 != null ? depth0.notes : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"notes","hash":{},"data":data}) : helper)));
-},"6":function(container,depth0,helpers,partials,data) {
- var helper;
-
- return container.escapeExpression(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"name","hash":{},"data":data}) : helper)));
-},"8":function(container,depth0,helpers,partials,data) {
- return "No data found\n";
-},"10":function(container,depth0,helpers,partials,data) {
- var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {});
-
- return "<div class=\"entry\" data-type=\"kanji\">\n <div class=\"actions\">\n"
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.addable : depth0),{"name":"if","hash":{},"fn":container.program(11, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + " <img src=\"/mixed/img/entry-current.svg\" class=\"current\" title=\"Current entry (Alt + Up/Down/Home/End/PgUp/PgDn)\" alt>\n </div>\n\n <div class=\"glyph expression-scan-toggle\">"
- + container.escapeExpression(((helper = (helper = helpers.character || (depth0 != null ? depth0.character : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(alias1,{"name":"character","hash":{},"data":data}) : helper)))
- + "</div>\n\n"
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.frequencies : depth0),{"name":"if","hash":{},"fn":container.program(13, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + "\n"
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.tags : depth0),{"name":"if","hash":{},"fn":container.program(16, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + "\n <table class=\"table table-condensed glyph-data\">\n <tr>\n <th>Glossary</th>\n <th>Readings</th>\n <th>Statistics</th>\n </tr>\n <tr>\n <td class=\"glossary\">\n"
- + ((stack1 = helpers["if"].call(alias1,((stack1 = (depth0 != null ? depth0.glossary : depth0)) != null ? stack1["1"] : stack1),{"name":"if","hash":{},"fn":container.program(19, data, 0),"inverse":container.program(22, data, 0),"data":data})) != null ? stack1 : "")
- + " </td>\n <td class=\"reading\">\n "
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.onyomi : depth0),{"name":"if","hash":{},"fn":container.program(24, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + "\n "
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.kunyomi : depth0),{"name":"if","hash":{},"fn":container.program(27, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + "\n </td>\n <td>"
- + ((stack1 = container.invokePartial(partials.table,depth0,{"name":"table","hash":{"data":((stack1 = (depth0 != null ? depth0.stats : depth0)) != null ? stack1.misc : stack1)},"data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
- + "</td>\n </tr>\n <tr>\n <th colspan=\"3\">Classifications</th>\n </tr>\n <tr>\n <td colspan=\"3\">"
- + ((stack1 = container.invokePartial(partials.table,depth0,{"name":"table","hash":{"data":((stack1 = (depth0 != null ? depth0.stats : depth0)) != null ? stack1["class"] : stack1)},"data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
- + "</td>\n </tr>\n <tr>\n <th colspan=\"3\">Codepoints</th>\n </tr>\n <tr>\n <td colspan=\"3\">"
- + ((stack1 = container.invokePartial(partials.table,depth0,{"name":"table","hash":{"data":((stack1 = (depth0 != null ? depth0.stats : depth0)) != null ? stack1.code : stack1)},"data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
- + "</td>\n </tr>\n <tr>\n <th colspan=\"3\">Dictionary Indices</th>\n </tr>\n <tr>\n <td colspan=\"3\">"
- + ((stack1 = container.invokePartial(partials.table,depth0,{"name":"table","hash":{"data":((stack1 = (depth0 != null ? depth0.stats : depth0)) != null ? stack1.index : stack1)},"data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
- + "</td>\n </tr>\n </table>\n\n"
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.debug : depth0),{"name":"if","hash":{},"fn":container.program(29, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + "</div>\n";
-},"11":function(container,depth0,helpers,partials,data) {
- return " <a href=\"#\" class=\"action-view-note pending disabled\"><img src=\"/mixed/img/view-note.svg\" title=\"View added note (Alt + V)\" alt></a>\n <a href=\"#\" class=\"action-add-note pending disabled\" data-mode=\"kanji\"><img src=\"/mixed/img/add-term-kanji.svg\" title=\"Add Kanji (Alt + K)\" alt></a>\n";
-},"13":function(container,depth0,helpers,partials,data) {
- var stack1;
-
- return " <div>\n"
- + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.frequencies : depth0),{"name":"each","hash":{},"fn":container.program(14, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + " </div>\n";
-},"14":function(container,depth0,helpers,partials,data) {
- var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
-
- return " <span class=\"label label-default tag-frequency\">"
- + alias4(((helper = (helper = helpers.dictionary || (depth0 != null ? depth0.dictionary : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"dictionary","hash":{},"data":data}) : helper)))
- + ":"
- + alias4(((helper = (helper = helpers.frequency || (depth0 != null ? depth0.frequency : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"frequency","hash":{},"data":data}) : helper)))
- + "</span>\n";
-},"16":function(container,depth0,helpers,partials,data) {
- var stack1;
-
- return " <div>\n"
- + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.tags : depth0),{"name":"each","hash":{},"fn":container.program(17, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + " </div>\n";
-},"17":function(container,depth0,helpers,partials,data) {
- var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
-
- return " <span class=\"label label-default tag-"
- + alias4(((helper = (helper = helpers.category || (depth0 != null ? depth0.category : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"category","hash":{},"data":data}) : helper)))
- + "\" title=\""
- + alias4(((helper = (helper = helpers.notes || (depth0 != null ? depth0.notes : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"notes","hash":{},"data":data}) : helper)))
- + "\">"
- + alias4(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"name","hash":{},"data":data}) : helper)))
- + "</span>\n";
-},"19":function(container,depth0,helpers,partials,data) {
- var stack1;
-
- return " <ol>"
- + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.glossary : depth0),{"name":"each","hash":{},"fn":container.program(20, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + "</ol>\n";
-},"20":function(container,depth0,helpers,partials,data) {
- return "<li><span class=\"glossary-item\">"
- + container.escapeExpression(container.lambda(depth0, depth0))
- + "</span></li>";
-},"22":function(container,depth0,helpers,partials,data) {
- var stack1;
-
- return " <span class=\"glossary-item\">"
- + container.escapeExpression(container.lambda(((stack1 = (depth0 != null ? depth0.glossary : depth0)) != null ? stack1["0"] : stack1), depth0))
- + "</span>\n";
-},"24":function(container,depth0,helpers,partials,data) {
- var stack1;
-
- return "<dl>"
- + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.onyomi : depth0),{"name":"each","hash":{},"fn":container.program(25, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + "</dl>";
-},"25":function(container,depth0,helpers,partials,data) {
- return "<dd>"
- + container.escapeExpression(container.lambda(depth0, depth0))
- + "</dd>";
-},"27":function(container,depth0,helpers,partials,data) {
- var stack1;
-
- return "<dl>"
- + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.kunyomi : depth0),{"name":"each","hash":{},"fn":container.program(25, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + "</dl>";
-},"29":function(container,depth0,helpers,partials,data) {
- var stack1, helper, options, buffer =
- " <pre>";
- stack1 = ((helper = (helper = helpers.dumpObject || (depth0 != null ? depth0.dumpObject : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"dumpObject","hash":{},"fn":container.program(30, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));
- if (!helpers.dumpObject) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
- if (stack1 != null) { buffer += stack1; }
- return buffer + "</pre>\n";
-},"30":function(container,depth0,helpers,partials,data) {
- var stack1;
-
- return ((stack1 = container.lambda(depth0, depth0)) != null ? stack1 : "");
-},"32":function(container,depth0,helpers,partials,data,blockParams,depths) {
- var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {});
-
- return "<div class=\"term-navigation\">\n <a href=\"#\" "
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.source : depth0),{"name":"if","hash":{},"fn":container.program(33, data, 0, blockParams, depths),"inverse":container.program(35, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "")
- + "><img src=\"/mixed/img/source-term.svg\" title=\"Source term (Alt + B)\" alt></a>\n <a href=\"#\" "
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.next : depth0),{"name":"if","hash":{},"fn":container.program(37, data, 0, blockParams, depths),"inverse":container.program(39, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "")
- + "><img src=\"/mixed/img/source-term.svg\" style=\"transform: scaleX(-1);\" title=\"Next term (Alt + F)\" alt></a>\n</div>\n"
- + ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.definitions : depth0),{"name":"each","hash":{},"fn":container.program(41, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "");
-},"33":function(container,depth0,helpers,partials,data) {
- return "class=\"source-term\"";
-},"35":function(container,depth0,helpers,partials,data) {
- return "class=\"source-term invisible\"";
-},"37":function(container,depth0,helpers,partials,data) {
- return "class=\"next-term\"";
-},"39":function(container,depth0,helpers,partials,data) {
- return "class=\"next-term invisible\"";
-},"41":function(container,depth0,helpers,partials,data,blockParams,depths) {
- var stack1;
-
- return ((stack1 = helpers.unless.call(depth0 != null ? depth0 : (container.nullContext || {}),(data && data.first),{"name":"unless","hash":{},"fn":container.program(42, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + "\n"
- + ((stack1 = container.invokePartial(partials.kanji,depth0,{"name":"kanji","hash":{"root":(depths[1] != null ? depths[1].root : depths[1]),"addable":(depths[1] != null ? depths[1].addable : depths[1]),"debug":(depths[1] != null ? depths[1].debug : depths[1])},"data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
-},"42":function(container,depth0,helpers,partials,data) {
- return "<hr>";
-},"44":function(container,depth0,helpers,partials,data) {
- return "<p class=\"note\">No results found</p>\n";
-},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data,blockParams,depths) {
- var stack1;
-
- return "\n\n"
- + ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.definitions : depth0),{"name":"if","hash":{},"fn":container.program(32, data, 0, blockParams, depths),"inverse":container.program(44, data, 0, blockParams, depths),"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":["table"],"data":data}) || fn;
- fn = decorators.inline(fn,props,container,{"name":"inline","hash":{},"fn":container.program(10, data, 0, blockParams, depths),"inverse":container.noop,"args":["kanji"],"data":data}) || fn;
- return fn;
- }
-
-,"useDecorators":true,"usePartial":true,"useData":true,"useDepths":true});
templates['query-parser.html'] = template({"1":function(container,depth0,helpers,partials,data) {
var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {});
@@ -225,300 +52,4 @@ templates['query-parser.html'] = template({"1":function(container,depth0,helpers
}
,"useDecorators":true,"usePartial":true,"useData":true,"useDepths":true});
-templates['terms.html'] = template({"1":function(container,depth0,helpers,partials,data) {
- var stack1, helper, options, alias1=depth0 != null ? depth0 : (container.nullContext || {}), buffer =
- "<div class=\"dict-";
- stack1 = ((helper = (helper = helpers.sanitizeCssClass || (depth0 != null ? depth0.sanitizeCssClass : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"sanitizeCssClass","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(alias1,options) : helper));
- if (!helpers.sanitizeCssClass) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
- if (stack1 != null) { buffer += stack1; }
- return buffer + "\">\n"
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.definitionTags : depth0),{"name":"if","hash":{},"fn":container.program(4, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.only : depth0),{"name":"if","hash":{},"fn":container.program(9, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + ((stack1 = helpers["if"].call(alias1,((stack1 = (depth0 != null ? depth0.glossary : depth0)) != null ? stack1["1"] : stack1),{"name":"if","hash":{},"fn":container.program(13, data, 0),"inverse":container.program(19, data, 0),"data":data})) != null ? stack1 : "")
- + "</div>\n";
-},"2":function(container,depth0,helpers,partials,data) {
- var helper;
-
- return container.escapeExpression(((helper = (helper = helpers.dictionary || (depth0 != null ? depth0.dictionary : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"dictionary","hash":{},"data":data}) : helper)));
-},"4":function(container,depth0,helpers,partials,data) {
- var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {});
-
- return " <div "
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.compactGlossaries : depth0),{"name":"if","hash":{},"fn":container.program(5, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + ">\n"
- + ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.definitionTags : depth0),{"name":"each","hash":{},"fn":container.program(7, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + " </div>\n";
-},"5":function(container,depth0,helpers,partials,data) {
- return "class=\"compact-info\"";
-},"7":function(container,depth0,helpers,partials,data) {
- var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
-
- return " <span class=\"label label-default tag-"
- + alias4(((helper = (helper = helpers.category || (depth0 != null ? depth0.category : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"category","hash":{},"data":data}) : helper)))
- + "\" title=\""
- + alias4(((helper = (helper = helpers.notes || (depth0 != null ? depth0.notes : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"notes","hash":{},"data":data}) : helper)))
- + "\">"
- + alias4(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"name","hash":{},"data":data}) : helper)))
- + "</span>\n";
-},"9":function(container,depth0,helpers,partials,data) {
- var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {});
-
- return " <div "
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.compactGlossaries : depth0),{"name":"if","hash":{},"fn":container.program(5, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + ">\n ("
- + ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.only : depth0),{"name":"each","hash":{},"fn":container.program(10, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + " only)\n </div>\n";
-},"10":function(container,depth0,helpers,partials,data) {
- var stack1;
-
- return ((stack1 = container.lambda(depth0, depth0)) != null ? stack1 : "")
- + ((stack1 = helpers.unless.call(depth0 != null ? depth0 : (container.nullContext || {}),(data && data.last),{"name":"unless","hash":{},"fn":container.program(11, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + "\n";
-},"11":function(container,depth0,helpers,partials,data) {
- return ", ";
-},"13":function(container,depth0,helpers,partials,data) {
- var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {});
-
- return " <ul "
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.compactGlossaries : depth0),{"name":"if","hash":{},"fn":container.program(14, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + ">\n"
- + ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.glossary : depth0),{"name":"each","hash":{},"fn":container.program(16, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + " </ul>\n";
-},"14":function(container,depth0,helpers,partials,data) {
- return "class=\"compact-glossary\"";
-},"16":function(container,depth0,helpers,partials,data) {
- var stack1, helper, options, buffer =
- " <li><span class=\"glossary-item\">";
- stack1 = ((helper = (helper = helpers.multiLine || (depth0 != null ? depth0.multiLine : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"multiLine","hash":{},"fn":container.program(17, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));
- if (!helpers.multiLine) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
- if (stack1 != null) { buffer += stack1; }
- return buffer + "</span></li>\n";
-},"17":function(container,depth0,helpers,partials,data) {
- return container.escapeExpression(container.lambda(depth0, depth0));
-},"19":function(container,depth0,helpers,partials,data) {
- var stack1, helper, options, alias1=depth0 != null ? depth0 : (container.nullContext || {}), buffer =
- " <div class=\"glossary-item "
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.compactGlossaries : depth0),{"name":"if","hash":{},"fn":container.program(20, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + "\">";
- stack1 = ((helper = (helper = helpers.multiLine || (depth0 != null ? depth0.multiLine : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"multiLine","hash":{},"fn":container.program(22, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(alias1,options) : helper));
- if (!helpers.multiLine) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
- if (stack1 != null) { buffer += stack1; }
- return buffer + "</div>\n";
-},"20":function(container,depth0,helpers,partials,data) {
- return "compact-glossary";
-},"22":function(container,depth0,helpers,partials,data) {
- var stack1;
-
- return container.escapeExpression(container.lambda(((stack1 = (depth0 != null ? depth0.glossary : depth0)) != null ? stack1["0"] : stack1), depth0));
-},"24":function(container,depth0,helpers,partials,data,blockParams,depths) {
- var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {});
-
- return "<div class=\"entry\" data-type=\"term\">\n <div class=\"actions\">\n"
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.addable : depth0),{"name":"if","hash":{},"fn":container.program(25, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + ((stack1 = helpers.unless.call(alias1,(depth0 != null ? depth0.merged : depth0),{"name":"unless","hash":{},"fn":container.program(27, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + " <img src=\"/mixed/img/entry-current.svg\" class=\"current\" title=\"Current entry (Alt + Up/Down/Home/End/PgUp/PgDn)\" alt>\n </div>\n\n"
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.merged : depth0),{"name":"if","hash":{},"fn":container.program(30, data, 0, blockParams, depths),"inverse":container.program(45, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "")
- + "\n"
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.reasons : depth0),{"name":"if","hash":{},"fn":container.program(48, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + "\n"
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.frequencies : depth0),{"name":"if","hash":{},"fn":container.program(52, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + "\n <div class=\"glossary\">\n"
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.grouped : depth0),{"name":"if","hash":{},"fn":container.program(55, data, 0, blockParams, depths),"inverse":container.program(61, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "")
- + " </div>\n\n"
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.debug : depth0),{"name":"if","hash":{},"fn":container.program(64, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + "</div>\n";
-},"25":function(container,depth0,helpers,partials,data) {
- return " <a href=\"#\" class=\"action-view-note pending disabled\"><img src=\"/mixed/img/view-note.svg\" title=\"View added note (Alt + V)\" alt></a>\n <a href=\"#\" class=\"action-add-note pending disabled\" data-mode=\"term-kanji\"><img src=\"/mixed/img/add-term-kanji.svg\" title=\"Add expression (Alt + E)\" alt></a>\n <a href=\"#\" class=\"action-add-note pending disabled\" data-mode=\"term-kana\"><img src=\"/mixed/img/add-term-kana.svg\" title=\"Add reading (Alt + R)\" alt></a>\n";
-},"27":function(container,depth0,helpers,partials,data) {
- var stack1;
-
- return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.playback : depth0),{"name":"if","hash":{},"fn":container.program(28, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
-},"28":function(container,depth0,helpers,partials,data) {
- return " <a href=\"#\" class=\"action-play-audio\"><img src=\"/mixed/img/play-audio.svg\" title=\"Play audio (Alt + P)\" alt></a>\n";
-},"30":function(container,depth0,helpers,partials,data,blockParams,depths) {
- var stack1;
-
- return ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.expressions : depth0),{"name":"each","hash":{},"fn":container.program(31, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "");
-},"31":function(container,depth0,helpers,partials,data,blockParams,depths) {
- var stack1, helper, options, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", buffer =
- "<div class=\"expression expression-scan-toggle\"><span class=\"expression-"
- + container.escapeExpression(((helper = (helper = helpers.termFrequency || (depth0 != null ? depth0.termFrequency : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"termFrequency","hash":{},"data":data}) : helper)))
- + "\">";
- stack1 = ((helper = (helper = helpers.kanjiLinks || (depth0 != null ? depth0.kanjiLinks : depth0)) != null ? helper : alias2),(options={"name":"kanjiLinks","hash":{},"fn":container.program(32, data, 0, blockParams, depths),"inverse":container.noop,"data":data}),(typeof helper === alias3 ? helper.call(alias1,options) : helper));
- if (!helpers.kanjiLinks) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
- if (stack1 != null) { buffer += stack1; }
- return buffer + "</span><div class=\"peek-wrapper\">"
- + ((stack1 = helpers["if"].call(alias1,(depths[1] != null ? depths[1].playback : depths[1]),{"name":"if","hash":{},"fn":container.program(35, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.termTags : depth0),{"name":"if","hash":{},"fn":container.program(37, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.frequencies : depth0),{"name":"if","hash":{},"fn":container.program(40, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + "</div><span class=\""
- + ((stack1 = helpers["if"].call(alias1,(data && data.last),{"name":"if","hash":{},"fn":container.program(43, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + "\">、</span></div>";
-},"32":function(container,depth0,helpers,partials,data) {
- var stack1, helper, options;
-
- stack1 = ((helper = (helper = helpers.furigana || (depth0 != null ? depth0.furigana : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"furigana","hash":{},"fn":container.program(33, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));
- if (!helpers.furigana) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
- if (stack1 != null) { return stack1; }
- else { return ''; }
-},"33":function(container,depth0,helpers,partials,data) {
- var stack1;
-
- return ((stack1 = container.lambda(depth0, depth0)) != null ? stack1 : "");
-},"35":function(container,depth0,helpers,partials,data) {
- return "<a href=\"#\" class=\"action-play-audio\"><img src=\"/mixed/img/play-audio.svg\" title=\"Play audio\" alt></a>";
-},"37":function(container,depth0,helpers,partials,data) {
- var stack1;
-
- return "<div class=\"tags\">"
- + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.termTags : depth0),{"name":"each","hash":{},"fn":container.program(38, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + "</div>";
-},"38":function(container,depth0,helpers,partials,data) {
- var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
-
- return " <span class=\"label label-default tag-"
- + alias4(((helper = (helper = helpers.category || (depth0 != null ? depth0.category : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"category","hash":{},"data":data}) : helper)))
- + "\" title=\""
- + alias4(((helper = (helper = helpers.notes || (depth0 != null ? depth0.notes : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"notes","hash":{},"data":data}) : helper)))
- + "\">"
- + alias4(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"name","hash":{},"data":data}) : helper)))
- + "</span>\n";
-},"40":function(container,depth0,helpers,partials,data) {
- var stack1;
-
- return "<div class=\"frequencies\">"
- + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.frequencies : depth0),{"name":"each","hash":{},"fn":container.program(41, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + "</div>";
-},"41":function(container,depth0,helpers,partials,data) {
- var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
-
- return " <span class=\"label label-default tag-frequency\">"
- + alias4(((helper = (helper = helpers.dictionary || (depth0 != null ? depth0.dictionary : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"dictionary","hash":{},"data":data}) : helper)))
- + ":"
- + alias4(((helper = (helper = helpers.frequency || (depth0 != null ? depth0.frequency : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"frequency","hash":{},"data":data}) : helper)))
- + "</span>\n";
-},"43":function(container,depth0,helpers,partials,data) {
- return "invisible";
-},"45":function(container,depth0,helpers,partials,data) {
- var stack1, helper, options, alias1=depth0 != null ? depth0 : (container.nullContext || {}), buffer =
- " <div class=\"expression expression-scan-toggle\">";
- stack1 = ((helper = (helper = helpers.kanjiLinks || (depth0 != null ? depth0.kanjiLinks : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"kanjiLinks","hash":{},"fn":container.program(32, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(alias1,options) : helper));
- if (!helpers.kanjiLinks) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
- if (stack1 != null) { buffer += stack1; }
- return buffer + "</div>\n"
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.termTags : depth0),{"name":"if","hash":{},"fn":container.program(46, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
-},"46":function(container,depth0,helpers,partials,data) {
- var stack1;
-
- return " <div style=\"display: inline-block;\">\n"
- + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.termTags : depth0),{"name":"each","hash":{},"fn":container.program(7, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + " </div>\n";
-},"48":function(container,depth0,helpers,partials,data) {
- var stack1;
-
- return " <div class=\"reasons\">\n"
- + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.reasons : depth0),{"name":"each","hash":{},"fn":container.program(49, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + " </div>\n";
-},"49":function(container,depth0,helpers,partials,data) {
- var stack1;
-
- return " <span class=\"reasons\">"
- + container.escapeExpression(container.lambda(depth0, depth0))
- + "</span> "
- + ((stack1 = helpers.unless.call(depth0 != null ? depth0 : (container.nullContext || {}),(data && data.last),{"name":"unless","hash":{},"fn":container.program(50, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + "\n";
-},"50":function(container,depth0,helpers,partials,data) {
- return "&laquo;";
-},"52":function(container,depth0,helpers,partials,data) {
- var stack1;
-
- return " <div>\n"
- + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.frequencies : depth0),{"name":"each","hash":{},"fn":container.program(53, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + " </div>\n";
-},"53":function(container,depth0,helpers,partials,data) {
- var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
-
- return " <span class=\"label label-default tag-frequency\">"
- + alias4(((helper = (helper = helpers.dictionary || (depth0 != null ? depth0.dictionary : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"dictionary","hash":{},"data":data}) : helper)))
- + ":"
- + alias4(((helper = (helper = helpers.frequency || (depth0 != null ? depth0.frequency : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"frequency","hash":{},"data":data}) : helper)))
- + "</span>\n";
-},"55":function(container,depth0,helpers,partials,data,blockParams,depths) {
- var stack1;
-
- return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),((stack1 = (depth0 != null ? depth0.definitions : depth0)) != null ? stack1["1"] : stack1),{"name":"if","hash":{},"fn":container.program(56, data, 0, blockParams, depths),"inverse":container.program(59, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "");
-},"56":function(container,depth0,helpers,partials,data,blockParams,depths) {
- var stack1;
-
- return " <ol>\n"
- + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.definitions : depth0),{"name":"each","hash":{},"fn":container.program(57, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + " </ol>\n";
-},"57":function(container,depth0,helpers,partials,data,blockParams,depths) {
- var stack1;
-
- return " <li>"
- + ((stack1 = container.invokePartial(partials.definition,depth0,{"name":"definition","hash":{"compactGlossaries":(depths[1] != null ? depths[1].compactGlossaries : depths[1])},"data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
- + "</li>\n";
-},"59":function(container,depth0,helpers,partials,data) {
- var stack1;
-
- return ((stack1 = container.invokePartial(partials.definition,((stack1 = (depth0 != null ? depth0.definitions : depth0)) != null ? stack1["0"] : stack1),{"name":"definition","hash":{"compactGlossaries":(depth0 != null ? depth0.compactGlossaries : depth0)},"data":data,"indent":" ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
-},"61":function(container,depth0,helpers,partials,data,blockParams,depths) {
- var stack1;
-
- return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.merged : depth0),{"name":"if","hash":{},"fn":container.program(55, data, 0, blockParams, depths),"inverse":container.program(62, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "");
-},"62":function(container,depth0,helpers,partials,data) {
- var stack1;
-
- return ((stack1 = container.invokePartial(partials.definition,depth0,{"name":"definition","hash":{"compactGlossaries":(depth0 != null ? depth0.compactGlossaries : depth0)},"data":data,"indent":" ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
- + " ";
-},"64":function(container,depth0,helpers,partials,data) {
- var stack1, helper, options, buffer =
- " <pre>";
- stack1 = ((helper = (helper = helpers.dumpObject || (depth0 != null ? depth0.dumpObject : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"dumpObject","hash":{},"fn":container.program(33, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));
- if (!helpers.dumpObject) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
- if (stack1 != null) { buffer += stack1; }
- return buffer + "</pre>\n";
-},"66":function(container,depth0,helpers,partials,data,blockParams,depths) {
- var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {});
-
- return "<div class=\"term-navigation\">\n <a href=\"#\" "
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.source : depth0),{"name":"if","hash":{},"fn":container.program(67, data, 0, blockParams, depths),"inverse":container.program(69, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "")
- + "><img src=\"/mixed/img/source-term.svg\" title=\"Source term (Alt + B)\" alt></a>\n <a href=\"#\" "
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.next : depth0),{"name":"if","hash":{},"fn":container.program(71, data, 0, blockParams, depths),"inverse":container.program(73, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "")
- + "><img src=\"/mixed/img/source-term.svg\" style=\"transform: scaleX(-1);\" title=\"Next term (Alt + F)\" alt></a>\n</div>\n"
- + ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.definitions : depth0),{"name":"each","hash":{},"fn":container.program(75, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "");
-},"67":function(container,depth0,helpers,partials,data) {
- return "class=\"source-term\"";
-},"69":function(container,depth0,helpers,partials,data) {
- return "class=\"source-term invisible\"";
-},"71":function(container,depth0,helpers,partials,data) {
- return "class=\"next-term\"";
-},"73":function(container,depth0,helpers,partials,data) {
- return "class=\"next-term invisible\"";
-},"75":function(container,depth0,helpers,partials,data,blockParams,depths) {
- var stack1;
-
- return ((stack1 = helpers.unless.call(depth0 != null ? depth0 : (container.nullContext || {}),(data && data.first),{"name":"unless","hash":{},"fn":container.program(76, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + "\n"
- + ((stack1 = container.invokePartial(partials.term,depth0,{"name":"term","hash":{"compactGlossaries":(depths[1] != null ? depths[1].compactGlossaries : depths[1]),"playback":(depths[1] != null ? depths[1].playback : depths[1]),"addable":(depths[1] != null ? depths[1].addable : depths[1]),"merged":(depths[1] != null ? depths[1].merged : depths[1]),"grouped":(depths[1] != null ? depths[1].grouped : depths[1]),"debug":(depths[1] != null ? depths[1].debug : depths[1])},"data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
-},"76":function(container,depth0,helpers,partials,data) {
- return "<hr>";
-},"78":function(container,depth0,helpers,partials,data) {
- return "<p class=\"note\">No results found.</p>\n";
-},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data,blockParams,depths) {
- var stack1;
-
- return "\n\n"
- + ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.definitions : depth0),{"name":"if","hash":{},"fn":container.program(66, data, 0, blockParams, depths),"inverse":container.program(78, data, 0, blockParams, depths),"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":["definition"],"data":data}) || fn;
- fn = decorators.inline(fn,props,container,{"name":"inline","hash":{},"fn":container.program(24, data, 0, blockParams, depths),"inverse":container.noop,"args":["term"],"data":data}) || fn;
- return fn;
- }
-
-,"useDecorators":true,"usePartial":true,"useData":true,"useDepths":true});
})(); \ No newline at end of file
diff --git a/ext/bg/js/translator.js b/ext/bg/js/translator.js
index 7473c6ad..dfec54ac 100644
--- a/ext/bg/js/translator.js
+++ b/ext/bg/js/translator.js
@@ -121,16 +121,10 @@ class Translator {
dictTermsSort(result.definitions, dictionaries);
const expressions = [];
- for (const expression of result.expressions.keys()) {
- for (const reading of result.expressions.get(expression).keys()) {
- const termTags = result.expressions.get(expression).get(reading);
+ for (const [expression, readingMap] of result.expressions.entries()) {
+ for (const [reading, termTags] of readingMap.entries()) {
const score = termTags.map((tag) => tag.score).reduce((p, v) => p + v, 0);
- expressions.push({
- expression: expression,
- reading: reading,
- termTags: dictTagsSort(termTags),
- termFrequency: Translator.scoreToTermFrequency(score)
- });
+ expressions.push(Translator.createExpression(expression, reading, dictTagsSort(termTags), Translator.scoreToTermFrequency(score)));
}
}
@@ -157,10 +151,10 @@ class Translator {
async findTermsGrouped(text, details, options) {
const dictionaries = dictEnabledSet(options);
const titles = Object.keys(dictionaries);
- const [definitions, length] = await this.findTermsInternal(text, dictionaries, options.scanning.alphanumeric, details);
+ const [definitions, length] = await this.findTermsInternal(text, dictionaries, details, options);
const definitionsGrouped = dictTermsGroup(definitions, dictionaries);
- await this.buildTermFrequencies(definitionsGrouped, titles);
+ await this.buildTermMeta(definitionsGrouped, titles);
if (options.general.compactTags) {
for (const definition of definitionsGrouped) {
@@ -175,7 +169,7 @@ class Translator {
const dictionaries = dictEnabledSet(options);
const secondarySearchTitles = Object.keys(options.dictionaries).filter((dict) => options.dictionaries[dict].allowSecondarySearches);
const titles = Object.keys(dictionaries);
- const [definitions, length] = await this.findTermsInternal(text, dictionaries, options.scanning.alphanumeric, details);
+ const [definitions, length] = await this.findTermsInternal(text, dictionaries, details, options);
const {sequencedDefinitions, defaultDefinitions} = await this.getSequencedDefinitions(definitions, options.general.mainDictionary);
const definitionsMerged = [];
const mergedByTermIndices = new Set();
@@ -194,11 +188,11 @@ class Translator {
const strayDefinitions = defaultDefinitions.filter((definition, index) => !mergedByTermIndices.has(index));
for (const groupedDefinition of dictTermsGroup(strayDefinitions, dictionaries)) {
- groupedDefinition.expressions = [{expression: groupedDefinition.expression, reading: groupedDefinition.reading}];
+ groupedDefinition.expressions = [Translator.createExpression(groupedDefinition.expression, groupedDefinition.reading)];
definitionsMerged.push(groupedDefinition);
}
- await this.buildTermFrequencies(definitionsMerged, titles);
+ await this.buildTermMeta(definitionsMerged, titles);
if (options.general.compactTags) {
for (const definition of definitionsMerged) {
@@ -212,26 +206,24 @@ class Translator {
async findTermsSplit(text, details, options) {
const dictionaries = dictEnabledSet(options);
const titles = Object.keys(dictionaries);
- const [definitions, length] = await this.findTermsInternal(text, dictionaries, options.scanning.alphanumeric, details);
+ const [definitions, length] = await this.findTermsInternal(text, dictionaries, details, options);
- await this.buildTermFrequencies(definitions, titles);
+ await this.buildTermMeta(definitions, titles);
return [definitions, length];
}
- async findTermsInternal(text, dictionaries, alphanumeric, details) {
- if (!alphanumeric && text.length > 0) {
- const c = text[0];
- if (!jpIsKana(c) && !jpIsKanji(c)) {
- return [[], 0];
- }
+ async findTermsInternal(text, dictionaries, details, options) {
+ text = Translator.getSearchableText(text, options);
+ if (text.length === 0) {
+ return [[], 0];
}
const titles = Object.keys(dictionaries);
const deinflections = (
details.wildcard ?
await this.findTermWildcard(text, titles, details.wildcard) :
- await this.findTermDeinflections(text, titles)
+ await this.findTermDeinflections(text, titles, options)
);
let definitions = [];
@@ -241,14 +233,19 @@ class Translator {
definitionTags.push(dictTagBuildSource(definition.dictionary));
const termTags = await this.expandTags(definition.termTags, definition.dictionary);
+ const {expression, reading} = definition;
+ const furiganaSegments = jpDistributeFurigana(expression, reading);
+
definitions.push({
source: deinflection.source,
+ rawSource: deinflection.rawSource,
reasons: deinflection.reasons,
score: definition.score,
id: definition.id,
dictionary: definition.dictionary,
- expression: definition.expression,
- reading: definition.reading,
+ expression,
+ reading,
+ furiganaSegments,
glossary: definition.glossary,
definitionTags: dictTagsSort(definitionTags),
termTags: dictTagsSort(termTags),
@@ -262,7 +259,7 @@ class Translator {
let length = 0;
for (const definition of definitions) {
- length = Math.max(length, definition.source.length);
+ length = Math.max(length, definition.rawSource.length);
}
return [definitions, length];
@@ -276,6 +273,7 @@ class Translator {
return [{
source: text,
+ rawSource: text,
term: text,
rules: 0,
definitions,
@@ -283,9 +281,8 @@ class Translator {
}];
}
- async findTermDeinflections(text, titles) {
- const text2 = jpKatakanaToHiragana(text);
- const deinflections = (text === text2 ? this.getDeinflections(text) : this.getDeinflections2(text, text2));
+ async findTermDeinflections(text, titles, options) {
+ const deinflections = this.getAllDeinflections(text, options);
if (deinflections.length === 0) {
return [];
@@ -293,17 +290,15 @@ class Translator {
const uniqueDeinflectionTerms = [];
const uniqueDeinflectionArrays = [];
- const uniqueDeinflectionsMap = {};
+ const uniqueDeinflectionsMap = new Map();
for (const deinflection of deinflections) {
const term = deinflection.term;
- let deinflectionArray;
- if (hasOwn(uniqueDeinflectionsMap, term)) {
- deinflectionArray = uniqueDeinflectionsMap[term];
- } else {
+ let deinflectionArray = uniqueDeinflectionsMap.get(term);
+ if (typeof deinflectionArray === 'undefined') {
deinflectionArray = [];
uniqueDeinflectionTerms.push(term);
uniqueDeinflectionArrays.push(deinflectionArray);
- uniqueDeinflectionsMap[term] = deinflectionArray;
+ uniqueDeinflectionsMap.set(term, deinflectionArray);
}
deinflectionArray.push(deinflection);
}
@@ -323,30 +318,77 @@ class Translator {
return deinflections.filter((e) => e.definitions.length > 0);
}
- getDeinflections(text) {
+ getAllDeinflections(text, options) {
+ const translationOptions = options.translation;
+ const textOptionVariantArray = [
+ Translator.getTextOptionEntryVariants(translationOptions.convertHalfWidthCharacters),
+ Translator.getTextOptionEntryVariants(translationOptions.convertNumericCharacters),
+ Translator.getTextOptionEntryVariants(translationOptions.convertAlphabeticCharacters),
+ Translator.getTextOptionEntryVariants(translationOptions.convertHiraganaToKatakana),
+ Translator.getTextOptionEntryVariants(translationOptions.convertKatakanaToHiragana)
+ ];
+
const deinflections = [];
+ const used = new Set();
+ for (const [halfWidth, numeric, alphabetic, katakana, hiragana] of Translator.getArrayVariants(textOptionVariantArray)) {
+ let text2 = text;
+ let sourceMapping = null;
+ if (halfWidth) {
+ if (sourceMapping === null) { sourceMapping = Translator.createTextSourceMapping(text2); }
+ text2 = jpConvertHalfWidthKanaToFullWidth(text2, sourceMapping);
+ }
+ if (numeric) {
+ text2 = jpConvertNumericTofullWidth(text2);
+ }
+ if (alphabetic) {
+ if (sourceMapping === null) { sourceMapping = Translator.createTextSourceMapping(text2); }
+ text2 = jpConvertAlphabeticToKana(text2, sourceMapping);
+ }
+ if (katakana) {
+ text2 = jpHiraganaToKatakana(text2);
+ }
+ if (hiragana) {
+ text2 = jpKatakanaToHiragana(text2);
+ }
- for (let i = text.length; i > 0; --i) {
- const textSubstring = text.substring(0, i);
- deinflections.push(...this.deinflector.deinflect(textSubstring));
+ for (let i = text2.length; i > 0; --i) {
+ const text2Substring = text2.substring(0, i);
+ if (used.has(text2Substring)) { break; }
+ used.add(text2Substring);
+ for (const deinflection of this.deinflector.deinflect(text2Substring)) {
+ deinflection.rawSource = Translator.getDeinflectionRawSource(text, i, sourceMapping);
+ deinflections.push(deinflection);
+ }
+ }
}
-
return deinflections;
}
- getDeinflections2(text1, text2) {
- const deinflections = [];
+ static getTextOptionEntryVariants(value) {
+ switch (value) {
+ case 'true': return [true];
+ case 'variant': return [false, true];
+ default: return [false];
+ }
+ }
- for (let i = text1.length; i > 0; --i) {
- const text1Substring = text1.substring(0, i);
- const text2Substring = text2.substring(0, i);
- deinflections.push(...this.deinflector.deinflect(text1Substring));
- if (text1Substring !== text2Substring) {
- deinflections.push(...this.deinflector.deinflect(text2Substring));
- }
+ static getDeinflectionRawSource(source, length, sourceMapping) {
+ if (sourceMapping === null) {
+ return source.substring(0, length);
}
- return deinflections;
+ let result = '';
+ let index = 0;
+ for (let i = 0; i < length; ++i) {
+ const c = sourceMapping[i];
+ result += source.substring(index, index + c);
+ index += c;
+ }
+ return result;
+ }
+
+ static createTextSourceMapping(text) {
+ return new Array(text.length).fill(1);
}
async findKanji(text, options) {
@@ -370,31 +412,23 @@ class Translator {
definitions.sort((a, b) => a.index - b.index);
}
- const kanjiList2 = [];
for (const definition of definitions) {
- kanjiList2.push(definition.character);
-
const tags = await this.expandTags(definition.tags, definition.dictionary);
tags.push(dictTagBuildSource(definition.dictionary));
+ dictTagsSort(tags);
- definition.tags = dictTagsSort(tags);
- definition.stats = await this.expandStats(definition.stats, definition.dictionary);
- definition.frequencies = [];
- }
+ const stats = await this.expandStats(definition.stats, definition.dictionary);
- for (const meta of await this.database.findKanjiMetaBulk(kanjiList2, titles)) {
- if (meta.mode !== 'freq') { continue; }
- definitions[meta.index].frequencies.push({
- character: meta.character,
- frequency: meta.data,
- dictionary: meta.dictionary
- });
+ definition.tags = tags;
+ definition.stats = stats;
}
+ await this.buildKanjiMeta(definitions, titles);
+
return definitions;
}
- async buildTermFrequencies(definitions, titles) {
+ async buildTermMeta(definitions, titles) {
const terms = [];
for (const definition of definitions) {
if (definition.expressions) {
@@ -411,34 +445,48 @@ class Translator {
// Create mapping of unique terms
const expressionsUnique = [];
const termsUnique = [];
- const termsUniqueMap = {};
+ const termsUniqueMap = new Map();
for (let i = 0, ii = terms.length; i < ii; ++i) {
const term = terms[i];
const expression = term.expression;
- term.frequencies = [];
-
- if (hasOwn(termsUniqueMap, expression)) {
- termsUniqueMap[expression].push(term);
- } else {
- const termList = [term];
+ let termList = termsUniqueMap.get(expression);
+ if (typeof termList === 'undefined') {
+ termList = [];
expressionsUnique.push(expression);
termsUnique.push(termList);
termsUniqueMap[expression] = termList;
}
+ termList.push(term);
+
+ // New data
+ term.frequencies = [];
}
const metas = await this.database.findTermMetaBulk(expressionsUnique, titles);
- for (const meta of metas) {
- if (meta.mode !== 'freq') {
- continue;
+ for (const {expression, mode, data, dictionary, index} of metas) {
+ switch (mode) {
+ case 'freq':
+ for (const term of termsUnique[index]) {
+ term.frequencies.push({expression, frequency: data, dictionary});
+ }
+ break;
}
+ }
+ }
- for (const term of termsUnique[meta.index]) {
- term.frequencies.push({
- expression: meta.expression,
- frequency: meta.data,
- dictionary: meta.dictionary
- });
+ async buildKanjiMeta(definitions, titles) {
+ const kanjiList = [];
+ for (const definition of definitions) {
+ kanjiList.push(definition.character);
+ definition.frequencies = [];
+ }
+
+ const metas = await this.database.findKanjiMetaBulk(kanjiList, titles);
+ for (const {character, mode, data, dictionary, index} of metas) {
+ switch (mode) {
+ case 'freq':
+ definitions[index].frequencies.push({character, frequency: data, dictionary});
+ break;
}
}
}
@@ -504,6 +552,17 @@ class Translator {
return tagMetaList;
}
+ static createExpression(expression, reading, termTags=null, termFrequency=null) {
+ const furiganaSegments = jpDistributeFurigana(expression, reading);
+ return {
+ expression,
+ reading,
+ furiganaSegments,
+ termTags,
+ termFrequency
+ };
+ }
+
static scoreToTermFrequency(score) {
if (score > 0) {
return 'popular';
@@ -518,4 +577,38 @@ class Translator {
const pos = name.indexOf(':');
return (pos >= 0 ? name.substring(0, pos) : name);
}
+
+ static *getArrayVariants(arrayVariants) {
+ const ii = arrayVariants.length;
+
+ let total = 1;
+ for (let i = 0; i < ii; ++i) {
+ total *= arrayVariants[i].length;
+ }
+
+ for (let a = 0; a < total; ++a) {
+ const variant = [];
+ let index = a;
+ for (let i = 0; i < ii; ++i) {
+ const entryVariants = arrayVariants[i];
+ variant.push(entryVariants[index % entryVariants.length]);
+ index = Math.floor(index / entryVariants.length);
+ }
+ yield variant;
+ }
+ }
+
+ static getSearchableText(text, options) {
+ if (!options.scanning.alphanumeric) {
+ const ii = text.length;
+ for (let i = 0; i < ii; ++i) {
+ if (!jpIsCharCodeJapanese(text.charCodeAt(i))) {
+ text = text.substring(0, i);
+ break;
+ }
+ }
+ }
+
+ return text;
+ }
}
diff --git a/ext/bg/search.html b/ext/bg/search.html
index 409243dd..74afbb68 100644
--- a/ext/bg/search.html
+++ b/ext/bg/search.html
@@ -43,9 +43,7 @@
</span>
</form>
- <div id="spinner">
- <img src="/mixed/img/spinner.gif">
- </div>
+ <div id="spinner" hidden><img src="/mixed/img/spinner.gif"></div>
<div class="scan-disable">
<div id="query-parser-select" class="input-group"></div>
@@ -54,7 +52,18 @@
<hr>
+ <div id="navigation-header" class="navigation-header" hidden><div class="navigation-header-actions">
+ <button class="action-button action-previous"><img src="/mixed/img/source-term.svg" class="icon-image" title="Source term (Alt + B)" alt></button>
+ <button class="action-button action-next"><img src="/mixed/img/source-term.svg" class="icon-image" title="Next term (Alt + F)" alt></button>
+ </div></div><div class="navigation-header-spacer"></div>
+
<div id="content"></div>
+
+ <div id="no-results" hidden>
+ <div class="entry">
+ <p>No results found.</p>
+ </div>
+ </div>
</div>
<script src="/mixed/lib/handlebars.min.js"></script>
@@ -72,6 +81,7 @@
<script src="/mixed/js/audio.js"></script>
<script src="/mixed/js/display-context.js"></script>
<script src="/mixed/js/display.js"></script>
+ <script src="/mixed/js/display-generator.js"></script>
<script src="/mixed/js/japanese.js"></script>
<script src="/mixed/js/scroll.js"></script>
<script src="/mixed/js/text-scanner.js"></script>
diff --git a/ext/bg/settings.html b/ext/bg/settings.html
index 4c973674..3e06d4b5 100644
--- a/ext/bg/settings.html
+++ b/ext/bg/settings.html
@@ -151,6 +151,14 @@
</div>
<div class="checkbox options-advanced">
+ <label><input type="checkbox" id="popup-scale-relative-to-page-zoom"> Change popup size relative to page zoom level</label>
+ </div>
+
+ <div class="checkbox options-advanced">
+ <label><input type="checkbox" id="popup-scale-relative-to-visual-viewport"> Change popup size relative to page viewport</label>
+ </div>
+
+ <div class="checkbox options-advanced">
<label><input type="checkbox" id="show-debug-info"> Show debug information</label>
</div>
@@ -171,6 +179,11 @@
</select>
</div>
+ <div class="form-group">
+ <label for="popup-scaling-factor">Popup size multiplier</label>
+ <input type="number" min="0" id="popup-scaling-factor" class="form-control">
+ </div>
+
<div class="form-group options-advanced">
<label for="max-displayed-results">Maximum displayed results</label>
<input type="number" min="1" id="max-displayed-results" class="form-control">
@@ -384,6 +397,81 @@
</div>
</div>
+ <div>
+ <h3>Translation Options</h3>
+
+ <p class="help-block">
+ The following options can be used during the translation process to provide alternate versions of the input text to search for.
+ This can be helpful when the input text doesn't exactly match the term or expression found in the database.
+ </p>
+
+ <p class="help-block">
+ The conversion options below are listed in the order that the conversions are applied to the input text.
+ Each conversion has three possible values:
+ </p>
+
+ <ul class="help-block">
+ <li>
+ <strong>Disabled</strong><br>
+ This conversion will never be applied to the input text.
+ </li>
+ <li>
+ <strong>Enabled</strong><br>
+ This conversion will always be applied to the input text.
+ </li>
+ <li>
+ <strong>Use both variants</strong><br>
+ The translator will check the database for two variations: the raw input text and the converted input text.
+ When multiple options use variants, the translator will search for combinations of the converted text.
+ </li>
+ </ul>
+
+ <div class="form-group">
+ <label for="translation-convert-half-width-characters">Convert half width characters to full width <span class="label-light">(ヨミチャン &rarr; ヨミチャン)</span></label>
+ <select class="form-control" id="translation-convert-half-width-characters">
+ <option value="false">Disabled</option>
+ <option value="true">Enabled</option>
+ <option value="variant">Use both variants</option>
+ </select>
+ </div>
+
+ <div class="form-group">
+ <label for="translation-convert-numeric-characters">Convert numeric characters to full width <span class="label-light">(1234 &rarr; 1234)</span></label>
+ <select class="form-control" id="translation-convert-numeric-characters">
+ <option value="false">Disabled</option>
+ <option value="true">Enabled</option>
+ <option value="variant">Use both variants</option>
+ </select>
+ </div>
+
+ <div class="form-group">
+ <label for="translation-convert-alphabetic-characters">Convert alphabetic characters to hiragana <span class="label-light">(yomichan &rarr; よみちゃん)</span></label>
+ <select class="form-control" id="translation-convert-alphabetic-characters">
+ <option value="false">Disabled</option>
+ <option value="true">Enabled</option>
+ <option value="variant">Use both variants</option>
+ </select>
+ </div>
+
+ <div class="form-group">
+ <label for="translation-convert-hiragana-to-katakana">Convert hiragana to katakana <span class="label-light">(よみちゃん &rarr; ヨミチャン)</span></label>
+ <select class="form-control" id="translation-convert-hiragana-to-katakana">
+ <option value="false">Disabled</option>
+ <option value="true">Enabled</option>
+ <option value="variant">Use both variants</option>
+ </select>
+ </div>
+
+ <div class="form-group">
+ <label for="translation-convert-katakana-to-hiragana">Convert katakana to hiragana <span class="label-light">(ヨミチャン &rarr; よみちゃん)</span></label>
+ <select class="form-control" id="translation-convert-katakana-to-hiragana">
+ <option value="false">Disabled</option>
+ <option value="true">Enabled</option>
+ <option value="variant">Use both variants</option>
+ </select>
+ </div>
+ </div>
+
<div id="popup-content-scanning">
<h3>Popup Content Scanning Options</h3>
@@ -438,12 +526,17 @@
<label><input type="checkbox" id="parsing-mecab-enable"> Enable text parsing using MeCab</label>
</div>
+ <div class="checkbox">
+ <label><input type="checkbox" id="parsing-term-spacing"> Enable small spaces between parsed words</label>
+ </div>
+
<div class="form-group">
<label for="parsing-reading-mode">Reading mode</label>
<select class="form-control" id="parsing-reading-mode">
<option value="hiragana">ひらがな</option>
<option value="katakana">カタカナ</option>
<option value="romaji">Romaji</option>
+ <option value="none">Disabled</option>
</select>
</div>
</div>