aboutsummaryrefslogtreecommitdiff
path: root/ext
diff options
context:
space:
mode:
authorAlex Yatskov <alex@foosoft.net>2017-09-23 12:42:02 -0700
committerAlex Yatskov <alex@foosoft.net>2017-09-23 12:42:02 -0700
commit327027d820759c62f50454822c11c0b8859c1d04 (patch)
tree3a13a25634b7eaf702634a3d6baeb8806c48ab02 /ext
parent2eb85cb835a4aece7839eba25c0030e9eb186f85 (diff)
parentd13cb09fae53834baaac254d7b55d02a26f4a5c0 (diff)
Merge branch 'dev'
Diffstat (limited to 'ext')
-rw-r--r--ext/bg/js/anki.js2
-rw-r--r--ext/bg/js/api.js2
-rw-r--r--ext/bg/js/audio.js4
-rw-r--r--ext/bg/js/backend.js2
-rw-r--r--ext/bg/js/database.js366
-rw-r--r--ext/bg/js/dictionary.js34
-rw-r--r--ext/bg/js/options.js3
-rw-r--r--ext/bg/js/request.js4
-rw-r--r--ext/bg/js/search.js2
-rw-r--r--ext/bg/js/settings.js42
-rw-r--r--ext/bg/js/templates.js216
-rw-r--r--ext/bg/js/translator.js114
-rw-r--r--ext/bg/js/util.js4
-rw-r--r--ext/bg/settings.html27
-rw-r--r--ext/fg/js/document.js2
-rw-r--r--ext/fg/js/float.js2
-rw-r--r--ext/fg/js/frontend.js38
-rw-r--r--ext/fg/js/popup.js15
-rw-r--r--ext/fg/js/source.js4
-rw-r--r--ext/manifest.json2
-rw-r--r--ext/mixed/css/display.css16
-rw-r--r--ext/mixed/js/display.js6
22 files changed, 631 insertions, 276 deletions
diff --git a/ext/bg/js/anki.js b/ext/bg/js/anki.js
index c327969f..183f37bc 100644
--- a/ext/bg/js/anki.js
+++ b/ext/bg/js/anki.js
@@ -62,7 +62,7 @@ class AnkiConnect {
if (this.remoteVersion < this.localVersion) {
this.remoteVersion = await this.ankiInvoke('version');
if (this.remoteVersion < this.localVersion) {
- throw 'extension and plugin versions incompatible';
+ throw 'Extension and plugin versions incompatible';
}
}
}
diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js
index 5c1aebb6..9f65bb07 100644
--- a/ext/bg/js/api.js
+++ b/ext/bg/js/api.js
@@ -31,7 +31,7 @@ async function apiTermsFind(text) {
const searcher = options.general.groupResults ?
translator.findTermsGrouped.bind(translator) :
- translator.findTerms.bind(translator);
+ translator.findTermsSplit.bind(translator);
const {definitions, length} = await searcher(
text,
diff --git a/ext/bg/js/audio.js b/ext/bg/js/audio.js
index 0952887e..ce47490c 100644
--- a/ext/bg/js/audio.js
+++ b/ext/bg/js/audio.js
@@ -57,7 +57,7 @@ async function audioBuildUrl(definition, mode, cache={}) {
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://www.japanesepod101.com/learningcenter/reference/dictionary_post');
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
- xhr.addEventListener('error', () => reject('failed to scrape audio data'));
+ xhr.addEventListener('error', () => reject('Failed to scrape audio data'));
xhr.addEventListener('load', () => {
cache[definition.expression] = xhr.responseText;
resolve(xhr.responseText);
@@ -87,7 +87,7 @@ async function audioBuildUrl(definition, mode, cache={}) {
} else {
const xhr = new XMLHttpRequest();
xhr.open('GET', `http://jisho.org/search/${definition.expression}`);
- xhr.addEventListener('error', () => reject('failed to scrape audio data'));
+ xhr.addEventListener('error', () => reject('Failed to scrape audio data'));
xhr.addEventListener('load', () => {
cache[definition.expression] = xhr.responseText;
resolve(xhr.responseText);
diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js
index 7d68ed84..01340419 100644
--- a/ext/bg/js/backend.js
+++ b/ext/bg/js/backend.js
@@ -71,7 +71,7 @@ class Backend {
return promise.then(result => {
callback({result});
}).catch(error => {
- callback({error});
+ callback({error: error.toString ? error.toString() : error});
});
};
diff --git a/ext/bg/js/database.js b/ext/bg/js/database.js
index e00cb7a3..6ceb3ec8 100644
--- a/ext/bg/js/database.js
+++ b/ext/bg/js/database.js
@@ -20,44 +20,33 @@
class Database {
constructor() {
this.db = null;
- this.version = 2;
this.tagCache = {};
}
- async sanitize() {
- try {
- const db = new Dexie('dict');
- await db.open();
- db.close();
- if (db.verno !== this.version) {
- await db.delete();
- }
- } catch(e) {
- // NOP
- }
- }
-
async prepare() {
if (this.db) {
- throw 'database already initialized';
+ throw 'Database already initialized';
}
- await this.sanitize();
-
this.db = new Dexie('dict');
- this.db.version(this.version).stores({
- terms: '++id,dictionary,expression,reading',
- kanji: '++,dictionary,character',
- tagMeta: '++,dictionary',
+ this.db.version(2).stores({
+ terms: '++id,dictionary,expression,reading',
+ kanji: '++,dictionary,character',
+ tagMeta: '++,dictionary',
dictionaries: '++,title,version'
});
+ this.db.version(3).stores({
+ termMeta: '++,dictionary,expression',
+ kanjiMeta: '++,dictionary,character',
+ tagMeta: '++,dictionary,name'
+ });
await this.db.open();
}
async purge() {
if (!this.db) {
- throw 'database not initialized';
+ throw 'Database not initialized';
}
this.db.close();
@@ -70,7 +59,7 @@ class Database {
async findTerms(term, titles) {
if (!this.db) {
- throw 'database not initialized';
+ throw 'Database not initialized';
}
const results = [];
@@ -89,17 +78,31 @@ class Database {
}
});
- await this.cacheTagMeta(titles);
- for (const result of results) {
- result.tagMeta = this.tagCache[result.dictionary] || {};
+ return results;
+ }
+
+ async findTermMeta(term, titles) {
+ if (!this.db) {
+ throw 'Database not initialized';
}
+ 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 findKanji(kanji, titles) {
if (!this.db) {
- return Promise.reject('database not initialized');
+ throw 'Database not initialized';
}
const results = [];
@@ -111,165 +114,304 @@ class Database {
kunyomi: dictFieldSplit(row.kunyomi),
tags: dictFieldSplit(row.tags),
glossary: row.meanings,
+ stats: row.stats,
dictionary: row.dictionary
});
}
});
- await this.cacheTagMeta(titles);
- for (const result of results) {
- result.tagMeta = this.tagCache[result.dictionary] || {};
+ return results;
+ }
+
+ async findKanjiMeta(kanji, titles) {
+ if (!this.db) {
+ throw 'Database not initialized';
}
+ const results = [];
+ await this.db.kanjiMeta.where('character').equals(kanji).each(row => {
+ if (titles.includes(row.dictionary)) {
+ results.push({
+ mode: row.mode,
+ data: row.data,
+ dictionary: row.dictionary
+ });
+ }
+ });
+
return results;
}
- async cacheTagMeta(titles) {
+ async findTagForTitle(name, title) {
if (!this.db) {
- throw 'database not initialized';
+ throw 'Database not initialized';
}
- for (const title of titles) {
- if (!this.tagCache[title]) {
- const tagMeta = {};
- await this.db.tagMeta.where('dictionary').equals(title).each(row => {
- tagMeta[row.name] = {category: row.category, notes: row.notes, order: row.order};
- });
+ this.tagCache[title] = this.tagCache[title] || {};
- this.tagCache[title] = tagMeta;
- }
+ let result = this.tagCache[title][name];
+ if (!result) {
+ await this.db.tagMeta.where('name').equals(name).each(row => {
+ if (title === row.dictionary) {
+ result = row;
+ }
+ });
+
+ this.tagCache[title][name] = result;
}
+
+ return result;
}
- async getDictionaries() {
+ async getTitles() {
if (this.db) {
return this.db.dictionaries.toArray();
} else {
- throw 'database not initialized';
+ throw 'Database not initialized';
}
}
async importDictionary(archive, callback) {
if (!this.db) {
- return Promise.reject('database not initialized');
+ throw 'Database not initialized';
}
- let summary = null;
- const indexLoaded = async (title, version, revision, tagMeta, hasTerms, hasKanji) => {
- summary = {title, version, revision, hasTerms, hasKanji};
+ const indexDataLoaded = async summary => {
+ if (summary.version > 2) {
+ throw 'Unsupported dictionary version';
+ }
- const count = await this.db.dictionaries.where('title').equals(title).count();
+ const count = await this.db.dictionaries.where('title').equals(summary.title).count();
if (count > 0) {
- throw `dictionary "${title}" is already imported`;
+ throw 'Dictionary is already imported';
}
- await this.db.dictionaries.add({title, version, revision, hasTerms, hasKanji});
+ await this.db.dictionaries.add(summary);
+ };
- const rows = [];
- for (const tag in tagMeta || {}) {
- const meta = tagMeta[tag];
- const row = dictTagSanitize({
- name: tag,
- category: meta.category,
- notes: meta.notes,
- order: meta.order,
- dictionary: title
- });
+ const termDataLoaded = async (summary, entries, total, current) => {
+ if (callback) {
+ callback(total, current);
+ }
- rows.push(row);
+ const rows = [];
+ if (summary.version === 1) {
+ for (const [expression, reading, tags, rules, score, ...glossary] of entries) {
+ rows.push({
+ expression,
+ reading,
+ tags,
+ rules,
+ score,
+ glossary,
+ dictionary: summary.title
+ });
+ }
+ } else {
+ for (const [expression, reading, tags, rules, score, glossary] of entries) {
+ rows.push({
+ expression,
+ reading,
+ tags,
+ rules,
+ score,
+ glossary,
+ dictionary: summary.title
+ });
+ }
}
- await this.db.tagMeta.bulkAdd(rows);
+ await this.db.terms.bulkAdd(rows);
};
- const termsLoaded = async (title, entries, total, current) => {
+ const termMetaDataLoaded = async (summary, entries, total, current) => {
if (callback) {
callback(total, current);
}
const rows = [];
- for (const [expression, reading, tags, rules, score, ...glossary] of entries) {
+ for (const [expression, mode, data] of entries) {
rows.push({
expression,
- reading,
- tags,
- rules,
- score,
- glossary,
- dictionary: title
+ mode,
+ data,
+ dictionary: summary.title
});
}
- await this.db.terms.bulkAdd(rows);
+ await this.db.termMeta.bulkAdd(rows);
+ };
+
+ const kanjiDataLoaded = async (summary, entries, total, current) => {
+ if (callback) {
+ callback(total, current);
+ }
+
+ const rows = [];
+ if (summary.version === 1) {
+ for (const [character, onyomi, kunyomi, tags, ...meanings] of entries) {
+ rows.push({
+ character,
+ onyomi,
+ kunyomi,
+ tags,
+ meanings,
+ dictionary: summary.title
+ });
+ }
+ } else {
+ for (const [character, onyomi, kunyomi, tags, meanings, stats] of entries) {
+ rows.push({
+ character,
+ onyomi,
+ kunyomi,
+ tags,
+ meanings,
+ stats,
+ dictionary: summary.title
+ });
+ }
+ }
+
+ await this.db.kanji.bulkAdd(rows);
};
- const kanjiLoaded = async (title, entries, total, current) => {
+ const kanjiMetaDataLoaded = async (summary, entries, total, current) => {
if (callback) {
callback(total, current);
}
const rows = [];
- for (const [character, onyomi, kunyomi, tags, ...meanings] of entries) {
+ for (const [character, mode, data] of entries) {
rows.push({
character,
- onyomi,
- kunyomi,
- tags,
- meanings,
- dictionary: title
+ mode,
+ data,
+ dictionary: summary.title
});
}
- await this.db.kanji.bulkAdd(rows);
+ await this.db.kanjiMeta.bulkAdd(rows);
};
- await Database.importDictionaryZip(archive, indexLoaded, termsLoaded, kanjiLoaded);
- return summary;
- }
+ const tagDataLoaded = async (summary, entries, total, current) => {
+ if (callback) {
+ callback(total, current);
+ }
- static async importDictionaryZip(archive, indexLoaded, termsLoaded, kanjiLoaded) {
- const files = (await JSZip.loadAsync(archive)).files;
+ const rows = [];
+ for (const [name, category, order, notes] of entries) {
+ const row = dictTagSanitize({
+ name,
+ category,
+ order,
+ notes,
+ dictionary: summary.title
+ });
+
+ rows.push(row);
+ }
- const indexFile = files['index.json'];
+ await this.db.tagMeta.bulkAdd(rows);
+ };
+
+ return await Database.importDictionaryZip(
+ archive,
+ indexDataLoaded,
+ termDataLoaded,
+ termMetaDataLoaded,
+ kanjiDataLoaded,
+ kanjiMetaDataLoaded,
+ tagDataLoaded
+ );
+ }
+
+ static async importDictionaryZip(
+ archive,
+ indexDataLoaded,
+ termDataLoaded,
+ termMetaDataLoaded,
+ kanjiDataLoaded,
+ kanjiMetaDataLoaded,
+ tagDataLoaded
+ ) {
+ const zip = await JSZip.loadAsync(archive);
+
+ const indexFile = zip.files['index.json'];
if (!indexFile) {
- throw 'no dictionary index found in archive';
+ throw 'No dictionary index found in archive';
}
const index = JSON.parse(await indexFile.async('string'));
- if (!index.title || !index.version || !index.revision) {
- throw 'unrecognized dictionary format';
+ if (!index.title || !index.revision) {
+ throw 'Unrecognized dictionary format';
}
- await indexLoaded(
- index.title,
- index.version,
- index.revision,
- index.tagMeta || {},
- index.termBanks > 0,
- index.kanjiBanks > 0
- );
+ const summary = {
+ title: index.title,
+ revision: index.revision,
+ version: index.format || index.version
+ };
+
+ if (indexDataLoaded) {
+ await indexDataLoaded(summary);
+ }
- const banksTotal = index.termBanks + index.kanjiBanks;
- let banksLoaded = 0;
+ const buildTermBankName = index => `term_bank_${index + 1}.json`;
+ const buildTermMetaBankName = index => `term_meta_bank_${index + 1}.json`;
+ const buildKanjiBankName = index => `kanji_bank_${index + 1}.json`;
+ const buildKanjiMetaBankName = index => `kanji_meta_bank_${index + 1}.json`;
+ const buildTagBankName = index => `tag_bank_${index + 1}.json`;
- for (let i = 1; i <= index.termBanks; ++i) {
- const bankFile = files[`term_bank_${i}.json`];
- if (bankFile) {
- const bank = JSON.parse(await bankFile.async('string'));
- await termsLoaded(index.title, bank, banksTotal, banksLoaded++);
- } else {
- throw 'missing term bank file';
+ const countBanks = namer => {
+ let count = 0;
+ while (zip.files[namer(count)]) {
+ ++count;
}
- }
- for (let i = 1; i <= index.kanjiBanks; ++i) {
- const bankFile = files[`kanji_bank_${i}.json`];
- if (bankFile) {
- const bank = JSON.parse(await bankFile.async('string'));
- await kanjiLoaded(index.title, bank, banksTotal, banksLoaded++);
- } else {
- throw 'missing kanji bank file';
+ return count;
+ };
+
+ const termBankCount = countBanks(buildTermBankName);
+ const termMetaBankCount = countBanks(buildTermMetaBankName);
+ const kanjiBankCount = countBanks(buildKanjiBankName);
+ const kanjiMetaBankCount = countBanks(buildKanjiMetaBankName);
+ const tagBankCount = countBanks(buildTagBankName);
+
+ let bankLoadedCount = 0;
+ let bankTotalCount =
+ termBankCount +
+ termMetaBankCount +
+ kanjiBankCount +
+ kanjiMetaBankCount +
+ tagBankCount;
+
+ if (tagDataLoaded && index.tagMeta) {
+ const bank = [];
+ for (const name in index.tagMeta) {
+ const tag = index.tagMeta[name];
+ bank.push([name, tag.category, tag.order, tag.notes]);
}
+
+ tagDataLoaded(summary, bank, ++bankTotalCount, bankLoadedCount++);
}
+
+ const loadBank = async (summary, namer, count, callback) => {
+ if (callback) {
+ for (let i = 0; i < count; ++i) {
+ const bankFile = zip.files[namer(i)];
+ const bank = JSON.parse(await bankFile.async('string'));
+ await callback(summary, bank, bankTotalCount, bankLoadedCount++);
+ }
+ }
+ };
+
+ await loadBank(summary, buildTermBankName, termBankCount, termDataLoaded);
+ await loadBank(summary, buildTermMetaBankName, termMetaBankCount, termMetaDataLoaded);
+ await loadBank(summary, buildKanjiBankName, kanjiBankCount, kanjiDataLoaded);
+ await loadBank(summary, buildKanjiMetaBankName, kanjiMetaBankCount, kanjiMetaDataLoaded);
+ await loadBank(summary, buildTagBankName, tagBankCount, tagDataLoaded);
+
+ return summary;
}
}
diff --git a/ext/bg/js/dictionary.js b/ext/bg/js/dictionary.js
index e749390f..57acbe5e 100644
--- a/ext/bg/js/dictionary.js
+++ b/ext/bg/js/dictionary.js
@@ -55,14 +55,6 @@ function dictRowsSort(rows, options) {
function dictTermsSort(definitions, dictionaries=null) {
return definitions.sort((v1, v2) => {
- const sl1 = v1.source.length;
- const sl2 = v2.source.length;
- if (sl1 > sl2) {
- return -1;
- } else if (sl1 < sl2) {
- return 1;
- }
-
if (dictionaries !== null) {
const p1 = (dictionaries[v1.dictionary] || {}).priority || 0;
const p2 = (dictionaries[v2.dictionary] || {}).priority || 0;
@@ -73,11 +65,11 @@ function dictTermsSort(definitions, dictionaries=null) {
}
}
- const s1 = v1.score;
- const s2 = v2.score;
- if (s1 > s2) {
+ const sl1 = v1.source.length;
+ const sl2 = v2.source.length;
+ if (sl1 > sl2) {
return -1;
- } else if (s1 < s2) {
+ } else if (sl1 < sl2) {
return 1;
}
@@ -89,6 +81,14 @@ function dictTermsSort(definitions, dictionaries=null) {
return 1;
}
+ const s1 = v1.score;
+ const s2 = v2.score;
+ if (s1 > s2) {
+ return -1;
+ } else if (s1 < s2) {
+ return 1;
+ }
+
return v2.expression.localeCompare(v1.expression);
});
}
@@ -148,16 +148,6 @@ function dictTagBuildSource(name) {
return dictTagSanitize({name, category: 'dictionary', order: 100});
}
-function dictTagBuild(name, meta) {
- const tag = {name};
- const symbol = name.split(':')[0];
- for (const prop in meta[symbol] || {}) {
- tag[prop] = meta[symbol][prop];
- }
-
- return dictTagSanitize(tag);
-}
-
function dictTagSanitize(tag) {
tag.name = tag.name || 'untitled';
tag.category = tag.category || 'default';
diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js
index dcad97d4..36ab7694 100644
--- a/ext/bg/js/options.js
+++ b/ext/bg/js/options.js
@@ -146,7 +146,8 @@ function optionsSetDefaults(options) {
middleMouse: true,
selectText: true,
alphanumeric: true,
- delay: 15,
+ autoHideResults: false,
+ delay: 20,
length: 10,
modifier: 'shift'
},
diff --git a/ext/bg/js/request.js b/ext/bg/js/request.js
index 94fd135a..e4359863 100644
--- a/ext/bg/js/request.js
+++ b/ext/bg/js/request.js
@@ -22,7 +22,7 @@ function requestJson(url, action, params) {
const xhr = new XMLHttpRequest();
xhr.overrideMimeType('application/json');
xhr.addEventListener('load', () => resolve(xhr.responseText));
- xhr.addEventListener('error', () => reject('failed to execute network request'));
+ xhr.addEventListener('error', () => reject('Failed to connect'));
xhr.open(action, url);
if (params) {
xhr.send(JSON.stringify(params));
@@ -34,7 +34,7 @@ function requestJson(url, action, params) {
return JSON.parse(responseText);
}
catch (e) {
- return Promise.reject('invalid JSON response');
+ return Promise.reject('Invalid response');
}
});
}
diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js
index 54cda8ec..40bf2019 100644
--- a/ext/bg/js/search.js
+++ b/ext/bg/js/search.js
@@ -29,7 +29,7 @@ class DisplaySearch extends Display {
}
onError(error) {
- window.alert(`Error: ${error}`);
+ window.alert(`Error: ${error.toString ? error.toString() : error}`);
}
onSearchClear() {
diff --git a/ext/bg/js/settings.js b/ext/bg/js/settings.js
index 55b469d0..a2f22371 100644
--- a/ext/bg/js/settings.js
+++ b/ext/bg/js/settings.js
@@ -35,6 +35,7 @@ async function formRead() {
optionsNew.scanning.middleMouse = $('#middle-mouse-button-scan').prop('checked');
optionsNew.scanning.selectText = $('#select-matched-text').prop('checked');
optionsNew.scanning.alphanumeric = $('#search-alphanumeric').prop('checked');
+ optionsNew.scanning.autoHideResults = $('#auto-hide-results').prop('checked');
optionsNew.scanning.delay = parseInt($('#scan-delay').val(), 10);
optionsNew.scanning.length = parseInt($('#scan-length').val(), 10);
optionsNew.scanning.modifier = $('#scan-modifier-key').val();
@@ -82,7 +83,9 @@ function formUpdateVisibility(options) {
const debug = $('#debug');
if (options.general.debugInfo) {
- const text = JSON.stringify(options, null, 4);
+ const temp = utilIsolate(options);
+ temp.anki.fieldTemplates = '...';
+ const text = JSON.stringify(temp, null, 4);
debug.html(handlebarsEscape(text));
debug.show();
} else {
@@ -134,11 +137,12 @@ async function onReady() {
$('#middle-mouse-button-scan').prop('checked', options.scanning.middleMouse);
$('#select-matched-text').prop('checked', options.scanning.selectText);
$('#search-alphanumeric').prop('checked', options.scanning.alphanumeric);
+ $('#auto-hide-results').prop('checked', options.scanning.autoHideResults);
$('#scan-delay').val(options.scanning.delay);
$('#scan-length').val(options.scanning.length);
$('#scan-modifier-key').val(options.scanning.modifier);
- $('#dict-purge').click(utilAsync(onDictionaryPurge));
+ $('#dict-purge-link').click(utilAsync(onDictionaryPurge));
$('#dict-file').change(utilAsync(onDictionaryImport));
$('#anki-enable').prop('checked', options.anki.enable);
@@ -175,7 +179,33 @@ $(document).ready(utilAsync(onReady));
function dictionaryErrorShow(error) {
const dialog = $('#dict-error');
if (error) {
- dialog.show().find('span').text(error);
+ const overrides = [
+ [
+ 'A mutation operation was attempted on a database that did not allow mutations.',
+ 'Access to IndexedDB appears to be restricted. Firefox seems to require that the history preference is set to "Remember history" before IndexedDB use of any kind is allowed.'
+ ],
+ [
+ 'The operation failed for reasons unrelated to the database itself and not covered by any other error code.',
+ 'Unable to access IndexedDB due to a possibly corrupt user profile. Try using the "Refresh Firefox" feature to reset your user profile.'
+ ],
+ [
+ 'BulkError',
+ 'Unable to finish importing dictionary data into IndexedDB. This may indicate that you do not have sufficient disk space available to complete this operation.'
+ ]
+ ];
+
+ if (error.toString) {
+ error = error.toString();
+ }
+
+ for (const [match, subst] of overrides) {
+ if (error.includes(match)) {
+ error = subst;
+ break;
+ }
+ }
+
+ dialog.show().text(error);
} else {
dialog.hide();
}
@@ -211,7 +241,7 @@ async function dictionaryGroupsPopulate(options) {
const dictGroups = $('#dict-groups').empty();
const dictWarning = $('#dict-warning').hide();
- const dictRows = await utilDatabaseGetDictionaries();
+ const dictRows = await utilDatabaseGetTitles();
if (dictRows.length === 0) {
dictWarning.show();
}
@@ -241,7 +271,7 @@ async function onDictionaryPurge(e) {
e.preventDefault();
const dictControls = $('#dict-importer, #dict-groups').hide();
- const dictProgress = $('#dict-purge-progress').show();
+ const dictProgress = $('#dict-purge').show();
try {
dictionaryErrorShow();
@@ -310,7 +340,7 @@ function ankiSpinnerShow(show) {
function ankiErrorShow(error) {
const dialog = $('#anki-error');
if (error) {
- dialog.show().find('span').text(error);
+ dialog.show().text(error);
}
else {
dialog.hide();
diff --git a/ext/bg/js/templates.js b/ext/bg/js/templates.js
index 1c059365..b5d352fc 100644
--- a/ext/bg/js/templates.js
+++ b/ext/bg/js/templates.js
@@ -22,38 +22,87 @@ templates['dictionary.html'] = template({"1":function(container,depth0,helpers,p
+ "\" class=\"form-control dict-priority\">\n </div>\n</div>\n";
},"useData":true});
templates['kanji.html'] = template({"1":function(container,depth0,helpers,partials,data) {
+ var stack1;
+
+ return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.data : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.program(8, data, 0),"data":data})) != null ? stack1 : "");
+},"2":function(container,depth0,helpers,partials,data) {
+ var stack1;
+
+ return "<table class=\"info-output\">\n"
+ + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.data : depth0),{"name":"each","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + "</table>\n";
+},"3":function(container,depth0,helpers,partials,data) {
+ var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {});
+
+ return " <tr>\n <th>"
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.notes : depth0),{"name":"if","hash":{},"fn":container.program(4, data, 0),"inverse":container.program(6, data, 0),"data":data})) != null ? stack1 : "")
+ + "</th>\n <td>"
+ + container.escapeExpression(((helper = (helper = helpers.value || (depth0 != null ? depth0.value : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(alias1,{"name":"value","hash":{},"data":data}) : helper)))
+ + "</td>\n </tr>\n";
+},"4":function(container,depth0,helpers,partials,data) {
+ var helper;
+
+ return container.escapeExpression(((helper = (helper = helpers.notes || (depth0 != null ? depth0.notes : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"notes","hash":{},"data":data}) : helper)));
+},"6":function(container,depth0,helpers,partials,data) {
+ var helper;
+
+ return container.escapeExpression(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"name","hash":{},"data":data}) : helper)));
+},"8":function(container,depth0,helpers,partials,data) {
+ return "No data found\n";
+},"10":function(container,depth0,helpers,partials,data) {
var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {});
return "<div class=\"entry\" data-type=\"kanji\">\n <div class=\"actions\">\n"
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.addable : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.source : 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.addable : depth0),{"name":"if","hash":{},"fn":container.program(11, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.source : depth0),{"name":"if","hash":{},"fn":container.program(13, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ " <img src=\"/mixed/img/entry-current.png\" class=\"current\" title=\"Current entry (Alt + Up/Down/Home/End/PgUp/PgDn)\" alt>\n </div>\n\n <div class=\"glyph\">"
+ container.escapeExpression(((helper = (helper = helpers.character || (depth0 != null ? depth0.character : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(alias1,{"name":"character","hash":{},"data":data}) : helper)))
- + "</div>\n\n <div class=\"reading\">\n <table>\n <tr>\n <th>Kunyomi:</th>\n <td>\n"
- + ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.kunyomi : depth0),{"name":"each","hash":{},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + " </td>\n </tr>\n <tr>\n <th>Onyomi:</th>\n <td>\n"
- + ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.onyomi : depth0),{"name":"each","hash":{},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + " </td>\n </tr>\n </table>\n </div>\n\n <div>\n"
- + ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.tags : depth0),{"name":"each","hash":{},"fn":container.program(9, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + " </div>\n\n <div class=\"glossary\">\n"
- + ((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(15, data, 0),"data":data})) != null ? stack1 : "")
- + " </div>\n\n"
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.debug : depth0),{"name":"if","hash":{},"fn":container.program(18, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + "</div>\n\n"
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.frequencies : depth0),{"name":"if","hash":{},"fn":container.program(15, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + "\n"
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.tags : depth0),{"name":"if","hash":{},"fn":container.program(18, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + "\n <table class=\"table table-condensed glyph-data\">\n <tr>\n <th>Glossary</th>\n <th>Readings</th>\n <th>Statistics</th>\n </tr>\n <tr>\n <td class=\"glossary\">\n"
+ + ((stack1 = helpers["if"].call(alias1,((stack1 = (depth0 != null ? depth0.glossary : depth0)) != null ? stack1["1"] : stack1),{"name":"if","hash":{},"fn":container.program(21, data, 0),"inverse":container.program(24, data, 0),"data":data})) != null ? stack1 : "")
+ + " </td>\n <td class=\"reading\">\n "
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.onyomi : depth0),{"name":"if","hash":{},"fn":container.program(26, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + "\n "
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.kunyomi : depth0),{"name":"if","hash":{},"fn":container.program(29, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + "\n </td>\n <td>"
+ + ((stack1 = container.invokePartial(partials.table,depth0,{"name":"table","hash":{"data":((stack1 = (depth0 != null ? depth0.stats : depth0)) != null ? stack1.misc : stack1)},"data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
+ + "</td>\n </tr>\n <tr>\n <th colspan=\"3\">Classifications</th>\n </tr>\n <tr>\n <td colspan=\"3\">"
+ + ((stack1 = container.invokePartial(partials.table,depth0,{"name":"table","hash":{"data":((stack1 = (depth0 != null ? depth0.stats : depth0)) != null ? stack1["class"] : stack1)},"data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
+ + "</td>\n </tr>\n <tr>\n <th colspan=\"3\">Codepoints</th>\n </tr>\n <tr>\n <td colspan=\"3\">"
+ + ((stack1 = container.invokePartial(partials.table,depth0,{"name":"table","hash":{"data":((stack1 = (depth0 != null ? depth0.stats : depth0)) != null ? stack1.code : stack1)},"data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
+ + "</td>\n </tr>\n <tr>\n <th colspan=\"3\">Dictionary Indices</th>\n </tr>\n <tr>\n <td colspan=\"3\">"
+ + ((stack1 = container.invokePartial(partials.table,depth0,{"name":"table","hash":{"data":((stack1 = (depth0 != null ? depth0.stats : depth0)) != null ? stack1.index : stack1)},"data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
+ + "</td>\n </tr>\n </table>\n\n"
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.debug : depth0),{"name":"if","hash":{},"fn":container.program(31, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "</div>\n";
-},"2":function(container,depth0,helpers,partials,data) {
+},"11":function(container,depth0,helpers,partials,data) {
return " <a href=\"#\" class=\"action-view-note pending disabled\"><img src=\"/mixed/img/view-note.png\" title=\"View added note (Alt + V)\" alt></a>\n <a href=\"#\" class=\"action-add-note pending disabled\" data-mode=\"kanji\"><img src=\"/mixed/img/add-kanji.png\" title=\"Add Kanji (Alt + K)\" alt></a>\n";
-},"4":function(container,depth0,helpers,partials,data) {
+},"13":function(container,depth0,helpers,partials,data) {
return " <a href=\"#\" class=\"source-term\"><img src=\"/mixed/img/source-term.png\" title=\"Source term (Alt + B)\" alt></a>\n";
-},"6":function(container,depth0,helpers,partials,data) {
+},"15":function(container,depth0,helpers,partials,data) {
var stack1;
- return " "
- + container.escapeExpression(container.lambda(depth0, depth0))
- + ((stack1 = helpers.unless.call(depth0 != null ? depth0 : (container.nullContext || {}),(data && data.last),{"name":"unless","hash":{},"fn":container.program(7, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + "\n";
-},"7":function(container,depth0,helpers,partials,data) {
- return ", ";
-},"9":function(container,depth0,helpers,partials,data) {
+ return " <div>\n"
+ + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.frequencies : depth0),{"name":"each","hash":{},"fn":container.program(16, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + " </div>\n";
+},"16":function(container,depth0,helpers,partials,data) {
+ var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
+
+ return " <span class=\"label label-default tag-frequency\">"
+ + alias4(((helper = (helper = helpers.dictionary || (depth0 != null ? depth0.dictionary : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"dictionary","hash":{},"data":data}) : helper)))
+ + ":"
+ + alias4(((helper = (helper = helpers.frequency || (depth0 != null ? depth0.frequency : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"frequency","hash":{},"data":data}) : helper)))
+ + "</span>\n";
+},"18":function(container,depth0,helpers,partials,data) {
+ var stack1;
+
+ return " <div>\n"
+ + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.tags : depth0),{"name":"each","hash":{},"fn":container.program(19, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + " </div>\n";
+},"19":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-"
@@ -63,67 +112,74 @@ templates['kanji.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";
-},"11":function(container,depth0,helpers,partials,data) {
+},"21":function(container,depth0,helpers,partials,data) {
var stack1;
- return " <ol>\n"
- + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.glossary : depth0),{"name":"each","hash":{},"fn":container.program(12, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + " </ol>\n";
-},"12":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(13, 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";
-},"13":function(container,depth0,helpers,partials,data) {
- return container.escapeExpression(container.lambda(depth0, depth0));
-},"15":function(container,depth0,helpers,partials,data) {
- var stack1, helper, options, buffer =
- " <div class=\"glossary-item\">";
- stack1 = ((helper = (helper = helpers.multiLine || (depth0 != null ? depth0.multiLine : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"multiLine","hash":{},"fn":container.program(16, 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 + "</div>\n";
-},"16":function(container,depth0,helpers,partials,data) {
+ return " <ol>"
+ + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.glossary : depth0),{"name":"each","hash":{},"fn":container.program(22, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + "</ol>\n";
+},"22":function(container,depth0,helpers,partials,data) {
+ return "<li><span class=\"glossary-item\">"
+ + container.escapeExpression(container.lambda(depth0, depth0))
+ + "</span></li>";
+},"24":function(container,depth0,helpers,partials,data) {
var stack1;
- return container.escapeExpression(container.lambda(((stack1 = (depth0 != null ? depth0.glossary : depth0)) != null ? stack1["0"] : stack1), depth0));
-},"18":function(container,depth0,helpers,partials,data) {
+ return " <span class=\"glossary-item\">"
+ + container.escapeExpression(container.lambda(((stack1 = (depth0 != null ? depth0.glossary : depth0)) != null ? stack1["0"] : stack1), depth0))
+ + "</span>\n";
+},"26":function(container,depth0,helpers,partials,data) {
+ var stack1;
+
+ return "<dl>"
+ + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.onyomi : depth0),{"name":"each","hash":{},"fn":container.program(27, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + "</dl>";
+},"27":function(container,depth0,helpers,partials,data) {
+ return "<dd>"
+ + container.escapeExpression(container.lambda(depth0, depth0))
+ + "</dd>";
+},"29":function(container,depth0,helpers,partials,data) {
+ var stack1;
+
+ return "<dl>"
+ + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.kunyomi : depth0),{"name":"each","hash":{},"fn":container.program(27, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + "</dl>";
+},"31":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(19, 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(32, 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";
-},"19":function(container,depth0,helpers,partials,data) {
+},"32":function(container,depth0,helpers,partials,data) {
var stack1;
return ((stack1 = container.lambda(depth0, depth0)) != null ? stack1 : "");
-},"21":function(container,depth0,helpers,partials,data,blockParams,depths) {
+},"34":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(22, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "");
-},"22":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(35, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "");
+},"35":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(23, 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(36, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "\n"
+ ((stack1 = container.invokePartial(partials.kanji,depth0,{"name":"kanji","hash":{"root":(depths[1] != null ? depths[1].root : depths[1]),"source":(depths[1] != null ? depths[1].source : depths[1]),"addable":(depths[1] != null ? depths[1].addable : depths[1]),"debug":(depths[1] != null ? depths[1].debug : depths[1])},"data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
-},"23":function(container,depth0,helpers,partials,data) {
+},"36":function(container,depth0,helpers,partials,data) {
return "<hr>";
-},"25":function(container,depth0,helpers,partials,data) {
- return "<p class=\"note\">No results found.</p>\n";
+},"38":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"
- + ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.definitions : depth0),{"name":"if","hash":{},"fn":container.program(21, data, 0, blockParams, depths),"inverse":container.program(25, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "");
+ return "\n\n"
+ + ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.definitions : depth0),{"name":"if","hash":{},"fn":container.program(34, data, 0, blockParams, depths),"inverse":container.program(38, 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":["kanji"],"data":data}) || fn;
+ fn = decorators.inline(fn,props,container,{"name":"inline","hash":{},"fn":container.program(1, data, 0, blockParams, depths),"inverse":container.noop,"args":["table"],"data":data}) || fn;
+ fn = decorators.inline(fn,props,container,{"name":"inline","hash":{},"fn":container.program(10, data, 0, blockParams, depths),"inverse":container.noop,"args":["kanji"],"data":data}) || fn;
return fn;
}
@@ -203,10 +259,12 @@ templates['terms.html'] = template({"1":function(container,depth0,helpers,partia
if (stack1 != null) { buffer += stack1; }
return buffer + "</div>\n\n"
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.reasons : depth0),{"name":"if","hash":{},"fn":container.program(20, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + "\n"
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.frequencies : depth0),{"name":"if","hash":{},"fn":container.program(24, data, 0),"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(24, data, 0),"inverse":container.program(30, data, 0),"data":data})) != null ? stack1 : "")
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.grouped : depth0),{"name":"if","hash":{},"fn":container.program(27, data, 0),"inverse":container.program(33, data, 0),"data":data})) != null ? stack1 : "")
+ " </div>\n\n"
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.debug : depth0),{"name":"if","hash":{},"fn":container.program(32, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.debug : depth0),{"name":"if","hash":{},"fn":container.program(35, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "</div>\n";
},"13":function(container,depth0,helpers,partials,data) {
return " <a href=\"#\" class=\"action-view-note pending disabled\"><img src=\"/mixed/img/view-note.png\" 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.png\" 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.png\" title=\"Add reading (Alt + R)\" alt></a>\n";
@@ -242,53 +300,67 @@ templates['terms.html'] = template({"1":function(container,depth0,helpers,partia
},"24":function(container,depth0,helpers,partials,data) {
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(25, data, 0),"inverse":container.program(28, data, 0),"data":data})) != null ? stack1 : "");
+ return " <div>\n"
+ + ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.frequencies : depth0),{"name":"each","hash":{},"fn":container.program(25, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + " </div>\n";
},"25":function(container,depth0,helpers,partials,data) {
+ var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
+
+ return " <span class=\"label label-default tag-frequency\">"
+ + alias4(((helper = (helper = helpers.dictionary || (depth0 != null ? depth0.dictionary : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"dictionary","hash":{},"data":data}) : helper)))
+ + ":"
+ + alias4(((helper = (helper = helpers.frequency || (depth0 != null ? depth0.frequency : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"frequency","hash":{},"data":data}) : helper)))
+ + "</span>\n";
+},"27":function(container,depth0,helpers,partials,data) {
+ 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(28, data, 0),"inverse":container.program(31, data, 0),"data":data})) != null ? stack1 : "");
+},"28":function(container,depth0,helpers,partials,data) {
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(26, data, 0),"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(29, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ " </ol>\n";
-},"26":function(container,depth0,helpers,partials,data) {
+},"29":function(container,depth0,helpers,partials,data) {
var stack1;
return " <li>"
+ ((stack1 = container.invokePartial(partials.definition,depth0,{"name":"definition","data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
+ "</li>\n";
-},"28":function(container,depth0,helpers,partials,data) {
+},"31":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","data":data,"indent":" ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
-},"30":function(container,depth0,helpers,partials,data) {
+},"33":function(container,depth0,helpers,partials,data) {
var stack1;
return ((stack1 = container.invokePartial(partials.definition,depth0,{"name":"definition","data":data,"indent":" ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
-},"32":function(container,depth0,helpers,partials,data) {
+},"35":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(18, 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";
-},"34":function(container,depth0,helpers,partials,data,blockParams,depths) {
+},"37":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(35, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "");
-},"35":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(38, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "");
+},"38":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(36, 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(39, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "\n"
+ ((stack1 = container.invokePartial(partials.term,depth0,{"name":"term","hash":{"playback":(depths[1] != null ? depths[1].playback : depths[1]),"addable":(depths[1] != null ? depths[1].addable : 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 : "");
-},"36":function(container,depth0,helpers,partials,data) {
+},"39":function(container,depth0,helpers,partials,data) {
return "<hr>";
-},"38":function(container,depth0,helpers,partials,data) {
+},"41":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(34, data, 0, blockParams, depths),"inverse":container.program(38, 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(37, data, 0, blockParams, depths),"inverse":container.program(41, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "");
},"main_d": function(fn, props, container, depth0, data, blockParams, depths) {
var decorators = container.decorators;
diff --git a/ext/bg/js/translator.js b/ext/bg/js/translator.js
index 1be485c7..21881cf3 100644
--- a/ext/bg/js/translator.js
+++ b/ext/bg/js/translator.js
@@ -37,8 +37,26 @@ class Translator {
}
async findTermsGrouped(text, dictionaries, alphanumeric) {
+ const titles = Object.keys(dictionaries);
const {length, definitions} = await this.findTerms(text, dictionaries, alphanumeric);
- return {length, definitions: dictTermsGroup(definitions, dictionaries)};
+
+ const definitionsGrouped = dictTermsGroup(definitions, dictionaries);
+ for (const definition of definitionsGrouped) {
+ await this.buildTermFrequencies(definition, titles);
+ }
+
+ return {length, definitions: definitionsGrouped};
+ }
+
+ async findTermsSplit(text, dictionaries, alphanumeric) {
+ const titles = Object.keys(dictionaries);
+ const {length, definitions} = await this.findTerms(text, dictionaries, alphanumeric);
+
+ for (const definition of definitions) {
+ await this.buildTermFrequencies(definition, titles);
+ }
+
+ return {length, definitions};
}
async findTerms(text, dictionaries, alphanumeric) {
@@ -51,17 +69,18 @@ class Translator {
const cache = {};
const titles = Object.keys(dictionaries);
- let deinflections = await this.findTermsDeinflected(text, titles, cache);
+ let deinflections = await this.findTermDeinflections(text, titles, cache);
const textHiragana = jpKatakanaToHiragana(text);
if (text !== textHiragana) {
- deinflections = deinflections.concat(await this.findTermsDeinflected(textHiragana, titles, cache));
+ deinflections = deinflections.concat(await this.findTermDeinflections(textHiragana, titles, cache));
}
let definitions = [];
for (const deinflection of deinflections) {
for (const definition of deinflection.definitions) {
- const tags = definition.tags.map(tag => dictTagBuild(tag, definition.tagMeta));
+ const tags = await this.expandTags(definition.tags, definition.dictionary);
tags.push(dictTagBuildSource(definition.dictionary));
+
definitions.push({
source: deinflection.source,
reasons: deinflection.reasons,
@@ -87,7 +106,7 @@ class Translator {
return {length, definitions};
}
- async findTermsDeinflected(text, titles, cache) {
+ async findTermDeinflections(text, titles, cache) {
const definer = async term => {
if (cache.hasOwnProperty(term)) {
return cache[term];
@@ -117,11 +136,94 @@ class Translator {
}
for (const definition of definitions) {
- const tags = definition.tags.map(tag => dictTagBuild(tag, definition.tagMeta));
+ 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 = [];
+ const metas = await this.database.findKanjiMeta(definition.character, titles);
+ for (const meta of metas) {
+ if (meta.mode === 'freq') {
+ definition.frequencies.push({
+ character: meta.character,
+ frequency: meta.data,
+ dictionary: meta.dictionary
+ });
+ }
+ }
}
return definitions;
}
+
+ async buildTermFrequencies(definition, titles) {
+ let metas = await this.database.findTermMeta(definition.expression, titles);
+ if (metas.length === 0) {
+ metas = await this.database.findTermMeta(definition.reading, titles);
+ }
+
+ definition.frequencies = [];
+ for (const meta of metas) {
+ if (meta.mode === 'freq') {
+ definition.frequencies.push({
+ expression: meta.expression,
+ frequency: meta.data,
+ dictionary: meta.dictionary
+ });
+ }
+ }
+ }
+
+ async expandTags(names, title) {
+ const tags = [];
+ for (const name of names) {
+ const base = name.split(':')[0];
+ const meta = await this.database.findTagForTitle(base, title);
+
+ const tag = {name};
+ for (const prop in meta || {}) {
+ if (prop !== 'name') {
+ tag[prop] = meta[prop];
+ }
+ }
+
+ tags.push(dictTagSanitize(tag));
+ }
+
+ return tags;
+ }
+
+ async expandStats(items, title) {
+ const stats = {};
+ for (const name in items) {
+ const base = name.split(':')[0];
+ const meta = await this.database.findTagForTitle(base, title);
+ const group = stats[meta.category] = stats[meta.category] || [];
+
+ const stat = {name, value: items[name]};
+ for (const prop in meta || {}) {
+ if (prop !== 'name') {
+ stat[prop] = meta[prop];
+ }
+ }
+
+ group.push(dictTagSanitize(stat));
+ }
+
+ 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;
+ }
+ });
+ }
+
+ return stats;
+ }
}
diff --git a/ext/bg/js/util.js b/ext/bg/js/util.js
index a92fd0bc..f44582eb 100644
--- a/ext/bg/js/util.js
+++ b/ext/bg/js/util.js
@@ -42,8 +42,8 @@ function utilAnkiGetModelFieldNames(modelName) {
return utilBackend().anki.getModelFieldNames(modelName);
}
-function utilDatabaseGetDictionaries() {
- return utilBackend().translator.database.getDictionaries();
+function utilDatabaseGetTitles() {
+ return utilBackend().translator.database.getTitles();
}
function utilDatabasePurge() {
diff --git a/ext/bg/settings.html b/ext/bg/settings.html
index 0a5c205c..4315d74b 100644
--- a/ext/bg/settings.html
+++ b/ext/bg/settings.html
@@ -7,7 +7,7 @@
<link rel="stylesheet" type="text/css" href="/mixed/lib/bootstrap/css/bootstrap-theme.min.css">
<style>
#anki-spinner, #anki-general, #anki-error,
- #dict-spinner, #dict-error, #dict-warning, #dict-purge-progress, #dict-import-progress,
+ #dict-spinner, #dict-error, #dict-warning, #dict-purge, #dict-import-progress,
#debug, .options-advanced {
display: none;
}
@@ -96,6 +96,10 @@
<label><input type="checkbox" id="search-alphanumeric"> Search alphanumeric text</label>
</div>
+ <div class="checkbox">
+ <label><input type="checkbox" id="auto-hide-results"> Automatically hide results</label>
+ </div>
+
<div class="form-group options-advanced">
<label for="scan-delay">Scan delay (in milliseconds)</label>
<input type="number" min="1" id="scan-delay" class="form-control">
@@ -125,24 +129,16 @@
<p class="help-block">
Yomichan can import and use a variety of dictionary formats. Unneeded dictionaries can be disabled,
- or you can simply <a href="#" id="dict-purge">purge the database</a> to delete everything.
+ or you can simply <a href="#" id="dict-purge-link">purge the database</a> to delete everything.
</p>
<p class="help-block">
Please visit the <a href="https://foosoft.net/projects/yomichan" target="_blank">Yomichan</a> homepage to download free
dictionaries that you can use with this extension.
</p>
- <div id="dict-purge-progress" class="text-danger">Dictionary data is being purged, please be patient...</div>
-
- <div class="alert alert-warning" id="dict-warning">
- <strong>Warning:</strong>
- <span>no dictionaries found; please use the importer below to install packaged dictionaries</span>
- </div>
-
- <div class="alert alert-danger" id="dict-error">
- <strong>Error:</strong>
- <span></span>
- </div>
+ <div class="text-danger" id="dict-purge">Dictionary data is being purged, please be patient...</div>
+ <div class="alert alert-warning" id="dict-warning">No dictionaries have been installed</div>
+ <div class="alert alert-danger" id="dict-error"></div>
<div id="dict-groups"></div>
@@ -170,10 +166,7 @@
<a href="https://foosoft.net/projects/anki-connect/" target="_blank">AnkiConnect</a> plugin.
</p>
- <div class="alert alert-danger" id="anki-error">
- <strong>Error:</strong>
- <span></span>
- </div>
+ <div class="alert alert-danger" id="anki-error"></div>
<div class="checkbox">
<label><input type="checkbox" id="anki-enable"> Enable Anki integration</label>
diff --git a/ext/fg/js/document.js b/ext/fg/js/document.js
index b1e71777..a1806d77 100644
--- a/ext/fg/js/document.js
+++ b/ext/fg/js/document.js
@@ -86,7 +86,7 @@ function docRangeFromPoint(point) {
}
const range = document.caretRangeFromPoint(point.x, point.y);
- if (range) {
+ if (range && range.startContainer.nodeType === 3 && range.endContainer.nodeType === 3) {
return new TextSourceRange(range);
}
}
diff --git a/ext/fg/js/float.js b/ext/fg/js/float.js
index 22374f8b..ff50483d 100644
--- a/ext/fg/js/float.js
+++ b/ext/fg/js/float.js
@@ -27,7 +27,7 @@ class DisplayFloat extends Display {
if (window.yomichan_orphaned) {
this.onOrphaned();
} else {
- window.alert(`Error: ${error}`);
+ window.alert(`Error: ${error.toString ? error.toString() : error}`);
}
}
diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js
index 41c93f00..3a90b3ad 100644
--- a/ext/fg/js/frontend.js
+++ b/ext/fg/js/frontend.js
@@ -80,7 +80,6 @@ class Frontend {
const search = async () => {
try {
await this.searchAt({x: e.clientX, y: e.clientY});
- this.pendingLookup = false;
} catch (e) {
this.onError(e);
}
@@ -153,7 +152,7 @@ class Frontend {
}
onError(error) {
- window.alert(`Error: ${error}`);
+ window.alert(`Error: ${error.toString ? error.toString() : error}`);
}
popupTimerSet(callback) {
@@ -169,27 +168,17 @@ class Frontend {
}
async searchAt(point) {
- let textSource = null;
-
- try {
- if (this.pendingLookup) {
- return;
- }
-
- textSource = docRangeFromPoint(point);
- if (!textSource || !textSource.containsPoint(point)) {
- docImposterDestroy();
- return;
- }
-
- if (this.textSourceLast && this.textSourceLast.equals(textSource)) {
- return;
- }
+ if (this.pendingLookup || this.popup.containsPoint(point)) {
+ return;
+ }
- this.pendingLookup = true;
+ const textSource = docRangeFromPoint(point);
+ let hideResults = !textSource || !textSource.containsPoint(point);
- if (!await this.searchTerms(textSource)) {
- await this.searchKanji(textSource);
+ try {
+ if (!hideResults && (!this.textSourceLast || !this.textSourceLast.equals(textSource))) {
+ this.pendingLookup = true;
+ hideResults = !await this.searchTerms(textSource) && !await this.searchKanji(textSource);
}
} catch (e) {
if (window.yomichan_orphaned) {
@@ -200,7 +189,12 @@ class Frontend {
this.onError(e);
}
} finally {
- docImposterDestroy();
+ if (hideResults && this.options.scanning.autoHideResults) {
+ this.searchClear();
+ } else {
+ docImposterDestroy();
+ }
+
this.pendingLookup = false;
}
}
diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js
index 03958832..d1009fe9 100644
--- a/ext/fg/js/popup.js
+++ b/ext/fg/js/popup.js
@@ -100,6 +100,21 @@ class Popup {
return this.injected && this.container.style.visibility !== 'hidden';
}
+ containsPoint(point) {
+ if (!this.isVisible()) {
+ return false;
+ }
+
+ const rect = this.container.getBoundingClientRect();
+ const contained =
+ point.x >= rect.left &&
+ point.y >= rect.top &&
+ point.x < rect.right &&
+ point.y < rect.bottom;
+
+ return contained;
+ }
+
async termsShow(elementRect, definitions, options, context) {
await this.show(elementRect, options);
this.invokeApi('termsShow', {definitions, options, context});
diff --git a/ext/fg/js/source.js b/ext/fg/js/source.js
index 3b6ecb2a..664dbec7 100644
--- a/ext/fg/js/source.js
+++ b/ext/fg/js/source.js
@@ -82,7 +82,7 @@ class TextSourceRange {
}
equals(other) {
- return other.range && other.range.compareBoundaryPoints(Range.START_TO_START, this.range) === 0;
+ return other && other.range && other.range.compareBoundaryPoints(Range.START_TO_START, this.range) === 0;
}
static shouldEnter(node) {
@@ -239,6 +239,6 @@ class TextSourceElement {
}
equals(other) {
- return other.element === this.element && other.content === this.content;
+ return other && other.element === this.element && other.content === this.content;
}
}
diff --git a/ext/manifest.json b/ext/manifest.json
index 0da3283c..db338b12 100644
--- a/ext/manifest.json
+++ b/ext/manifest.json
@@ -1,7 +1,7 @@
{
"manifest_version": 2,
"name": "Yomichan",
- "version": "1.3.5",
+ "version": "1.4.0",
"description": "Japanese dictionary with Anki integration",
"icons": {"16": "mixed/img/icon16.png", "48": "mixed/img/icon48.png", "128": "mixed/img/icon128.png"},
diff --git a/ext/mixed/css/display.css b/ext/mixed/css/display.css
index 8b1819bd..cdc1be8c 100644
--- a/ext/mixed/css/display.css
+++ b/ext/mixed/css/display.css
@@ -84,6 +84,10 @@ hr {
background-color: #aa66cc;
}
+.tag-frequency {
+ background-color: #5cb85c;
+}
+
.actions .disabled {
pointer-events: none;
cursor: default;
@@ -144,3 +148,15 @@ hr {
padding: 0.01em;
vertical-align: top;
}
+
+.glyph-data {
+ margin-top: 10px;
+}
+
+.info-output {
+ width: 100%;
+}
+
+.info-output td {
+ text-align: right;
+}
diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js
index 47efd195..75ee339a 100644
--- a/ext/mixed/js/display.js
+++ b/ext/mixed/js/display.js
@@ -32,11 +32,11 @@ class Display {
}
onError(error) {
- throw 'override me';
+ throw 'Override me';
}
onSearchClear() {
- throw 'override me';
+ throw 'Override me';
}
onSourceTermView(e) {
@@ -350,7 +350,7 @@ class Display {
Display.adderButtonFind(index, mode).addClass('disabled');
Display.viewerButtonFind(index).removeClass('pending disabled').data('noteId', noteId);
} else {
- throw 'note could note be added';
+ throw 'Note could note be added';
}
} catch (e) {
this.onError(e);