summaryrefslogtreecommitdiff
path: root/ext/bg/js
diff options
context:
space:
mode:
authorAlex Yatskov <alex@foosoft.net>2019-10-20 11:23:20 -0700
committerAlex Yatskov <alex@foosoft.net>2019-10-20 11:23:20 -0700
commit438498435227cfa59cf9ed3430045b288cd2a7c0 (patch)
tree6a05520e5d6fa8d26d372673a9ed3e5d2da7e3fd /ext/bg/js
parent06d7713189be9eb51669d3842b78278371e6cfa4 (diff)
parentd32fd1381b6cd5141a21c22f9ef639b2fe9774fb (diff)
Merge branch 'master' into testing
Diffstat (limited to 'ext/bg/js')
-rw-r--r--ext/bg/js/api.js174
-rw-r--r--ext/bg/js/audio.js38
-rw-r--r--ext/bg/js/backend.js11
-rw-r--r--ext/bg/js/context.js47
-rw-r--r--ext/bg/js/database.js167
-rw-r--r--ext/bg/js/dictionary.js4
-rw-r--r--ext/bg/js/handlebars.js5
-rw-r--r--ext/bg/js/options.js10
-rw-r--r--ext/bg/js/search-frontend.js5
-rw-r--r--ext/bg/js/search.js71
-rw-r--r--ext/bg/js/settings-popup-preview.js183
-rw-r--r--ext/bg/js/settings.js233
-rw-r--r--ext/bg/js/templates.js223
-rw-r--r--ext/bg/js/translator.js306
-rw-r--r--ext/bg/js/util.js2
15 files changed, 1070 insertions, 409 deletions
diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js
index f768e6f9..93d9c155 100644
--- a/ext/bg/js/api.js
+++ b/ext/bg/js/api.js
@@ -144,24 +144,46 @@ async function apiTemplateRender(template, data, dynamic) {
}
}
-async function apiCommandExec(command) {
+async function apiCommandExec(command, params) {
const handlers = apiCommandExec.handlers;
if (handlers.hasOwnProperty(command)) {
const handler = handlers[command];
- handler();
+ handler(params);
}
}
apiCommandExec.handlers = {
- search: () => {
- chrome.tabs.create({url: chrome.extension.getURL('/bg/search.html')});
+ search: async (params) => {
+ const url = chrome.extension.getURL('/bg/search.html');
+ if (!(params && params.newTab)) {
+ try {
+ const tab = await apiFindTab(1000, (url2) => (
+ url2 !== null &&
+ url2.startsWith(url) &&
+ (url2.length === url.length || url2[url.length] === '?' || url2[url.length] === '#')
+ ));
+ if (tab !== null) {
+ await apiFocusTab(tab);
+ return;
+ }
+ } catch (e) {
+ // NOP
+ }
+ }
+ chrome.tabs.create({url});
},
help: () => {
chrome.tabs.create({url: 'https://foosoft.net/projects/yomichan/'});
},
- options: () => {
- chrome.runtime.openOptionsPage();
+ options: (params) => {
+ if (!(params && params.newTab)) {
+ chrome.runtime.openOptionsPage();
+ } else {
+ const manifest = chrome.runtime.getManifest();
+ const url = chrome.extension.getURL(manifest.options_ui.page);
+ chrome.tabs.create({url});
+ }
},
toggle: async () => {
@@ -176,7 +198,7 @@ apiCommandExec.handlers = {
};
async function apiAudioGetUrl(definition, source, optionsContext) {
- return audioBuildUrl(definition, source, optionsContext);
+ return audioGetUrl(definition, source, optionsContext);
}
async function apiInjectScreenshot(definition, fields, screenshot) {
@@ -241,3 +263,141 @@ function apiFrameInformationGet(sender) {
const frameId = sender.frameId;
return Promise.resolve({frameId});
}
+
+function apiInjectStylesheet(css, sender) {
+ if (!sender.tab) {
+ return Promise.reject(new Error('Invalid tab'));
+ }
+
+ const tabId = sender.tab.id;
+ const frameId = sender.frameId;
+ const details = {
+ code: css,
+ runAt: 'document_start',
+ cssOrigin: 'user',
+ allFrames: false
+ };
+ if (typeof frameId === 'number') {
+ details.frameId = frameId;
+ }
+
+ return new Promise((resolve, reject) => {
+ chrome.tabs.insertCSS(tabId, details, () => {
+ const e = chrome.runtime.lastError;
+ if (e) {
+ reject(new Error(e.message));
+ } else {
+ resolve();
+ }
+ });
+ });
+}
+
+async function apiGetEnvironmentInfo() {
+ const browser = await apiGetBrowser();
+ const platform = await new Promise((resolve) => chrome.runtime.getPlatformInfo(resolve));
+ return {
+ browser,
+ platform: {
+ os: platform.os
+ }
+ };
+}
+
+async function apiGetBrowser() {
+ if (EXTENSION_IS_BROWSER_EDGE) {
+ return 'edge';
+ }
+ if (typeof browser !== 'undefined') {
+ try {
+ const info = await browser.runtime.getBrowserInfo();
+ if (info.name === 'Fennec') {
+ return 'firefox-mobile';
+ }
+ } catch (e) { }
+ return 'firefox';
+ } else {
+ return 'chrome';
+ }
+}
+
+function apiGetTabUrl(tab) {
+ return new Promise((resolve) => {
+ chrome.tabs.sendMessage(tab.id, {action: 'getUrl'}, {frameId: 0}, (response) => {
+ let url = null;
+ if (!chrome.runtime.lastError) {
+ url = (response !== null && typeof response === 'object' && !Array.isArray(response) ? response.url : null);
+ if (url !== null && typeof url !== 'string') {
+ url = null;
+ }
+ }
+ resolve({tab, url});
+ });
+ });
+}
+
+async function apiFindTab(timeout, checkUrl) {
+ // This function works around the need to have the "tabs" permission to access tab.url.
+ const tabs = await new Promise((resolve) => chrome.tabs.query({}, resolve));
+ let matchPromiseResolve = null;
+ const matchPromise = new Promise((resolve) => { matchPromiseResolve = resolve; });
+
+ const checkTabUrl = ({tab, url}) => {
+ if (checkUrl(url, tab)) {
+ matchPromiseResolve(tab);
+ }
+ };
+
+ const promises = [];
+ for (const tab of tabs) {
+ const promise = apiGetTabUrl(tab);
+ promise.then(checkTabUrl);
+ promises.push(promise);
+ }
+
+ const racePromises = [
+ matchPromise,
+ Promise.all(promises).then(() => null)
+ ];
+ if (typeof timeout === 'number') {
+ racePromises.push(new Promise((resolve) => setTimeout(() => resolve(null), timeout)));
+ }
+
+ return await Promise.race(racePromises);
+}
+
+async function apiFocusTab(tab) {
+ await new Promise((resolve, reject) => {
+ chrome.tabs.update(tab.id, {active: true}, () => {
+ const e = chrome.runtime.lastError;
+ if (e) { reject(e); }
+ else { resolve(); }
+ });
+ });
+
+ if (!(typeof chrome.windows === 'object' && chrome.windows !== null)) {
+ // Windows not supported (e.g. on Firefox mobile)
+ return;
+ }
+
+ try {
+ const tabWindow = await new Promise((resolve) => {
+ chrome.windows.get(tab.windowId, {}, (tabWindow) => {
+ const e = chrome.runtime.lastError;
+ if (e) { reject(e); }
+ else { resolve(tabWindow); }
+ });
+ });
+ if (!tabWindow.focused) {
+ await new Promise((resolve, reject) => {
+ chrome.windows.update(tab.windowId, {focused: true}, () => {
+ const e = chrome.runtime.lastError;
+ if (e) { reject(e); }
+ else { resolve(); }
+ });
+ });
+ }
+ } catch (e) {
+ // Edge throws exception for no reason here.
+ }
+}
diff --git a/ext/bg/js/audio.js b/ext/bg/js/audio.js
index 9e0ae67c..3efcce46 100644
--- a/ext/bg/js/audio.js
+++ b/ext/bg/js/audio.js
@@ -86,6 +86,24 @@ const audioUrlBuilders = {
throw new Error('Failed to find audio URL');
},
+ 'text-to-speech': async (definition, optionsContext) => {
+ const options = await apiOptionsGet(optionsContext);
+ const voiceURI = options.audio.textToSpeechVoice;
+ if (!voiceURI) {
+ throw new Error('No voice');
+ }
+
+ return `tts:?text=${encodeURIComponent(definition.expression)}&voice=${encodeURIComponent(voiceURI)}`;
+ },
+ 'text-to-speech-reading': async (definition, optionsContext) => {
+ const options = await apiOptionsGet(optionsContext);
+ const voiceURI = options.audio.textToSpeechVoice;
+ if (!voiceURI) {
+ throw new Error('No voice');
+ }
+
+ return `tts:?text=${encodeURIComponent(definition.reading || definition.expression)}&voice=${encodeURIComponent(voiceURI)}`;
+ },
'custom': async (definition, optionsContext) => {
const options = await apiOptionsGet(optionsContext);
const customSourceUrl = options.audio.customSourceUrl;
@@ -93,20 +111,14 @@ const audioUrlBuilders = {
}
};
-async function audioBuildUrl(definition, mode, optionsContext, cache={}) {
- const cacheKey = `${mode}:${definition.expression}`;
- if (cache.hasOwnProperty(cacheKey)) {
- return Promise.resolve(cache[cacheKey]);
- }
-
+async function audioGetUrl(definition, mode, optionsContext, download) {
if (audioUrlBuilders.hasOwnProperty(mode)) {
const handler = audioUrlBuilders[mode];
- return handler(definition, optionsContext).then(
- (url) => {
- cache[cacheKey] = url;
- return url;
- },
- () => null);
+ try {
+ return await handler(definition, optionsContext, download);
+ } catch (e) {
+ // NOP
+ }
}
return null;
}
@@ -163,7 +175,7 @@ async function audioInject(definition, fields, sources, optionsContext) {
audioSourceDefinition = definition.expressions[0];
}
- const {url} = await audioGetFromSources(audioSourceDefinition, sources, optionsContext, false);
+ const {url} = await audioGetFromSources(audioSourceDefinition, sources, optionsContext, true);
if (url !== null) {
const filename = audioBuildFilename(audioSourceDefinition);
if (filename !== null) {
diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js
index 453f4282..f29230a2 100644
--- a/ext/bg/js/backend.js
+++ b/ext/bg/js/backend.js
@@ -73,9 +73,10 @@ class Backend {
if (handlers.hasOwnProperty(action)) {
const handler = handlers[action];
const promise = handler(params, sender);
- promise
- .then(result => callback({result}))
- .catch(error => callback(errorToJson(error)));
+ promise.then(
+ result => callback({result}),
+ error => callback({error: errorToJson(error)})
+ );
}
return true;
@@ -180,11 +181,13 @@ Backend.messageHandlers = {
definitionsAddable: ({definitions, modes, optionsContext}) => apiDefinitionsAddable(definitions, modes, optionsContext),
noteView: ({noteId}) => apiNoteView(noteId),
templateRender: ({template, data, dynamic}) => apiTemplateRender(template, data, dynamic),
- commandExec: ({command}) => apiCommandExec(command),
+ commandExec: ({command, params}) => apiCommandExec(command, params),
audioGetUrl: ({definition, source, optionsContext}) => apiAudioGetUrl(definition, source, optionsContext),
screenshotGet: ({options}, sender) => apiScreenshotGet(options, sender),
forward: ({action, params}, sender) => apiForward(action, params, sender),
frameInformationGet: (params, sender) => apiFrameInformationGet(sender),
+ injectStylesheet: ({css}, sender) => apiInjectStylesheet(css, sender),
+ getEnvironmentInfo: () => apiGetEnvironmentInfo()
};
window.yomichan_backend = new Backend();
diff --git a/ext/bg/js/context.js b/ext/bg/js/context.js
index 0f88e9c0..8e1dbce6 100644
--- a/ext/bg/js/context.js
+++ b/ext/bg/js/context.js
@@ -17,10 +17,47 @@
*/
+function showExtensionInfo() {
+ const node = document.getElementById('extension-info');
+ if (node === null) { return; }
+
+ const manifest = chrome.runtime.getManifest();
+ node.textContent = `${manifest.name} v${manifest.version}`;
+}
+
+function setupButtonEvents(selector, command, url) {
+ const node = $(selector);
+ node.on('click', (e) => {
+ if (e.button !== 0) { return; }
+ apiCommandExec(command, {newTab: e.ctrlKey});
+ e.preventDefault();
+ })
+ .on('auxclick', (e) => {
+ if (e.button !== 1) { return; }
+ apiCommandExec(command, {newTab: true});
+ e.preventDefault();
+ });
+
+ if (typeof url === 'string') {
+ node.attr('href', url);
+ node.attr('target', '_blank');
+ node.attr('rel', 'noopener');
+ }
+}
+
$(document).ready(utilAsync(() => {
- $('#open-search').click(() => apiCommandExec('search'));
- $('#open-options').click(() => apiCommandExec('options'));
- $('#open-help').click(() => apiCommandExec('help'));
+ showExtensionInfo();
+
+ apiGetEnvironmentInfo().then(({browser}) => {
+ // Firefox mobile opens this page as a full webpage.
+ document.documentElement.dataset.mode = (browser === 'firefox-mobile' ? 'full' : 'mini');
+ });
+
+ const manifest = chrome.runtime.getManifest();
+
+ setupButtonEvents('.action-open-search', 'search', chrome.extension.getURL('/bg/search.html'));
+ setupButtonEvents('.action-open-options', 'options', chrome.extension.getURL(manifest.options_ui.page));
+ setupButtonEvents('.action-open-help', 'help');
const optionsContext = {
depth: 0,
@@ -31,5 +68,9 @@ $(document).ready(utilAsync(() => {
toggle.prop('checked', options.general.enable).change();
toggle.bootstrapToggle();
toggle.change(() => apiCommandExec('toggle'));
+
+ const toggle2 = $('#enable-search2');
+ toggle2.prop('checked', options.general.enable).change();
+ toggle2.change(() => apiCommandExec('toggle'));
});
}));
diff --git a/ext/bg/js/database.js b/ext/bg/js/database.js
index 771a71c9..9f477b24 100644
--- a/ext/bg/js/database.js
+++ b/ext/bg/js/database.js
@@ -20,7 +20,6 @@
class Database {
constructor() {
this.db = null;
- this.tagCache = {};
}
async prepare() {
@@ -53,33 +52,20 @@ class Database {
this.db.close();
await this.db.delete();
this.db = null;
- this.tagCache = {};
await this.prepare();
}
- async findTerms(term, titles) {
+ async findTermsBulk(termList, titles) {
this.validate();
- const results = [];
- await this.db.terms.where('expression').equals(term).or('reading').equals(term).each(row => {
- if (titles.includes(row.dictionary)) {
- results.push(Database.createTerm(row));
- }
- });
-
- return results;
- }
-
- async findTermsBulk(terms, titles) {
const promises = [];
const visited = {};
const results = [];
- const createResult = Database.createTerm;
const processRow = (row, index) => {
if (titles.includes(row.dictionary) && !visited.hasOwnProperty(row.id)) {
visited[row.id] = true;
- results.push(createResult(row, index));
+ results.push(Database.createTerm(row, index));
}
};
@@ -89,8 +75,8 @@ class Database {
const dbIndex1 = dbTerms.index('expression');
const dbIndex2 = dbTerms.index('reading');
- for (let i = 0; i < terms.length; ++i) {
- const only = IDBKeyRange.only(terms[i]);
+ for (let i = 0; i < termList.length; ++i) {
+ const only = IDBKeyRange.only(termList[i]);
promises.push(
Database.getAll(dbIndex1, only, i, processRow),
Database.getAll(dbIndex2, only, i, processRow)
@@ -102,66 +88,50 @@ class Database {
return results;
}
- async findTermsExact(term, reading, titles) {
+ async findTermsExactBulk(termList, readingList, titles) {
this.validate();
+ const promises = [];
const results = [];
- await this.db.terms.where('expression').equals(term).each(row => {
- if (row.reading === reading && titles.includes(row.dictionary)) {
- results.push(Database.createTerm(row));
+ const processRow = (row, index) => {
+ if (row.reading === readingList[index] && titles.includes(row.dictionary)) {
+ results.push(Database.createTerm(row, index));
}
- });
+ };
- return results;
- }
+ const db = this.db.backendDB();
+ const dbTransaction = db.transaction(['terms'], 'readonly');
+ const dbTerms = dbTransaction.objectStore('terms');
+ const dbIndex = dbTerms.index('expression');
- async findTermsBySequence(sequence, mainDictionary) {
- this.validate();
+ for (let i = 0; i < termList.length; ++i) {
+ const only = IDBKeyRange.only(termList[i]);
+ promises.push(Database.getAll(dbIndex, only, i, processRow));
+ }
- const results = [];
- await this.db.terms.where('sequence').equals(sequence).each(row => {
- if (row.dictionary === mainDictionary) {
- results.push(Database.createTerm(row));
- }
- });
+ await Promise.all(promises);
return results;
}
- async findTermMeta(term, titles) {
+ async findTermsBySequenceBulk(sequenceList, mainDictionary) {
this.validate();
- const results = [];
- await this.db.termMeta.where('expression').equals(term).each(row => {
- if (titles.includes(row.dictionary)) {
- results.push({
- mode: row.mode,
- data: row.data,
- dictionary: row.dictionary
- });
- }
- });
-
- return results;
- }
-
- async findTermMetaBulk(terms, titles) {
const promises = [];
const results = [];
- const createResult = Database.createTermMeta;
const processRow = (row, index) => {
- if (titles.includes(row.dictionary)) {
- results.push(createResult(row, index));
+ if (row.dictionary === mainDictionary) {
+ results.push(Database.createTerm(row, index));
}
};
const db = this.db.backendDB();
- const dbTransaction = db.transaction(['termMeta'], 'readonly');
- const dbTerms = dbTransaction.objectStore('termMeta');
- const dbIndex = dbTerms.index('expression');
+ const dbTransaction = db.transaction(['terms'], 'readonly');
+ const dbTerms = dbTransaction.objectStore('terms');
+ const dbIndex = dbTerms.index('sequence');
- for (let i = 0; i < terms.length; ++i) {
- const only = IDBKeyRange.only(terms[i]);
+ for (let i = 0; i < sequenceList.length; ++i) {
+ const only = IDBKeyRange.only(sequenceList[i]);
promises.push(Database.getAll(dbIndex, only, i, processRow));
}
@@ -170,67 +140,59 @@ class Database {
return results;
}
- async findKanji(kanji, titles) {
- this.validate();
+ async findTermMetaBulk(termList, titles) {
+ return this.findGenericBulk('termMeta', 'expression', termList, titles, Database.createMeta);
+ }
- const results = [];
- await this.db.kanji.where('character').equals(kanji).each(row => {
- if (titles.includes(row.dictionary)) {
- results.push({
- character: row.character,
- onyomi: dictFieldSplit(row.onyomi),
- kunyomi: dictFieldSplit(row.kunyomi),
- tags: dictFieldSplit(row.tags),
- glossary: row.meanings,
- stats: row.stats,
- dictionary: row.dictionary
- });
- }
- });
+ async findKanjiBulk(kanjiList, titles) {
+ return this.findGenericBulk('kanji', 'character', kanjiList, titles, Database.createKanji);
+ }
- return results;
+ async findKanjiMetaBulk(kanjiList, titles) {
+ return this.findGenericBulk('kanjiMeta', 'character', kanjiList, titles, Database.createMeta);
}
- async findKanjiMeta(kanji, titles) {
+ async findGenericBulk(tableName, indexName, indexValueList, titles, createResult) {
this.validate();
+ const promises = [];
const results = [];
- await this.db.kanjiMeta.where('character').equals(kanji).each(row => {
+ const processRow = (row, index) => {
if (titles.includes(row.dictionary)) {
- results.push({
- mode: row.mode,
- data: row.data,
- dictionary: row.dictionary
- });
+ results.push(createResult(row, index));
}
- });
+ };
- return results;
- }
+ const db = this.db.backendDB();
+ const dbTransaction = db.transaction([tableName], 'readonly');
+ const dbTerms = dbTransaction.objectStore(tableName);
+ const dbIndex = dbTerms.index(indexName);
- findTagForTitleCached(name, title) {
- if (this.tagCache.hasOwnProperty(title)) {
- const cache = this.tagCache[title];
- if (cache.hasOwnProperty(name)) {
- return cache[name];
- }
+ for (let i = 0; i < indexValueList.length; ++i) {
+ const only = IDBKeyRange.only(indexValueList[i]);
+ promises.push(Database.getAll(dbIndex, only, i, processRow));
}
+
+ await Promise.all(promises);
+
+ return results;
}
async findTagForTitle(name, title) {
this.validate();
- const cache = (this.tagCache.hasOwnProperty(title) ? this.tagCache[title] : (this.tagCache[title] = {}));
-
let result = null;
- await this.db.tagMeta.where('name').equals(name).each(row => {
+ const db = this.db.backendDB();
+ const dbTransaction = db.transaction(['tagMeta'], 'readonly');
+ const dbTerms = dbTransaction.objectStore('tagMeta');
+ const dbIndex = dbTerms.index('name');
+ const only = IDBKeyRange.only(name);
+ await Database.getAll(dbIndex, only, null, row => {
if (title === row.dictionary) {
result = row;
}
});
- cache[name] = result;
-
return result;
}
@@ -522,7 +484,20 @@ class Database {
};
}
- static createTermMeta(row, index) {
+ static createKanji(row, index) {
+ return {
+ index,
+ character: row.character,
+ onyomi: dictFieldSplit(row.onyomi),
+ kunyomi: dictFieldSplit(row.kunyomi),
+ tags: dictFieldSplit(row.tags),
+ glossary: row.meanings,
+ stats: row.stats,
+ dictionary: row.dictionary
+ };
+ }
+
+ static createMeta(row, index) {
return {
index,
mode: row.mode,
diff --git a/ext/bg/js/dictionary.js b/ext/bg/js/dictionary.js
index 498eafcd..191058c1 100644
--- a/ext/bg/js/dictionary.js
+++ b/ext/bg/js/dictionary.js
@@ -342,10 +342,10 @@ async function dictFieldFormat(field, definition, mode, options) {
'kunyomi',
'onyomi',
'reading',
+ 'screenshot',
'sentence',
'tags',
- 'url',
- 'screenshot'
+ 'url'
];
for (const marker of markers) {
diff --git a/ext/bg/js/handlebars.js b/ext/bg/js/handlebars.js
index 66d5fa2b..92764a20 100644
--- a/ext/bg/js/handlebars.js
+++ b/ext/bg/js/handlebars.js
@@ -75,6 +75,10 @@ function handlebarsMultiLine(options) {
return options.fn(this).split('\n').join('<br>');
}
+function handlebarsSanitizeCssClass(options) {
+ return options.fn(this).replace(/[^_a-z0-9\u00a0-\uffff]/ig, '_');
+}
+
function handlebarsRegisterHelpers() {
if (Handlebars.partials !== Handlebars.templates) {
Handlebars.partials = Handlebars.templates;
@@ -83,6 +87,7 @@ function handlebarsRegisterHelpers() {
Handlebars.registerHelper('furiganaPlain', handlebarsFuriganaPlain);
Handlebars.registerHelper('kanjiLinks', handlebarsKanjiLinks);
Handlebars.registerHelper('multiLine', handlebarsMultiLine);
+ Handlebars.registerHelper('sanitizeCssClass', handlebarsSanitizeCssClass);
}
}
diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js
index d0aa6fd3..4854cd65 100644
--- a/ext/bg/js/options.js
+++ b/ext/bg/js/options.js
@@ -276,15 +276,19 @@ function profileOptionsCreateDefaults() {
compactTags: false,
compactGlossaries: false,
mainDictionary: '',
- customPopupCss: ''
+ popupTheme: 'default',
+ popupOuterTheme: 'default',
+ customPopupCss: '',
+ customPopupOuterCss: ''
},
audio: {
enabled: true,
- sources: ['jpod101', 'jpod101-alternate', 'jisho', 'custom'],
+ sources: ['jpod101'],
volume: 100,
autoPlay: false,
- customSourceUrl: ''
+ customSourceUrl: '',
+ textToSpeechVoice: ''
},
scanning: {
diff --git a/ext/bg/js/search-frontend.js b/ext/bg/js/search-frontend.js
index 0c1a61ea..b21dac17 100644
--- a/ext/bg/js/search-frontend.js
+++ b/ext/bg/js/search-frontend.js
@@ -25,11 +25,14 @@ async function searchFrontendSetup() {
const options = await apiOptionsGet(optionsContext);
if (!options.scanning.enableOnSearchPage) { return; }
+ window.frontendInitializationData = {depth: 1, proxy: false};
+
const scriptSrcs = [
'/fg/js/frontend-api-receiver.js',
'/fg/js/popup.js',
'/fg/js/popup-proxy-host.js',
- '/fg/js/frontend.js'
+ '/fg/js/frontend.js',
+ '/fg/js/frontend-initialize.js'
];
for (const src of scriptSrcs) {
const script = document.createElement('script');
diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js
index ead9ba6f..431478c9 100644
--- a/ext/bg/js/search.js
+++ b/ext/bg/js/search.js
@@ -31,25 +31,37 @@ class DisplaySearch extends Display {
this.intro = document.querySelector('#intro');
this.introVisible = true;
this.introAnimationTimer = null;
+ }
- this.dependencies = Object.assign({}, this.dependencies, {docRangeFromPoint, docSentenceExtract});
+ static create() {
+ const instance = new DisplaySearch();
+ instance.prepare();
+ return instance;
+ }
- if (this.search !== null) {
- this.search.addEventListener('click', (e) => this.onSearch(e), false);
- }
- if (this.query !== null) {
- this.query.addEventListener('input', () => this.onSearchInput(), false);
+ async prepare() {
+ try {
+ await this.initialize();
- const query = DisplaySearch.getSearchQueryFromLocation(window.location.href);
- if (query !== null) {
- this.query.value = window.wanakana.toKana(query);
- this.onSearchQueryUpdated(query, false);
+ if (this.search !== null) {
+ this.search.addEventListener('click', (e) => this.onSearch(e), false);
}
+ if (this.query !== null) {
+ this.query.addEventListener('input', () => this.onSearchInput(), false);
- window.wanakana.bind(this.query);
- }
+ const query = DisplaySearch.getSearchQueryFromLocation(window.location.href);
+ if (query !== null) {
+ this.query.value = window.wanakana.toKana(query);
+ this.onSearchQueryUpdated(query, false);
+ }
- this.updateSearchButton();
+ window.wanakana.bind(this.query);
+ }
+
+ this.updateSearchButton();
+ } catch (e) {
+ this.onError(e);
+ }
}
onError(error) {
@@ -89,7 +101,11 @@ class DisplaySearch extends Display {
this.updateSearchButton();
if (valid) {
const {definitions} = await apiTermsFind(query, this.optionsContext);
- this.termsShow(definitions, await apiOptionsGet(this.optionsContext));
+ this.setContentTerms(definitions, {
+ focus: false,
+ sentence: null,
+ url: window.location.href
+ });
} else {
this.container.textContent = '';
}
@@ -98,6 +114,25 @@ class DisplaySearch extends Display {
}
}
+ onRuntimeMessage({action, params}, sender, callback) {
+ const handlers = DisplaySearch.runtimeMessageHandlers;
+ if (handlers.hasOwnProperty(action)) {
+ const handler = handlers[action];
+ const result = handler(this, params);
+ callback(result);
+ } else {
+ return super.onRuntimeMessage({action, params}, sender, callback);
+ }
+ }
+
+ getOptionsContext() {
+ return this.optionsContext;
+ }
+
+ setCustomCss() {
+ // No custom CSS
+ }
+
setIntroVisible(visible, animate) {
if (this.introVisible === visible) {
return;
@@ -164,4 +199,10 @@ class DisplaySearch extends Display {
}
}
-window.yomichan_search = new DisplaySearch();
+DisplaySearch.runtimeMessageHandlers = {
+ getUrl: () => {
+ return {url: window.location.href};
+ }
+};
+
+window.yomichan_search = DisplaySearch.create();
diff --git a/ext/bg/js/settings-popup-preview.js b/ext/bg/js/settings-popup-preview.js
new file mode 100644
index 00000000..b12fb726
--- /dev/null
+++ b/ext/bg/js/settings-popup-preview.js
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2019 Alex Yatskov <alex@foosoft.net>
+ * Author: Alex Yatskov <alex@foosoft.net>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+class SettingsPopupPreview {
+ constructor() {
+ this.frontend = null;
+ this.apiOptionsGetOld = apiOptionsGet;
+ this.popupInjectOuterStylesheetOld = Popup.injectOuterStylesheet;
+ this.popupShown = false;
+ this.themeChangeTimeout = null;
+ }
+
+ static create() {
+ const instance = new SettingsPopupPreview();
+ instance.prepare();
+ return instance;
+ }
+
+ 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');
+ if (themeDarkCheckbox !== null) {
+ themeDarkCheckbox.addEventListener('change', () => this.onThemeDarkCheckboxChanged(themeDarkCheckbox), false);
+ }
+
+ // Overwrite API functions
+ window.apiOptionsGet = (...args) => this.apiOptionsGet(...args);
+
+ // Overwrite frontend
+ this.frontend = Frontend.create();
+ window.yomichan_frontend = this.frontend;
+
+ this.frontend.setEnabled = function () {};
+ this.frontend.searchClear = function () {};
+
+ this.frontend.popup.childrenSupported = false;
+ this.frontend.popup.interactive = false;
+
+ await this.frontend.isPrepared();
+
+ // Overwrite popup
+ Popup.injectOuterStylesheet = (...args) => this.popupInjectOuterStylesheet(...args);
+
+ // Update search
+ this.updateSearch();
+ }
+
+ async apiOptionsGet(...args) {
+ const options = await this.apiOptionsGetOld(...args);
+ options.general.enable = true;
+ options.general.debugInfo = false;
+ options.general.popupWidth = 400;
+ options.general.popupHeight = 250;
+ options.general.popupHorizontalOffset = 0;
+ options.general.popupVerticalOffset = 10;
+ options.general.popupHorizontalOffset2 = 10;
+ options.general.popupVerticalOffset2 = 0;
+ options.general.popupHorizontalTextPosition = 'below';
+ options.general.popupVerticalTextPosition = 'before';
+ options.scanning.selectText = false;
+ return options;
+ }
+
+ popupInjectOuterStylesheet(...args) {
+ // This simulates the stylesheet priorities when injecting using the web extension API.
+ const result = this.popupInjectOuterStylesheetOld(...args);
+
+ const outerStylesheet = Popup.outerStylesheet;
+ const node = document.querySelector('#client-css');
+ if (node !== null && outerStylesheet !== null) {
+ node.parentNode.insertBefore(outerStylesheet, node);
+ }
+
+ return result;
+ }
+
+ onWindowResize() {
+ if (this.frontend === null) { return; }
+ const textSource = this.frontend.textSourceLast;
+ 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 handlers = SettingsPopupPreview.messageHandlers;
+ if (handlers.hasOwnProperty(action)) {
+ const handler = handlers[action];
+ handler(this, params);
+ }
+ }
+
+ onThemeDarkCheckboxChanged(node) {
+ document.documentElement.classList.toggle('dark', node.checked);
+ if (this.themeChangeTimeout !== null) {
+ clearTimeout(this.themeChangeTimeout);
+ }
+ this.themeChangeTimeout = setTimeout(() => {
+ this.themeChangeTimeout = null;
+ this.frontend.popup.updateTheme();
+ }, 300);
+ }
+
+ setText(text) {
+ const exampleText = document.querySelector('#example-text');
+ if (exampleText === null) { return; }
+
+ exampleText.textContent = text;
+ this.updateSearch();
+ }
+
+ setInfoVisible(visible) {
+ const node = document.querySelector('.placeholder-info');
+ if (node === null) { return; }
+
+ node.classList.toggle('placeholder-info-visible', visible);
+ }
+
+ setCustomCss(css) {
+ if (this.frontend === null) { return; }
+ this.frontend.popup.setCustomCss(css);
+ }
+
+ setCustomOuterCss(css) {
+ if (this.frontend === null) { return; }
+ this.frontend.popup.setCustomOuterCss(css, true);
+ }
+
+ async updateSearch() {
+ const exampleText = document.querySelector('#example-text');
+ if (exampleText === null) { return; }
+
+ const textNode = exampleText.firstChild;
+ if (textNode === null) { return; }
+
+ const range = document.createRange();
+ range.selectNode(textNode);
+ const source = new TextSourceRange(range, range.toString(), null);
+
+ this.frontend.textSourceLast = null;
+ await this.frontend.searchSource(source, 'script');
+ await this.frontend.lastShowPromise;
+
+ if (this.frontend.popup.isVisible()) {
+ this.popupShown = true;
+ }
+
+ this.setInfoVisible(!this.popupShown);
+ }
+}
+
+SettingsPopupPreview.messageHandlers = {
+ setText: (self, {text}) => self.setText(text),
+ setCustomCss: (self, {css}) => self.setCustomCss(css),
+ setCustomOuterCss: (self, {css}) => self.setCustomOuterCss(css)
+};
+
+SettingsPopupPreview.instance = SettingsPopupPreview.create();
+
+
+
diff --git a/ext/bg/js/settings.js b/ext/bg/js/settings.js
index f3b5ff16..05a0604a 100644
--- a/ext/bg/js/settings.js
+++ b/ext/bg/js/settings.js
@@ -39,12 +39,16 @@ 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.popupTheme = $('#popup-theme').val();
+ options.general.popupOuterTheme = $('#popup-outer-theme').val();
options.general.customPopupCss = $('#custom-popup-css').val();
+ options.general.customPopupOuterCss = $('#custom-popup-outer-css').val();
options.audio.enabled = $('#audio-playback-enabled').prop('checked');
options.audio.autoPlay = $('#auto-play-audio').prop('checked');
options.audio.volume = parseFloat($('#audio-playback-volume').val());
options.audio.customSourceUrl = $('#audio-custom-source').val();
+ options.audio.textToSpeechVoice = $('#text-to-speech-voice').val();
options.scanning.middleMouse = $('#middle-mouse-button-scan').prop('checked');
options.scanning.touchInputEnabled = $('#touch-input-enabled').prop('checked');
@@ -107,12 +111,16 @@ 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-theme').val(options.general.popupTheme);
+ $('#popup-outer-theme').val(options.general.popupOuterTheme);
$('#custom-popup-css').val(options.general.customPopupCss);
+ $('#custom-popup-outer-css').val(options.general.customPopupOuterCss);
$('#audio-playback-enabled').prop('checked', options.audio.enabled);
$('#auto-play-audio').prop('checked', options.audio.autoPlay);
$('#audio-playback-volume').val(options.audio.volume);
$('#audio-custom-source').val(options.audio.customSourceUrl);
+ $('#text-to-speech-voice').val(options.audio.textToSpeechVoice).attr('data-value', options.audio.textToSpeechVoice);
$('#middle-mouse-button-scan').prop('checked', options.scanning.middleMouse);
$('#touch-input-enabled').prop('checked', options.scanning.touchInputEnabled);
@@ -248,6 +256,7 @@ async function onReady() {
showExtensionInformation();
formSetupEventListeners();
+ appearanceInitialize();
await audioSettingsInitialize();
await profileOptionsSetup();
@@ -260,6 +269,55 @@ $(document).ready(utilAsync(onReady));
/*
+ * Appearance
+ */
+
+function appearanceInitialize() {
+ let previewVisible = false;
+ $('#settings-popup-preview-button').on('click', () => {
+ if (previewVisible) { return; }
+ showAppearancePreview();
+ previewVisible = true;
+ });
+}
+
+function showAppearancePreview() {
+ const container = $('#settings-popup-preview-container');
+ const buttonContainer = $('#settings-popup-preview-button-container');
+ const settings = $('#settings-popup-preview-settings');
+ const text = $('#settings-popup-preview-text');
+ const customCss = $('#custom-popup-css');
+ const customOuterCss = $('#custom-popup-outer-css');
+
+ const frame = document.createElement('iframe');
+ frame.src = '/bg/settings-popup-preview.html';
+ frame.id = 'settings-popup-preview-frame';
+
+ window.wanakana.bind(text[0]);
+
+ text.on('input', () => {
+ const action = 'setText';
+ const params = {text: text.val()};
+ frame.contentWindow.postMessage({action, params}, '*');
+ });
+ customCss.on('input', () => {
+ const action = 'setCustomCss';
+ const params = {css: customCss.val()};
+ frame.contentWindow.postMessage({action, params}, '*');
+ });
+ customOuterCss.on('input', () => {
+ const action = 'setCustomOuterCss';
+ const params = {css: customOuterCss.val()};
+ frame.contentWindow.postMessage({action, params}, '*');
+ });
+
+ container.append(frame);
+ buttonContainer.remove();
+ settings.css('display', '');
+}
+
+
+/*
* Audio
*/
@@ -270,6 +328,81 @@ async function audioSettingsInitialize() {
const options = await apiOptionsGet(optionsContext);
audioSourceUI = new AudioSourceUI.Container(options.audio.sources, $('.audio-source-list'), $('.audio-source-add'));
audioSourceUI.save = () => apiOptionsSave();
+
+ textToSpeechInitialize();
+}
+
+function textToSpeechInitialize() {
+ if (typeof speechSynthesis === 'undefined') { return; }
+
+ speechSynthesis.addEventListener('voiceschanged', () => updateTextToSpeechVoices(), false);
+ updateTextToSpeechVoices();
+
+ $('#text-to-speech-voice-test').on('click', () => textToSpeechTest());
+}
+
+function updateTextToSpeechVoices() {
+ const voices = Array.prototype.map.call(speechSynthesis.getVoices(), (voice, index) => ({voice, index}));
+ voices.sort(textToSpeechVoiceCompare);
+ if (voices.length > 0) {
+ $('#text-to-speech-voice-container').css('display', '');
+ }
+
+ const select = $('#text-to-speech-voice');
+ select.empty();
+ select.append($('<option>').val('').text('None'));
+ for (const {voice} of voices) {
+ select.append($('<option>').val(voice.voiceURI).text(`${voice.name} (${voice.lang})`));
+ }
+
+ select.val(select.attr('data-value'));
+}
+
+function languageTagIsJapanese(languageTag) {
+ return (
+ languageTag.startsWith('ja-') ||
+ languageTag.startsWith('jpn-')
+ );
+}
+
+function textToSpeechVoiceCompare(a, b) {
+ const aIsJapanese = languageTagIsJapanese(a.voice.lang);
+ const bIsJapanese = languageTagIsJapanese(b.voice.lang);
+ if (aIsJapanese) {
+ if (!bIsJapanese) { return -1; }
+ } else {
+ if (bIsJapanese) { return 1; }
+ }
+
+ const aIsDefault = a.voice.default;
+ const bIsDefault = b.voice.default;
+ if (aIsDefault) {
+ if (!bIsDefault) { return -1; }
+ } else {
+ if (bIsDefault) { return 1; }
+ }
+
+ if (a.index < b.index) { return -1; }
+ if (a.index > b.index) { return 1; }
+ return 0;
+}
+
+function textToSpeechTest() {
+ try {
+ const text = $('#text-to-speech-voice-test').attr('data-speech-text') || '';
+ const voiceURI = $('#text-to-speech-voice').val();
+ const voice = audioGetTextToSpeechVoice(voiceURI);
+ if (voice === null) { return; }
+
+ const utterance = new SpeechSynthesisUtterance(text);
+ utterance.lang = 'ja-JP';
+ utterance.voice = voice;
+ utterance.volume = 1.0;
+
+ speechSynthesis.speak(utterance);
+ } catch (e) {
+ // NOP
+ }
}
@@ -297,9 +430,14 @@ async function onOptionsUpdate({source}) {
await formWrite(options);
}
-function onMessage({action, params}) {
- if (action === 'optionsUpdate') {
- onOptionsUpdate(params);
+function onMessage({action, params}, sender, callback) {
+ switch (action) {
+ case 'optionsUpdate':
+ onOptionsUpdate(params);
+ break;
+ case 'getUrl':
+ callback({url: window.location.href});
+ break;
}
}
@@ -607,10 +745,10 @@ async function ankiFieldsPopulate(element, options) {
'glossary',
'glossary-brief',
'reading',
+ 'screenshot',
'sentence',
'tags',
- 'url',
- 'screenshot'
+ 'url'
],
'kanji': [
'character',
@@ -618,6 +756,7 @@ async function ankiFieldsPopulate(element, options) {
'glossary',
'kunyomi',
'onyomi',
+ 'screenshot',
'sentence',
'tags',
'url'
@@ -685,32 +824,15 @@ async function onAnkiFieldTemplatesReset(e) {
* Storage
*/
-async function getBrowser() {
- if (EXTENSION_IS_BROWSER_EDGE) {
- return 'edge';
- }
- if (typeof browser !== 'undefined') {
- try {
- const info = await browser.runtime.getBrowserInfo();
- if (info.name === 'Fennec') {
- return 'firefox-mobile';
- }
- } catch (e) { }
- return 'firefox';
- } else {
- return 'chrome';
- }
-}
-
function storageBytesToLabeledString(size) {
const base = 1000;
- const labels = ['bytes', 'KB', 'MB', 'GB'];
+ const labels = [' bytes', 'KB', 'MB', 'GB'];
let labelIndex = 0;
while (size >= base) {
size /= base;
++labelIndex;
}
- const label = size.toFixed(1);
+ const label = labelIndex === 0 ? `${size}` : size.toFixed(1);
return `${label}${labels[labelIndex]}`;
}
@@ -722,15 +844,21 @@ async function storageEstimate() {
}
storageEstimate.mostRecent = null;
+async function isStoragePeristent() {
+ try {
+ return await navigator.storage.persisted();
+ } catch (e) { }
+ return false;
+}
+
async function storageInfoInitialize() {
- const browser = await getBrowser();
- const container = document.querySelector('#storage-info');
- container.setAttribute('data-browser', browser);
+ storagePersistInitialize();
+ const {browser, platform} = await apiGetEnvironmentInfo();
+ document.documentElement.dataset.browser = browser;
+ document.documentElement.dataset.operatingSystem = platform.os;
await storageShowInfo();
- container.classList.remove('storage-hidden');
-
document.querySelector('#storage-refresh').addEventListener('click', () => storageShowInfo(), false);
}
@@ -741,8 +869,14 @@ async function storageUpdateStats() {
const valid = (estimate !== null);
if (valid) {
- document.querySelector('#storage-usage').textContent = storageBytesToLabeledString(estimate.usage);
- document.querySelector('#storage-quota').textContent = storageBytesToLabeledString(estimate.quota);
+ // Firefox reports usage as 0 when persistent storage is enabled.
+ const finite = (estimate.usage > 0 || !(await isStoragePeristent()));
+ if (finite) {
+ document.querySelector('#storage-usage').textContent = storageBytesToLabeledString(estimate.usage);
+ document.querySelector('#storage-quota').textContent = storageBytesToLabeledString(estimate.quota);
+ }
+ document.querySelector('#storage-use-finite').classList.toggle('storage-hidden', !finite);
+ document.querySelector('#storage-use-infinite').classList.toggle('storage-hidden', finite);
}
storageUpdateStats.isUpdating = false;
@@ -769,6 +903,43 @@ function storageSpinnerShow(show) {
}
}
+async function storagePersistInitialize() {
+ if (!(navigator.storage && navigator.storage.persist)) {
+ // Not supported
+ return;
+ }
+
+ const info = document.querySelector('#storage-persist-info');
+ const button = document.querySelector('#storage-persist-button');
+ const checkbox = document.querySelector('#storage-persist-button-checkbox');
+
+ info.classList.remove('storage-hidden');
+ button.classList.remove('storage-hidden');
+
+ let persisted = await isStoragePeristent();
+ checkbox.checked = persisted;
+
+ button.addEventListener('click', async () => {
+ if (persisted) {
+ return;
+ }
+ let result = false;
+ try {
+ result = await navigator.storage.persist();
+ } catch (e) {
+ // NOP
+ }
+
+ if (result) {
+ persisted = true;
+ checkbox.checked = true;
+ storageShowInfo();
+ } else {
+ $('.storage-persist-fail-warning').removeClass('storage-hidden');
+ }
+ }, false);
+}
+
/*
* Information
diff --git a/ext/bg/js/templates.js b/ext/bg/js/templates.js
index c61f5d7f..59516d97 100644
--- a/ext/bg/js/templates.js
+++ b/ext/bg/js/templates.js
@@ -208,148 +208,157 @@ templates['model.html'] = template({"1":function(container,depth0,helpers,partia
+ " </ul>\n </div>\n </div>\n </td>\n</tr>\n";
},"useData":true});
templates['terms.html'] = template({"1":function(container,depth0,helpers,partials,data) {
- var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {});
-
- return ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.definitionTags : depth0),{"name":"if","hash":{},"fn":container.program(2, 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(7, 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(11, data, 0),"inverse":container.program(17, data, 0),"data":data})) != null ? stack1 : "");
+ 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(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ 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(5, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + "</div>\n";
-},"3":function(container,depth0,helpers,partials,data) {
- return "class=\"compact-info\"";
+ + ((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-"
+ 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";
-},"7":function(container,depth0,helpers,partials,data) {
+},"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(3, 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(8, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + " only)\n</div>\n";
-},"8":function(container,depth0,helpers,partials,data) {
+ 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(9, data, 0),"inverse":container.noop,"data":data})) != 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";
-},"9":function(container,depth0,helpers,partials,data) {
- return ", ";
},"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(12, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ 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(14, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + "</ul>\n";
-},"12":function(container,depth0,helpers,partials,data) {
- return "class=\"compact-glossary\"";
+ + ((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(15, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),options) : helper));
+ " <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";
-},"15":function(container,depth0,helpers,partials,data) {
- return container.escapeExpression(container.lambda(depth0, depth0));
},"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(18, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ " <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(20, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(alias1,options) : helper));
+ 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";
-},"18":function(container,depth0,helpers,partials,data) {
- return "compact-glossary";
},"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));
-},"22":function(container,depth0,helpers,partials,data,blockParams,depths) {
+},"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(23, 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(25, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.source : depth0),{"name":"if","hash":{},"fn":container.program(28, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + ((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 : "")
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.source : depth0),{"name":"if","hash":{},"fn":container.program(30, 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 : "")
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.merged : depth0),{"name":"if","hash":{},"fn":container.program(32, data, 0, blockParams, depths),"inverse":container.program(47, 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(49, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.reasons : depth0),{"name":"if","hash":{},"fn":container.program(50, 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(53, 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(54, 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(56, data, 0, blockParams, depths),"inverse":container.program(62, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "")
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.grouped : depth0),{"name":"if","hash":{},"fn":container.program(57, data, 0, blockParams, depths),"inverse":container.program(63, 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(65, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.debug : depth0),{"name":"if","hash":{},"fn":container.program(66, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "</div>\n";
-},"23":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";
},"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(26, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
-},"26":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";
+ 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) {
return " <a href=\"#\" class=\"source-term\"><img src=\"/mixed/img/source-term.svg\" title=\"Source term (Alt + B)\" alt></a>\n";
-},"30":function(container,depth0,helpers,partials,data,blockParams,depths) {
+},"32":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) {
+ return ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.expressions : depth0),{"name":"each","hash":{},"fn":container.program(33, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "");
+},"33":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\"><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));
+ stack1 = ((helper = (helper = helpers.kanjiLinks || (depth0 != null ? depth0.kanjiLinks : depth0)) != null ? helper : alias2),(options={"name":"kanjiLinks","hash":{},"fn":container.program(34, 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 : "")
+ + ((stack1 = helpers["if"].call(alias1,(depths[1] != null ? depths[1].playback : depths[1]),{"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.termTags : depth0),{"name":"if","hash":{},"fn":container.program(39, 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(42, 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 : "")
+ + ((stack1 = helpers["if"].call(alias1,(data && data.last),{"name":"if","hash":{},"fn":container.program(45, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "\">、</span></div>";
-},"32":function(container,depth0,helpers,partials,data) {
+},"34":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));
+ stack1 = ((helper = (helper = helpers.furigana || (depth0 != null ? depth0.furigana : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"furigana","hash":{},"fn":container.program(35, 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) {
+},"35":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) {
+ return "<a href=\"#\" class=\"action-play-audio\"><img src=\"/mixed/img/play-audio.svg\" title=\"Play audio\" alt></a>";
+},"39":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 : "")
+ + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.termTags : depth0),{"name":"each","hash":{},"fn":container.program(40, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "</div>";
-},"38":function(container,depth0,helpers,partials,data) {
+},"40":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-"
@@ -359,13 +368,13 @@ templates['terms.html'] = template({"1":function(container,depth0,helpers,partia
+ "\">"
+ 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) {
+},"42":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 : "")
+ + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.frequencies : depth0),{"name":"each","hash":{},"fn":container.program(43, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "</div>";
-},"41":function(container,depth0,helpers,partials,data) {
+},"43":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\">"
@@ -373,55 +382,45 @@ templates['terms.html'] = template({"1":function(container,depth0,helpers,partia
+ ":"
+ 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) {
+ return "invisible";
+},"47":function(container,depth0,helpers,partials,data) {
var stack1, helper, options, alias1=depth0 != null ? depth0 : (container.nullContext || {}), buffer =
" <div class=\"expression\">";
- 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));
+ stack1 = ((helper = (helper = helpers.kanjiLinks || (depth0 != null ? depth0.kanjiLinks : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"kanjiLinks","hash":{},"fn":container.program(34, 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) {
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.termTags : depth0),{"name":"if","hash":{},"fn":container.program(48, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "");
+},"48":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(47, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + ((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";
-},"47":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";
-},"49":function(container,depth0,helpers,partials,data) {
+},"50":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(50, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.reasons : depth0),{"name":"each","hash":{},"fn":container.program(51, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ " </div>\n";
-},"50":function(container,depth0,helpers,partials,data) {
+},"51":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(51, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + ((stack1 = helpers.unless.call(depth0 != null ? depth0 : (container.nullContext || {}),(data && data.last),{"name":"unless","hash":{},"fn":container.program(52, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "\n";
-},"51":function(container,depth0,helpers,partials,data) {
+},"52":function(container,depth0,helpers,partials,data) {
return "&laquo;";
-},"53":function(container,depth0,helpers,partials,data) {
+},"54":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(54, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.frequencies : depth0),{"name":"each","hash":{},"fn":container.program(55, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ " </div>\n";
-},"54":function(container,depth0,helpers,partials,data) {
+},"55":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\">"
@@ -429,67 +428,67 @@ templates['terms.html'] = template({"1":function(container,depth0,helpers,partia
+ ":"
+ 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";
-},"56":function(container,depth0,helpers,partials,data,blockParams,depths) {
+},"57":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(57, data, 0, blockParams, depths),"inverse":container.program(60, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "");
-},"57":function(container,depth0,helpers,partials,data,blockParams,depths) {
+ 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(58, data, 0, blockParams, depths),"inverse":container.program(61, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "");
+},"58":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(58, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.definitions : depth0),{"name":"each","hash":{},"fn":container.program(59, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ " </ol>\n";
-},"58":function(container,depth0,helpers,partials,data,blockParams,depths) {
+},"59":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";
-},"60":function(container,depth0,helpers,partials,data) {
+},"61":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 : "");
-},"62":function(container,depth0,helpers,partials,data,blockParams,depths) {
+},"63":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(56, data, 0, blockParams, depths),"inverse":container.program(63, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "");
-},"63":function(container,depth0,helpers,partials,data) {
+ return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.merged : depth0),{"name":"if","hash":{},"fn":container.program(57, data, 0, blockParams, depths),"inverse":container.program(64, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "");
+},"64":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 : "")
+ " ";
-},"65":function(container,depth0,helpers,partials,data) {
+},"66":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));
+ stack1 = ((helper = (helper = helpers.dumpObject || (depth0 != null ? depth0.dumpObject : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"dumpObject","hash":{},"fn":container.program(35, 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";
-},"67":function(container,depth0,helpers,partials,data,blockParams,depths) {
+},"68":function(container,depth0,helpers,partials,data,blockParams,depths) {
var stack1;
- return ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.definitions : depth0),{"name":"each","hash":{},"fn":container.program(68, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "");
-},"68":function(container,depth0,helpers,partials,data,blockParams,depths) {
+ return ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.definitions : depth0),{"name":"each","hash":{},"fn":container.program(69, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "");
+},"69":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(69, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ return ((stack1 = helpers.unless.call(depth0 != null ? depth0 : (container.nullContext || {}),(data && data.first),{"name":"unless","hash":{},"fn":container.program(70, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "\n"
+ ((stack1 = container.invokePartial(partials.term,depth0,{"name":"term","hash":{"source":(depths[1] != null ? depths[1].source : depths[1]),"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 : "");
-},"69":function(container,depth0,helpers,partials,data) {
+},"70":function(container,depth0,helpers,partials,data) {
return "<hr>";
-},"71":function(container,depth0,helpers,partials,data) {
+},"72":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(67, data, 0, blockParams, depths),"inverse":container.program(71, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "");
+ + ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.definitions : depth0),{"name":"if","hash":{},"fn":container.program(68, data, 0, blockParams, depths),"inverse":container.program(72, 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(22, data, 0, blockParams, depths),"inverse":container.noop,"args":["term"],"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;
}
diff --git a/ext/bg/js/translator.js b/ext/bg/js/translator.js
index 601ee30c..ee012d96 100644
--- a/ext/bg/js/translator.js
+++ b/ext/bg/js/translator.js
@@ -21,6 +21,7 @@ class Translator {
constructor() {
this.database = null;
this.deinflector = null;
+ this.tagCache = {};
}
async prepare() {
@@ -36,6 +37,11 @@ class Translator {
}
}
+ async purgeDatabase() {
+ this.tagCache = {};
+ await this.database.purge();
+ }
+
async findTermsGrouped(text, dictionaries, alphanumeric, options) {
const titles = Object.keys(dictionaries);
const {length, definitions} = await this.findTerms(text, dictionaries, alphanumeric);
@@ -52,94 +58,121 @@ class Translator {
return {length, definitions: definitionsGrouped};
}
- async findTermsMerged(text, dictionaries, alphanumeric, options) {
- const secondarySearchTitles = Object.keys(options.dictionaries).filter(dict => options.dictionaries[dict].allowSecondarySearches);
- const titles = Object.keys(dictionaries);
- const {length, definitions} = await this.findTerms(text, dictionaries, alphanumeric);
+ async getSequencedDefinitions(definitions, mainDictionary) {
+ const definitionsBySequence = dictTermsMergeBySequence(definitions, mainDictionary);
+ const defaultDefinitions = definitionsBySequence['-1'];
- const definitionsBySequence = dictTermsMergeBySequence(definitions, options.general.mainDictionary);
+ const sequenceList = Object.keys(definitionsBySequence).map(v => Number(v)).filter(v => v >= 0);
+ const sequencedDefinitions = sequenceList.map((key) => ({
+ definitions: definitionsBySequence[key],
+ rawDefinitions: []
+ }));
- const definitionsMerged = [];
- const mergedByTermIndices = new Set();
- for (const sequence in definitionsBySequence) {
- if (sequence < 0) {
- continue;
- }
+ for (const definition of await this.database.findTermsBySequenceBulk(sequenceList, mainDictionary)) {
+ sequencedDefinitions[definition.index].rawDefinitions.push(definition);
+ }
- const result = definitionsBySequence[sequence];
+ return {sequencedDefinitions, defaultDefinitions};
+ }
- const rawDefinitionsBySequence = await this.database.findTermsBySequence(Number(sequence), options.general.mainDictionary);
+ async getMergedSecondarySearchResults(text, expressionsMap, secondarySearchTitles) {
+ if (secondarySearchTitles.length === 0) {
+ return [];
+ }
- for (const definition of rawDefinitionsBySequence) {
- const definitionTags = await this.expandTags(definition.definitionTags, definition.dictionary);
- definitionTags.push(dictTagBuildSource(definition.dictionary));
- definition.definitionTags = definitionTags;
- const termTags = await this.expandTags(definition.termTags, definition.dictionary);
- definition.termTags = termTags;
+ const expressionList = [];
+ const readingList = [];
+ for (const expression of expressionsMap.keys()) {
+ if (expression === text) { continue; }
+ for (const reading of expressionsMap.get(expression).keys()) {
+ expressionList.push(expression);
+ readingList.push(reading);
}
+ }
- const definitionsByGloss = dictTermsMergeByGloss(result, rawDefinitionsBySequence);
-
- const secondarySearchResults = [];
- if (secondarySearchTitles.length > 0) {
- for (const expression of result.expressions.keys()) {
- if (expression === text) {
- continue;
- }
-
- for (const reading of result.expressions.get(expression).keys()) {
- for (const definition of await this.database.findTermsExact(expression, reading, secondarySearchTitles)) {
- const definitionTags = await this.expandTags(definition.definitionTags, definition.dictionary);
- definitionTags.push(dictTagBuildSource(definition.dictionary));
- definition.definitionTags = definitionTags;
- const termTags = await this.expandTags(definition.termTags, definition.dictionary);
- definition.termTags = termTags;
- secondarySearchResults.push(definition);
- }
- }
- }
- }
+ const definitions = await this.database.findTermsExactBulk(expressionList, readingList, secondarySearchTitles);
+ for (const definition of definitions) {
+ const definitionTags = await this.expandTags(definition.definitionTags, definition.dictionary);
+ definitionTags.push(dictTagBuildSource(definition.dictionary));
+ definition.definitionTags = definitionTags;
+ const termTags = await this.expandTags(definition.termTags, definition.dictionary);
+ definition.termTags = termTags;
+ }
- dictTermsMergeByGloss(result, definitionsBySequence['-1'].concat(secondarySearchResults), definitionsByGloss, mergedByTermIndices);
+ if (definitions.length > 1) {
+ definitions.sort((a, b) => a.index - b.index);
+ }
- for (const gloss in definitionsByGloss) {
- const definition = definitionsByGloss[gloss];
- dictTagsSort(definition.definitionTags);
- result.definitions.push(definition);
- }
+ return definitions;
+ }
- 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);
- expressions.push({
- expression: expression,
- reading: reading,
- termTags: dictTagsSort(termTags),
- termFrequency: (score => {
- if (score > 0) {
- return 'popular';
- } else if (score < 0) {
- return 'rare';
- } else {
- return 'normal';
- }
- })(termTags.map(tag => tag.score).reduce((p, v) => p + v, 0))
- });
- }
+ async getMergedDefinition(text, dictionaries, sequencedDefinition, defaultDefinitions, secondarySearchTitles, mergedByTermIndices) {
+ const result = sequencedDefinition.definitions;
+ const rawDefinitionsBySequence = sequencedDefinition.rawDefinitions;
+
+ for (const definition of rawDefinitionsBySequence) {
+ const definitionTags = await this.expandTags(definition.definitionTags, definition.dictionary);
+ definitionTags.push(dictTagBuildSource(definition.dictionary));
+ definition.definitionTags = definitionTags;
+ const termTags = await this.expandTags(definition.termTags, definition.dictionary);
+ definition.termTags = termTags;
+ }
+
+ const definitionsByGloss = dictTermsMergeByGloss(result, rawDefinitionsBySequence);
+ const secondarySearchResults = await this.getMergedSecondarySearchResults(text, result.expressions, secondarySearchTitles);
+
+ dictTermsMergeByGloss(result, defaultDefinitions.concat(secondarySearchResults), definitionsByGloss, mergedByTermIndices);
+
+ for (const gloss in definitionsByGloss) {
+ const definition = definitionsByGloss[gloss];
+ dictTagsSort(definition.definitionTags);
+ result.definitions.push(definition);
+ }
+
+ 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);
+ 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)
+ });
}
+ }
- result.expressions = expressions;
+ result.expressions = expressions;
+ result.expression = Array.from(result.expression);
+ result.reading = Array.from(result.reading);
- result.expression = Array.from(result.expression);
- result.reading = Array.from(result.reading);
+ return result;
+ }
+
+ async findTermsMerged(text, dictionaries, alphanumeric, options) {
+ const secondarySearchTitles = Object.keys(options.dictionaries).filter(dict => options.dictionaries[dict].allowSecondarySearches);
+ const titles = Object.keys(dictionaries);
+ const {length, definitions} = await this.findTerms(text, dictionaries, alphanumeric);
+ const {sequencedDefinitions, defaultDefinitions} = await this.getSequencedDefinitions(definitions, options.general.mainDictionary);
+ const definitionsMerged = [];
+ const mergedByTermIndices = new Set();
+ for (const sequencedDefinition of sequencedDefinitions) {
+ const result = await this.getMergedDefinition(
+ text,
+ dictionaries,
+ sequencedDefinition,
+ defaultDefinitions,
+ secondarySearchTitles,
+ mergedByTermIndices
+ );
definitionsMerged.push(result);
}
- const strayDefinitions = definitionsBySequence['-1'].filter((definition, index) => !mergedByTermIndices.has(index));
+ const strayDefinitions = defaultDefinitions.filter((definition, index) => !mergedByTermIndices.has(index));
for (const groupedDefinition of dictTermsGroup(strayDefinitions, dictionaries)) {
groupedDefinition.expressions = [{expression: groupedDefinition.expression, reading: groupedDefinition.reading}];
definitionsMerged.push(groupedDefinition);
@@ -277,33 +310,44 @@ class Translator {
}
async findKanji(text, dictionaries) {
- let definitions = [];
- const processed = {};
const titles = Object.keys(dictionaries);
+ const kanjiUnique = {};
+ const kanjiList = [];
for (const c of text) {
- if (!processed[c]) {
- definitions.push(...await this.database.findKanji(c, titles));
- processed[c] = true;
+ if (!kanjiUnique.hasOwnProperty(c)) {
+ kanjiList.push(c);
+ kanjiUnique[c] = true;
}
}
+ const definitions = await this.database.findKanjiBulk(kanjiList, titles);
+ if (definitions.length === 0) {
+ return definitions;
+ }
+
+ if (definitions.length > 1) {
+ 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));
definition.tags = dictTagsSort(tags);
definition.stats = await this.expandStats(definition.stats, definition.dictionary);
-
definition.frequencies = [];
- for (const meta of await this.database.findKanjiMeta(definition.character, titles)) {
- if (meta.mode === 'freq') {
- definition.frequencies.push({
- character: meta.character,
- frequency: meta.data,
- dictionary: meta.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
+ });
}
return definitions;
@@ -359,56 +403,76 @@ class Translator {
}
async expandTags(names, title) {
- const tags = [];
- for (const name of names) {
- const base = Translator.getNameBase(name);
- let meta = this.database.findTagForTitleCached(base, title);
- if (typeof meta === 'undefined') {
- meta = await this.database.findTagForTitle(base, title);
- }
-
- const tag = Object.assign({}, meta !== null ? meta : {}, {name});
-
- tags.push(dictTagSanitize(tag));
- }
-
- return tags;
+ const tagMetaList = await this.getTagMetaList(names, title);
+ return tagMetaList.map((meta, index) => {
+ const name = names[index];
+ const tag = dictTagSanitize(Object.assign({}, meta !== null ? meta : {}, {name}));
+ return dictTagSanitize(tag);
+ });
}
async expandStats(items, title) {
- const stats = {};
- for (const name in items) {
- const base = Translator.getNameBase(name);
- let meta = this.database.findTagForTitleCached(base, title);
- if (typeof meta === 'undefined') {
- meta = await this.database.findTagForTitle(base, title);
- if (meta === null) {
- continue;
- }
- }
+ const names = Object.keys(items);
+ const tagMetaList = await this.getTagMetaList(names, title);
- const group = stats[meta.category] = stats[meta.category] || [];
+ const stats = {};
+ for (let i = 0; i < names.length; ++i) {
+ const name = names[i];
+ const meta = tagMetaList[i];
+ if (meta === null) { continue; }
+
+ const category = meta.category;
+ const group = (
+ stats.hasOwnProperty(category) ?
+ stats[category] :
+ (stats[category] = [])
+ );
const stat = Object.assign({}, meta, {name, value: items[name]});
-
group.push(dictTagSanitize(stat));
}
+ const sortCompare = (a, b) => a.notes - b.notes;
for (const category in stats) {
- stats[category].sort((a, b) => {
- if (a.notes < b.notes) {
- return -1;
- } else if (a.notes > b.notes) {
- return 1;
- } else {
- return 0;
- }
- });
+ stats[category].sort(sortCompare);
}
return stats;
}
+ async getTagMetaList(names, title) {
+ const tagMetaList = [];
+ const cache = (
+ this.tagCache.hasOwnProperty(title) ?
+ this.tagCache[title] :
+ (this.tagCache[title] = {})
+ );
+
+ for (const name of names) {
+ const base = Translator.getNameBase(name);
+
+ if (cache.hasOwnProperty(base)) {
+ tagMetaList.push(cache[base]);
+ } else {
+ const tagMeta = await this.database.findTagForTitle(base, title);
+ cache[base] = tagMeta;
+ tagMetaList.push(tagMeta);
+ }
+ }
+
+ return tagMetaList;
+ }
+
+ static scoreToTermFrequency(score) {
+ if (score > 0) {
+ return 'popular';
+ } else if (score < 0) {
+ return 'rare';
+ } else {
+ return 'normal';
+ }
+ }
+
static getNameBase(name) {
const pos = name.indexOf(':');
return (pos >= 0 ? name.substr(0, pos) : name);
diff --git a/ext/bg/js/util.js b/ext/bg/js/util.js
index 73a8396f..1ca0833b 100644
--- a/ext/bg/js/util.js
+++ b/ext/bg/js/util.js
@@ -89,7 +89,7 @@ function utilAnkiGetModelFieldNames(modelName) {
}
function utilDatabasePurge() {
- return utilBackend().translator.database.purge();
+ return utilBackend().translator.purgeDatabase();
}
async function utilDatabaseImport(data, progress, exceptions) {