diff options
author | Alex Yatskov <alex@foosoft.net> | 2020-01-26 11:29:30 -0800 |
---|---|---|
committer | Alex Yatskov <alex@foosoft.net> | 2020-01-26 11:29:30 -0800 |
commit | 0c5b9b1fa1599cbf769d96cdebc226310f9dd8bc (patch) | |
tree | e734e2c3005078dbc248b541d357a934baa8a116 /ext/bg | |
parent | 2a12036ca305044291f1f4105d6a8d249848b210 (diff) | |
parent | 0cf1cf3aa094585bd6db8db2c1f229ba0ea37b6e (diff) |
Merge branch 'master' into testing
Diffstat (limited to 'ext/bg')
-rw-r--r-- | ext/bg/css/settings.css | 17 | ||||
-rw-r--r-- | ext/bg/data/options-schema.json | 60 | ||||
-rw-r--r-- | ext/bg/js/api.js | 4 | ||||
-rw-r--r-- | ext/bg/js/backend.js | 50 | ||||
-rw-r--r-- | ext/bg/js/database.js | 17 | ||||
-rw-r--r-- | ext/bg/js/dictionary.js | 5 | ||||
-rw-r--r-- | ext/bg/js/handlebars.js | 2 | ||||
-rw-r--r-- | ext/bg/js/mecab.js | 23 | ||||
-rw-r--r-- | ext/bg/js/options.js | 12 | ||||
-rw-r--r-- | ext/bg/js/request.js | 22 | ||||
-rw-r--r-- | ext/bg/js/search-frontend.js | 2 | ||||
-rw-r--r-- | ext/bg/js/search-query-parser.js | 117 | ||||
-rw-r--r-- | ext/bg/js/search.js | 11 | ||||
-rw-r--r-- | ext/bg/js/settings/anki.js | 26 | ||||
-rw-r--r-- | ext/bg/js/settings/main.js | 20 | ||||
-rw-r--r-- | ext/bg/js/settings/popup-preview-frame.js | 14 | ||||
-rw-r--r-- | ext/bg/js/templates.js | 469 | ||||
-rw-r--r-- | ext/bg/js/translator.js | 255 | ||||
-rw-r--r-- | ext/bg/search.html | 16 | ||||
-rw-r--r-- | ext/bg/settings.html | 93 |
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 "«"; -},"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">(ヨミチャン → ヨミチャン)</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 → 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 → よみちゃん)</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">(よみちゃん → ヨミチャン)</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">(ヨミチャン → よみちゃん)</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> |