Merge branch 'master' into firefox-amo

This commit is contained in:
Alex Yatskov 2017-09-24 11:01:40 -07:00
commit 00c20aed42
25 changed files with 729 additions and 332 deletions

View File

@ -55,13 +55,15 @@ primary language is not English, you may consider also importing the English ver
* [jmdict_swedish.zip](https://foosoft.net/projects/yomichan/dl/dict/jmdict_swedish.zip) * [jmdict_swedish.zip](https://foosoft.net/projects/yomichan/dl/dict/jmdict_swedish.zip)
* **[JMnedict](http://www.edrdg.org/enamdict/enamdict_doc.html)** (Japanese names) * **[JMnedict](http://www.edrdg.org/enamdict/enamdict_doc.html)** (Japanese names)
* [jmnedict.zip](https://foosoft.net/projects/yomichan/dl/dict/jmnedict.zip) * [jmnedict.zip](https://foosoft.net/projects/yomichan/dl/dict/jmnedict.zip)
* **[KANJIDIC](http://nihongo.monash.edu/kanjidic2/index.html)** (Japanese Kanji)
* [kanjidic_english.zip](https://foosoft.net/projects/yomichan/dl/dict/kanjidic_english.zip)
* [kanjidic_french.zip](https://foosoft.net/projects/yomichan/dl/dict/kanjidic_french.zip)
* [kanjidic_portuguese.zip](https://foosoft.net/projects/yomichan/dl/dict/kanjidic_portuguese.zip)
* [kanjidic_spanish.zip](https://foosoft.net/projects/yomichan/dl/dict/kanjidic_spanish.zip)
* **[KireiCake](https://kireicake.com/rikaicakes/)** (Japanese slang) * **[KireiCake](https://kireicake.com/rikaicakes/)** (Japanese slang)
* [kireicake.zip](https://foosoft.net/projects/yomichan/dl/dict/kireicake.zip) * [kireicake.zip](https://foosoft.net/projects/yomichan/dl/dict/kireicake.zip)
* **[KANJIDIC](http://nihongo.monash.edu/kanjidic2/index.html)** (Japanese Kanji)
* [kanjidic_english.zip](https://foosoft.net/projects/yomichan/dl/dict/kanjidic_english.zip) ([ver. 1.3.5 and older](https://foosoft.net/projects/yomichan/dl/dict/kanjidic_english_old.zip))
* [kanjidic_french.zip](https://foosoft.net/projects/yomichan/dl/dict/kanjidic_french.zip) ([ver. 1.3.5 and older](https://foosoft.net/projects/yomichan/dl/dict/kanjidic_french_old.zip))
* [kanjidic_portuguese.zip](https://foosoft.net/projects/yomichan/dl/dict/kanjidic_portuguese.zip) ([ver. 1.3.5 and older](https://foosoft.net/projects/yomichan/dl/dict/kanjidic_portuguese_old.zip))
* [kanjidic_spanish.zip](https://foosoft.net/projects/yomichan/dl/dict/kanjidic_spanish.zip) ([ver. 1.3.5 and older](https://foosoft.net/projects/yomichan/dl/dict/kanjidic_spanish_old.zip))
* **[Innocent Corpus](https://forum.koohii.com/post-168613.html#pid168613)** (Frequency list of terms and Kanji in 5000+ novels)
* [innocent_corpus.zip](https://foosoft.net/projects/yomichan/dl/dict/innocent_corpus.zip)
## Basic Usage ## ## Basic Usage ##
@ -240,6 +242,12 @@ exact versions used for distribution.
## Frequently Asked Questions ## ## Frequently Asked Questions ##
* **Why does the Kanji results page display "No data found" for several fields?**
You are using data from the KANJIDIC dictionary that was exported for an earlier version of Yomichan. It does not
contain the additional information which newer versions of Yomichan expect. Please purge your database and install
the latest version of the KANJIDIC to see additional information about characters.
* **Can I still create cards without HTML formatting? The option for it is gone!** * **Can I still create cards without HTML formatting? The option for it is gone!**
Developing Yomichan is a constant balance between including useful features and keeping complexity at a minimum. Developing Yomichan is a constant balance between including useful features and keeping complexity at a minimum.

View File

@ -62,7 +62,7 @@ class AnkiConnect {
if (this.remoteVersion < this.localVersion) { if (this.remoteVersion < this.localVersion) {
this.remoteVersion = await this.ankiInvoke('version'); this.remoteVersion = await this.ankiInvoke('version');
if (this.remoteVersion < this.localVersion) { if (this.remoteVersion < this.localVersion) {
throw 'extension and plugin versions incompatible'; throw 'Extension and plugin versions incompatible';
} }
} }
} }

View File

@ -31,7 +31,7 @@ async function apiTermsFind(text) {
const searcher = options.general.groupResults ? const searcher = options.general.groupResults ?
translator.findTermsGrouped.bind(translator) : translator.findTermsGrouped.bind(translator) :
translator.findTerms.bind(translator); translator.findTermsSplit.bind(translator);
const {definitions, length} = await searcher( const {definitions, length} = await searcher(
text, text,

View File

@ -57,7 +57,7 @@ async function audioBuildUrl(definition, mode, cache={}) {
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://www.japanesepod101.com/learningcenter/reference/dictionary_post'); xhr.open('POST', 'https://www.japanesepod101.com/learningcenter/reference/dictionary_post');
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); 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', () => { xhr.addEventListener('load', () => {
cache[definition.expression] = xhr.responseText; cache[definition.expression] = xhr.responseText;
resolve(xhr.responseText); resolve(xhr.responseText);
@ -87,7 +87,7 @@ async function audioBuildUrl(definition, mode, cache={}) {
} else { } else {
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
xhr.open('GET', `http://jisho.org/search/${definition.expression}`); 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', () => { xhr.addEventListener('load', () => {
cache[definition.expression] = xhr.responseText; cache[definition.expression] = xhr.responseText;
resolve(xhr.responseText); resolve(xhr.responseText);

View File

@ -71,7 +71,7 @@ class Backend {
return promise.then(result => { return promise.then(result => {
callback({result}); callback({result});
}).catch(error => { }).catch(error => {
callback({error}); callback({error: error.toString ? error.toString() : error});
}); });
}; };

View File

@ -20,44 +20,33 @@
class Database { class Database {
constructor() { constructor() {
this.db = null; this.db = null;
this.version = 2;
this.tagCache = {}; 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() { async prepare() {
if (this.db) { if (this.db) {
throw 'database already initialized'; throw 'Database already initialized';
} }
await this.sanitize();
this.db = new Dexie('dict'); this.db = new Dexie('dict');
this.db.version(this.version).stores({ this.db.version(2).stores({
terms: '++id,dictionary,expression,reading', terms: '++id,dictionary,expression,reading',
kanji: '++,dictionary,character', kanji: '++,dictionary,character',
tagMeta: '++,dictionary', tagMeta: '++,dictionary',
dictionaries: '++,title,version' dictionaries: '++,title,version'
}); });
this.db.version(3).stores({
termMeta: '++,dictionary,expression',
kanjiMeta: '++,dictionary,character',
tagMeta: '++,dictionary,name'
});
await this.db.open(); await this.db.open();
} }
async purge() { async purge() {
if (!this.db) { if (!this.db) {
throw 'database not initialized'; throw 'Database not initialized';
} }
this.db.close(); this.db.close();
@ -70,7 +59,7 @@ class Database {
async findTerms(term, titles) { async findTerms(term, titles) {
if (!this.db) { if (!this.db) {
throw 'database not initialized'; throw 'Database not initialized';
} }
const results = []; const results = [];
@ -89,17 +78,31 @@ class Database {
} }
}); });
await this.cacheTagMeta(titles); return results;
for (const result of results) {
result.tagMeta = this.tagCache[result.dictionary] || {};
} }
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; return results;
} }
async findKanji(kanji, titles) { async findKanji(kanji, titles) {
if (!this.db) { if (!this.db) {
return Promise.reject('database not initialized'); throw 'Database not initialized';
} }
const results = []; const results = [];
@ -111,83 +114,88 @@ class Database {
kunyomi: dictFieldSplit(row.kunyomi), kunyomi: dictFieldSplit(row.kunyomi),
tags: dictFieldSplit(row.tags), tags: dictFieldSplit(row.tags),
glossary: row.meanings, glossary: row.meanings,
stats: row.stats,
dictionary: row.dictionary dictionary: row.dictionary
}); });
} }
}); });
await this.cacheTagMeta(titles); return results;
for (const result of results) {
result.tagMeta = this.tagCache[result.dictionary] || {};
} }
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; return results;
} }
async cacheTagMeta(titles) { async findTagForTitle(name, title) {
if (!this.db) { if (!this.db) {
throw 'database not initialized'; throw 'Database not initialized';
} }
for (const title of titles) { this.tagCache[title] = this.tagCache[title] || {};
if (!this.tagCache[title]) {
const tagMeta = {}; let result = this.tagCache[title][name];
await this.db.tagMeta.where('dictionary').equals(title).each(row => { if (!result) {
tagMeta[row.name] = {category: row.category, notes: row.notes, order: row.order}; await this.db.tagMeta.where('name').equals(name).each(row => {
if (title === row.dictionary) {
result = row;
}
}); });
this.tagCache[title] = tagMeta; this.tagCache[title][name] = result;
}
}
} }
async getDictionaries() { return result;
}
async getTitles() {
if (this.db) { if (this.db) {
return this.db.dictionaries.toArray(); return this.db.dictionaries.toArray();
} else { } else {
throw 'database not initialized'; throw 'Database not initialized';
} }
} }
async importDictionary(archive, callback) { async importDictionary(archive, callback) {
if (!this.db) { if (!this.db) {
return Promise.reject('database not initialized'); throw 'Database not initialized';
} }
let summary = null; const indexDataLoaded = async summary => {
const indexLoaded = async (title, version, revision, tagMeta, hasTerms, hasKanji) => { if (summary.version > 2) {
summary = {title, version, revision, hasTerms, hasKanji}; 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) { 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
});
rows.push(row);
}
await this.db.tagMeta.bulkAdd(rows);
}; };
const termsLoaded = async (title, entries, total, current) => { const termDataLoaded = async (summary, entries, total, current) => {
if (callback) { if (callback) {
callback(total, current); callback(total, current);
} }
const rows = []; const rows = [];
if (summary.version === 1) {
for (const [expression, reading, tags, rules, score, ...glossary] of entries) { for (const [expression, reading, tags, rules, score, ...glossary] of entries) {
rows.push({ rows.push({
expression, expression,
@ -196,19 +204,51 @@ class Database {
rules, rules,
score, score,
glossary, glossary,
dictionary: title 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.terms.bulkAdd(rows); await this.db.terms.bulkAdd(rows);
}; };
const kanjiLoaded = async (title, entries, total, current) => { const termMetaDataLoaded = async (summary, entries, total, current) => {
if (callback) { if (callback) {
callback(total, current); callback(total, current);
} }
const rows = []; const rows = [];
for (const [expression, mode, data] of entries) {
rows.push({
expression,
mode,
data,
dictionary: summary.title
});
}
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) { for (const [character, onyomi, kunyomi, tags, ...meanings] of entries) {
rows.push({ rows.push({
character, character,
@ -216,60 +256,162 @@ class Database {
kunyomi, kunyomi,
tags, tags,
meanings, meanings,
dictionary: title 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); await this.db.kanji.bulkAdd(rows);
}; };
await Database.importDictionaryZip(archive, indexLoaded, termsLoaded, kanjiLoaded); const kanjiMetaDataLoaded = async (summary, entries, total, current) => {
return summary; if (callback) {
callback(total, current);
} }
static async importDictionaryZip(archive, indexLoaded, termsLoaded, kanjiLoaded) { const rows = [];
const files = (await JSZip.loadAsync(archive)).files; for (const [character, mode, data] of entries) {
rows.push({
character,
mode,
data,
dictionary: summary.title
});
}
const indexFile = files['index.json']; await this.db.kanjiMeta.bulkAdd(rows);
};
const tagDataLoaded = async (summary, entries, total, current) => {
if (callback) {
callback(total, current);
}
const rows = [];
for (const [name, category, order, notes] of entries) {
const row = dictTagSanitize({
name,
category,
order,
notes,
dictionary: summary.title
});
rows.push(row);
}
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) { if (!indexFile) {
throw 'no dictionary index found in archive'; throw 'No dictionary index found in archive';
} }
const index = JSON.parse(await indexFile.async('string')); const index = JSON.parse(await indexFile.async('string'));
if (!index.title || !index.version || !index.revision) { if (!index.title || !index.revision) {
throw 'unrecognized dictionary format'; throw 'Unrecognized dictionary format';
} }
await indexLoaded( const summary = {
index.title, title: index.title,
index.version, revision: index.revision,
index.revision, version: index.format || index.version
index.tagMeta || {}, };
index.termBanks > 0,
index.kanjiBanks > 0
);
const banksTotal = index.termBanks + index.kanjiBanks; if (indexDataLoaded) {
let banksLoaded = 0; await indexDataLoaded(summary);
}
for (let i = 1; i <= index.termBanks; ++i) { const buildTermBankName = index => `term_bank_${index + 1}.json`;
const bankFile = files[`term_bank_${i}.json`]; const buildTermMetaBankName = index => `term_meta_bank_${index + 1}.json`;
if (bankFile) { 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`;
const countBanks = namer => {
let count = 0;
while (zip.files[namer(count)]) {
++count;
}
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')); const bank = JSON.parse(await bankFile.async('string'));
await termsLoaded(index.title, bank, banksTotal, banksLoaded++); await callback(summary, bank, bankTotalCount, bankLoadedCount++);
} else {
throw 'missing term bank file';
} }
} }
};
for (let i = 1; i <= index.kanjiBanks; ++i) { await loadBank(summary, buildTermBankName, termBankCount, termDataLoaded);
const bankFile = files[`kanji_bank_${i}.json`]; await loadBank(summary, buildTermMetaBankName, termMetaBankCount, termMetaDataLoaded);
if (bankFile) { await loadBank(summary, buildKanjiBankName, kanjiBankCount, kanjiDataLoaded);
const bank = JSON.parse(await bankFile.async('string')); await loadBank(summary, buildKanjiMetaBankName, kanjiMetaBankCount, kanjiMetaDataLoaded);
await kanjiLoaded(index.title, bank, banksTotal, banksLoaded++); await loadBank(summary, buildTagBankName, tagBankCount, tagDataLoaded);
} else {
throw 'missing kanji bank file'; return summary;
}
}
} }
} }

View File

@ -55,14 +55,6 @@ function dictRowsSort(rows, options) {
function dictTermsSort(definitions, dictionaries=null) { function dictTermsSort(definitions, dictionaries=null) {
return definitions.sort((v1, v2) => { 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) { if (dictionaries !== null) {
const p1 = (dictionaries[v1.dictionary] || {}).priority || 0; const p1 = (dictionaries[v1.dictionary] || {}).priority || 0;
const p2 = (dictionaries[v2.dictionary] || {}).priority || 0; const p2 = (dictionaries[v2.dictionary] || {}).priority || 0;
@ -73,11 +65,11 @@ function dictTermsSort(definitions, dictionaries=null) {
} }
} }
const s1 = v1.score; const sl1 = v1.source.length;
const s2 = v2.score; const sl2 = v2.source.length;
if (s1 > s2) { if (sl1 > sl2) {
return -1; return -1;
} else if (s1 < s2) { } else if (sl1 < sl2) {
return 1; return 1;
} }
@ -89,6 +81,14 @@ function dictTermsSort(definitions, dictionaries=null) {
return 1; 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); return v2.expression.localeCompare(v1.expression);
}); });
} }
@ -148,16 +148,6 @@ function dictTagBuildSource(name) {
return dictTagSanitize({name, category: 'dictionary', order: 100}); 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) { function dictTagSanitize(tag) {
tag.name = tag.name || 'untitled'; tag.name = tag.name || 'untitled';
tag.category = tag.category || 'default'; tag.category = tag.category || 'default';

View File

@ -146,7 +146,8 @@ function optionsSetDefaults(options) {
middleMouse: true, middleMouse: true,
selectText: true, selectText: true,
alphanumeric: true, alphanumeric: true,
delay: 15, autoHideResults: false,
delay: 20,
length: 10, length: 10,
modifier: 'shift' modifier: 'shift'
}, },

View File

@ -22,7 +22,7 @@ function requestJson(url, action, params) {
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
xhr.overrideMimeType('application/json'); xhr.overrideMimeType('application/json');
xhr.addEventListener('load', () => resolve(xhr.responseText)); 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); xhr.open(action, url);
if (params) { if (params) {
xhr.send(JSON.stringify(params)); xhr.send(JSON.stringify(params));
@ -34,7 +34,7 @@ function requestJson(url, action, params) {
return JSON.parse(responseText); return JSON.parse(responseText);
} }
catch (e) { catch (e) {
return Promise.reject('invalid JSON response'); return Promise.reject('Invalid response');
} }
}); });
} }

View File

@ -29,7 +29,7 @@ class DisplaySearch extends Display {
} }
onError(error) { onError(error) {
window.alert(`Error: ${error}`); window.alert(`Error: ${error.toString ? error.toString() : error}`);
} }
onSearchClear() { onSearchClear() {

View File

@ -35,6 +35,7 @@ async function formRead() {
optionsNew.scanning.middleMouse = $('#middle-mouse-button-scan').prop('checked'); optionsNew.scanning.middleMouse = $('#middle-mouse-button-scan').prop('checked');
optionsNew.scanning.selectText = $('#select-matched-text').prop('checked'); optionsNew.scanning.selectText = $('#select-matched-text').prop('checked');
optionsNew.scanning.alphanumeric = $('#search-alphanumeric').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.delay = parseInt($('#scan-delay').val(), 10);
optionsNew.scanning.length = parseInt($('#scan-length').val(), 10); optionsNew.scanning.length = parseInt($('#scan-length').val(), 10);
optionsNew.scanning.modifier = $('#scan-modifier-key').val(); optionsNew.scanning.modifier = $('#scan-modifier-key').val();
@ -82,7 +83,9 @@ function formUpdateVisibility(options) {
const debug = $('#debug'); const debug = $('#debug');
if (options.general.debugInfo) { 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.html(handlebarsEscape(text));
debug.show(); debug.show();
} else { } else {
@ -134,11 +137,12 @@ async function onReady() {
$('#middle-mouse-button-scan').prop('checked', options.scanning.middleMouse); $('#middle-mouse-button-scan').prop('checked', options.scanning.middleMouse);
$('#select-matched-text').prop('checked', options.scanning.selectText); $('#select-matched-text').prop('checked', options.scanning.selectText);
$('#search-alphanumeric').prop('checked', options.scanning.alphanumeric); $('#search-alphanumeric').prop('checked', options.scanning.alphanumeric);
$('#auto-hide-results').prop('checked', options.scanning.autoHideResults);
$('#scan-delay').val(options.scanning.delay); $('#scan-delay').val(options.scanning.delay);
$('#scan-length').val(options.scanning.length); $('#scan-length').val(options.scanning.length);
$('#scan-modifier-key').val(options.scanning.modifier); $('#scan-modifier-key').val(options.scanning.modifier);
$('#dict-purge').click(utilAsync(onDictionaryPurge)); $('#dict-purge-link').click(utilAsync(onDictionaryPurge));
$('#dict-file').change(utilAsync(onDictionaryImport)); $('#dict-file').change(utilAsync(onDictionaryImport));
$('#anki-enable').prop('checked', options.anki.enable); $('#anki-enable').prop('checked', options.anki.enable);
@ -175,7 +179,33 @@ $(document).ready(utilAsync(onReady));
function dictionaryErrorShow(error) { function dictionaryErrorShow(error) {
const dialog = $('#dict-error'); const dialog = $('#dict-error');
if (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 { } else {
dialog.hide(); dialog.hide();
} }
@ -211,7 +241,7 @@ async function dictionaryGroupsPopulate(options) {
const dictGroups = $('#dict-groups').empty(); const dictGroups = $('#dict-groups').empty();
const dictWarning = $('#dict-warning').hide(); const dictWarning = $('#dict-warning').hide();
const dictRows = await utilDatabaseGetDictionaries(); const dictRows = await utilDatabaseGetTitles();
if (dictRows.length === 0) { if (dictRows.length === 0) {
dictWarning.show(); dictWarning.show();
} }
@ -241,7 +271,7 @@ async function onDictionaryPurge(e) {
e.preventDefault(); e.preventDefault();
const dictControls = $('#dict-importer, #dict-groups').hide(); const dictControls = $('#dict-importer, #dict-groups').hide();
const dictProgress = $('#dict-purge-progress').show(); const dictProgress = $('#dict-purge').show();
try { try {
dictionaryErrorShow(); dictionaryErrorShow();
@ -310,7 +340,7 @@ function ankiSpinnerShow(show) {
function ankiErrorShow(error) { function ankiErrorShow(error) {
const dialog = $('#anki-error'); const dialog = $('#anki-error');
if (error) { if (error) {
dialog.show().find('span').text(error); dialog.show().text(error);
} }
else { else {
dialog.hide(); dialog.hide();

View File

@ -22,38 +22,87 @@ templates['dictionary.html'] = template({"1":function(container,depth0,helpers,p
+ "\" class=\"form-control dict-priority\">\n </div>\n</div>\n"; + "\" class=\"form-control dict-priority\">\n </div>\n</div>\n";
},"useData":true}); },"useData":true});
templates['kanji.html'] = template({"1":function(container,depth0,helpers,partials,data) { 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 || {}); var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {});
return "<div class=\"entry\" data-type=\"kanji\">\n <div class=\"actions\">\n" 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.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(4, 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\">" + " <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))) + 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" + "</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 : "") + ((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"; + "</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"; 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"; 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; var stack1;
return " " return " <div>\n"
+ container.escapeExpression(container.lambda(depth0, depth0)) + ((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 : "")
+ ((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 : "") + " </div>\n";
+ "\n"; },"16":function(container,depth0,helpers,partials,data) {
},"7":function(container,depth0,helpers,partials,data) { var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
return ", ";
},"9":function(container,depth0,helpers,partials,data) { 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; 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-"
@ -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))) + 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"; + "</span>\n";
},"11":function(container,depth0,helpers,partials,data) { },"21":function(container,depth0,helpers,partials,data) {
var stack1; var stack1;
return " <ol>\n" return " <ol>"
+ ((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 : "") + ((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"; + "</ol>\n";
},"12":function(container,depth0,helpers,partials,data) { },"22":function(container,depth0,helpers,partials,data) {
var stack1, helper, options, buffer = return "<li><span class=\"glossary-item\">"
" <li><span class=\"glossary-item\">"; + container.escapeExpression(container.lambda(depth0, depth0))
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)); + "</span></li>";
if (!helpers.multiLine) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)} },"24":function(container,depth0,helpers,partials,data) {
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) {
var stack1; var stack1;
return container.escapeExpression(container.lambda(((stack1 = (depth0 != null ? depth0.glossary : depth0)) != null ? stack1["0"] : stack1), depth0)); return " <span class=\"glossary-item\">"
},"18":function(container,depth0,helpers,partials,data) { + 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 = var stack1, helper, options, buffer =
" <pre>"; " <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 (!helpers.dumpObject) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
if (stack1 != null) { buffer += stack1; } if (stack1 != null) { buffer += stack1; }
return buffer + "</pre>\n"; return buffer + "</pre>\n";
},"19":function(container,depth0,helpers,partials,data) { },"32":function(container,depth0,helpers,partials,data) {
var stack1; var stack1;
return ((stack1 = container.lambda(depth0, depth0)) != null ? 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; 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 : ""); 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 : "");
},"22":function(container,depth0,helpers,partials,data,blockParams,depths) { },"35":function(container,depth0,helpers,partials,data,blockParams,depths) {
var stack1; 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" + "\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 : ""); + ((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>"; return "<hr>";
},"25":function(container,depth0,helpers,partials,data) { },"38":function(container,depth0,helpers,partials,data) {
return "<p class=\"note\">No results found.</p>\n"; return "<p class=\"note\">No results found</p>\n";
},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data,blockParams,depths) { },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data,blockParams,depths) {
var stack1; var stack1;
return "\n" return "\n\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 : ""); + ((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) { },"main_d": function(fn, props, container, depth0, data, blockParams, depths) {
var decorators = container.decorators; 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; return fn;
} }
@ -203,10 +259,12 @@ templates['terms.html'] = template({"1":function(container,depth0,helpers,partia
if (stack1 != null) { buffer += stack1; } if (stack1 != null) { buffer += stack1; }
return buffer + "</div>\n\n" 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 : "") + ((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" + "\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" + " </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"; + "</div>\n";
},"13":function(container,depth0,helpers,partials,data) { },"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"; 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) { },"24":function(container,depth0,helpers,partials,data) {
var stack1; 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) { },"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; var stack1;
return " <ol>\n" 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"; + " </ol>\n";
},"26":function(container,depth0,helpers,partials,data) { },"29":function(container,depth0,helpers,partials,data) {
var stack1; var stack1;
return " <li>" return " <li>"
+ ((stack1 = container.invokePartial(partials.definition,depth0,{"name":"definition","data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "") + ((stack1 = container.invokePartial(partials.definition,depth0,{"name":"definition","data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
+ "</li>\n"; + "</li>\n";
},"28":function(container,depth0,helpers,partials,data) { },"31":function(container,depth0,helpers,partials,data) {
var stack1; 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 : ""); 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; var stack1;
return ((stack1 = container.invokePartial(partials.definition,depth0,{"name":"definition","data":data,"indent":" ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? 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 = var stack1, helper, options, buffer =
" <pre>"; " <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)); 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 (!helpers.dumpObject) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
if (stack1 != null) { buffer += stack1; } if (stack1 != null) { buffer += stack1; }
return buffer + "</pre>\n"; return buffer + "</pre>\n";
},"34":function(container,depth0,helpers,partials,data,blockParams,depths) { },"37":function(container,depth0,helpers,partials,data,blockParams,depths) {
var stack1; 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 : ""); 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 : "");
},"35":function(container,depth0,helpers,partials,data,blockParams,depths) { },"38":function(container,depth0,helpers,partials,data,blockParams,depths) {
var stack1; 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" + "\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 : ""); + ((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>"; 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"; return "<p class=\"note\">No results found.</p>\n";
},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data,blockParams,depths) { },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data,blockParams,depths) {
var stack1; var stack1;
return "\n\n" 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) { },"main_d": function(fn, props, container, depth0, data, blockParams, depths) {
var decorators = container.decorators; var decorators = container.decorators;

View File

@ -37,8 +37,26 @@ class Translator {
} }
async findTermsGrouped(text, dictionaries, alphanumeric) { async findTermsGrouped(text, dictionaries, alphanumeric) {
const titles = Object.keys(dictionaries);
const {length, definitions} = await this.findTerms(text, dictionaries, alphanumeric); 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) { async findTerms(text, dictionaries, alphanumeric) {
@ -51,17 +69,18 @@ class Translator {
const cache = {}; const cache = {};
const titles = Object.keys(dictionaries); 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); const textHiragana = jpKatakanaToHiragana(text);
if (text !== textHiragana) { if (text !== textHiragana) {
deinflections = deinflections.concat(await this.findTermsDeinflected(textHiragana, titles, cache)); deinflections = deinflections.concat(await this.findTermDeinflections(textHiragana, titles, cache));
} }
let definitions = []; let definitions = [];
for (const deinflection of deinflections) { for (const deinflection of deinflections) {
for (const definition of deinflection.definitions) { 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)); tags.push(dictTagBuildSource(definition.dictionary));
definitions.push({ definitions.push({
source: deinflection.source, source: deinflection.source,
reasons: deinflection.reasons, reasons: deinflection.reasons,
@ -87,7 +106,7 @@ class Translator {
return {length, definitions}; return {length, definitions};
} }
async findTermsDeinflected(text, titles, cache) { async findTermDeinflections(text, titles, cache) {
const definer = async term => { const definer = async term => {
if (cache.hasOwnProperty(term)) { if (cache.hasOwnProperty(term)) {
return cache[term]; return cache[term];
@ -117,11 +136,88 @@ class Translator {
} }
for (const definition of definitions) { 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)); tags.push(dictTagBuildSource(definition.dictionary));
definition.tags = dictTagsSort(tags); 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
});
}
}
} }
return definitions; return definitions;
} }
async buildTermFrequencies(definition, titles) {
definition.frequencies = [];
for (const meta of await this.database.findTermMeta(definition.expression, titles)) {
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;
}
} }

View File

@ -42,8 +42,8 @@ function utilAnkiGetModelFieldNames(modelName) {
return utilBackend().anki.getModelFieldNames(modelName); return utilBackend().anki.getModelFieldNames(modelName);
} }
function utilDatabaseGetDictionaries() { function utilDatabaseGetTitles() {
return utilBackend().translator.database.getDictionaries(); return utilBackend().translator.database.getTitles();
} }
function utilDatabasePurge() { function utilDatabasePurge() {

View File

@ -7,7 +7,7 @@
<link rel="stylesheet" type="text/css" href="/mixed/lib/bootstrap/css/bootstrap-theme.min.css"> <link rel="stylesheet" type="text/css" href="/mixed/lib/bootstrap/css/bootstrap-theme.min.css">
<style> <style>
#anki-spinner, #anki-general, #anki-error, #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 { #debug, .options-advanced {
display: none; display: none;
} }
@ -96,6 +96,10 @@
<label><input type="checkbox" id="search-alphanumeric"> Search alphanumeric text</label> <label><input type="checkbox" id="search-alphanumeric"> Search alphanumeric text</label>
</div> </div>
<div class="checkbox">
<label><input type="checkbox" id="auto-hide-results"> Automatically hide results</label>
</div>
<div class="form-group options-advanced"> <div class="form-group options-advanced">
<label for="scan-delay">Scan delay (in milliseconds)</label> <label for="scan-delay">Scan delay (in milliseconds)</label>
<input type="number" min="1" id="scan-delay" class="form-control"> <input type="number" min="1" id="scan-delay" class="form-control">
@ -125,24 +129,16 @@
<p class="help-block"> <p class="help-block">
Yomichan can import and use a variety of dictionary formats. Unneeded dictionaries can be disabled, 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>
<p class="help-block"> <p class="help-block">
Please visit the <a href="https://foosoft.net/projects/yomichan" target="_blank">Yomichan</a> homepage to download free 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. dictionaries that you can use with this extension.
</p> </p>
<div id="dict-purge-progress" class="text-danger">Dictionary data is being purged, please be patient...</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-warning" id="dict-warning"> <div class="alert alert-danger" id="dict-error"></div>
<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 id="dict-groups"></div> <div id="dict-groups"></div>
@ -170,10 +166,7 @@
<a href="https://foosoft.net/projects/anki-connect/" target="_blank">AnkiConnect</a> plugin. <a href="https://foosoft.net/projects/anki-connect/" target="_blank">AnkiConnect</a> plugin.
</p> </p>
<div class="alert alert-danger" id="anki-error"> <div class="alert alert-danger" id="anki-error"></div>
<strong>Error:</strong>
<span></span>
</div>
<div class="checkbox"> <div class="checkbox">
<label><input type="checkbox" id="anki-enable"> Enable Anki integration</label> <label><input type="checkbox" id="anki-enable"> Enable Anki integration</label>

View File

@ -86,7 +86,7 @@ function docRangeFromPoint(point) {
} }
const range = document.caretRangeFromPoint(point.x, point.y); const range = document.caretRangeFromPoint(point.x, point.y);
if (range) { if (range && range.startContainer.nodeType === 3 && range.endContainer.nodeType === 3) {
return new TextSourceRange(range); return new TextSourceRange(range);
} }
} }

View File

@ -27,7 +27,7 @@ class DisplayFloat extends Display {
if (window.yomichan_orphaned) { if (window.yomichan_orphaned) {
this.onOrphaned(); this.onOrphaned();
} else { } else {
window.alert(`Error: ${error}`); window.alert(`Error: ${error.toString ? error.toString() : error}`);
} }
} }

View File

@ -80,7 +80,6 @@ class Frontend {
const search = async () => { const search = async () => {
try { try {
await this.searchAt({x: e.clientX, y: e.clientY}); await this.searchAt({x: e.clientX, y: e.clientY});
this.pendingLookup = false;
} catch (e) { } catch (e) {
this.onError(e); this.onError(e);
} }
@ -153,7 +152,7 @@ class Frontend {
} }
onError(error) { onError(error) {
window.alert(`Error: ${error}`); window.alert(`Error: ${error.toString ? error.toString() : error}`);
} }
popupTimerSet(callback) { popupTimerSet(callback) {
@ -169,27 +168,17 @@ class Frontend {
} }
async searchAt(point) { async searchAt(point) {
let textSource = null; if (this.pendingLookup || this.popup.containsPoint(point)) {
return;
}
const textSource = docRangeFromPoint(point);
let hideResults = !textSource || !textSource.containsPoint(point);
try { try {
if (this.pendingLookup) { if (!hideResults && (!this.textSourceLast || !this.textSourceLast.equals(textSource))) {
return;
}
textSource = docRangeFromPoint(point);
if (!textSource || !textSource.containsPoint(point)) {
docImposterDestroy();
return;
}
if (this.textSourceLast && this.textSourceLast.equals(textSource)) {
return;
}
this.pendingLookup = true; this.pendingLookup = true;
hideResults = !await this.searchTerms(textSource) && !await this.searchKanji(textSource);
if (!await this.searchTerms(textSource)) {
await this.searchKanji(textSource);
} }
} catch (e) { } catch (e) {
if (window.yomichan_orphaned) { if (window.yomichan_orphaned) {
@ -200,7 +189,12 @@ class Frontend {
this.onError(e); this.onError(e);
} }
} finally { } finally {
if (hideResults && this.options.scanning.autoHideResults) {
this.searchClear();
} else {
docImposterDestroy(); docImposterDestroy();
}
this.pendingLookup = false; this.pendingLookup = false;
} }
} }

View File

@ -100,6 +100,21 @@ class Popup {
return this.injected && this.container.style.visibility !== 'hidden'; 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) { async termsShow(elementRect, definitions, options, context) {
await this.show(elementRect, options); await this.show(elementRect, options);
this.invokeApi('termsShow', {definitions, options, context}); this.invokeApi('termsShow', {definitions, options, context});

View File

@ -82,7 +82,7 @@ class TextSourceRange {
} }
equals(other) { 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) { static shouldEnter(node) {
@ -239,6 +239,6 @@ class TextSourceElement {
} }
equals(other) { equals(other) {
return other.element === this.element && other.content === this.content; return other && other.element === this.element && other.content === this.content;
} }
} }

View File

@ -1,7 +1,7 @@
{ {
"manifest_version": 2, "manifest_version": 2,
"name": "Yomichan", "name": "Yomichan",
"version": "1.3.5", "version": "1.4.0",
"description": "Japanese dictionary with Anki integration", "description": "Japanese dictionary with Anki integration",
"icons": {"16": "mixed/img/icon16.png", "48": "mixed/img/icon48.png", "128": "mixed/img/icon128.png"}, "icons": {"16": "mixed/img/icon16.png", "48": "mixed/img/icon48.png", "128": "mixed/img/icon128.png"},

View File

@ -84,6 +84,10 @@ hr {
background-color: #aa66cc; background-color: #aa66cc;
} }
.tag-frequency {
background-color: #5cb85c;
}
.actions .disabled { .actions .disabled {
pointer-events: none; pointer-events: none;
cursor: default; cursor: default;
@ -144,3 +148,15 @@ hr {
padding: 0.01em; padding: 0.01em;
vertical-align: top; vertical-align: top;
} }
.glyph-data {
margin-top: 10px;
}
.info-output {
width: 100%;
}
.info-output td {
text-align: right;
}

View File

@ -32,11 +32,11 @@ class Display {
} }
onError(error) { onError(error) {
throw 'override me'; throw 'Override me';
} }
onSearchClear() { onSearchClear() {
throw 'override me'; throw 'Override me';
} }
onSourceTermView(e) { onSourceTermView(e) {
@ -350,7 +350,7 @@ class Display {
Display.adderButtonFind(index, mode).addClass('disabled'); Display.adderButtonFind(index, mode).addClass('disabled');
Display.viewerButtonFind(index).removeClass('pending disabled').data('noteId', noteId); Display.viewerButtonFind(index).removeClass('pending disabled').data('noteId', noteId);
} else { } else {
throw 'note could note be added'; throw 'Note could note be added';
} }
} catch (e) { } catch (e) {
this.onError(e); this.onError(e);

View File

@ -1,3 +1,18 @@
{{#*inline "table"}}
{{#if data}}
<table class="info-output">
{{#each data}}
<tr>
<th>{{#if notes}}{{notes}}{{else}}{{name}}{{/if}}</th>
<td>{{value}}</td>
</tr>
{{/each}}
</table>
{{else}}
No data found
{{/if}}
{{/inline}}
{{#*inline "kanji"}} {{#*inline "kanji"}}
<div class="entry" data-type="kanji"> <div class="entry" data-type="kanji">
<div class="actions"> <div class="actions">
@ -13,44 +28,61 @@
<div class="glyph">{{character}}</div> <div class="glyph">{{character}}</div>
<div class="reading"> {{#if frequencies}}
<table> <div>
<tr> {{#each frequencies}}
<th>Kunyomi:</th> <span class="label label-default tag-frequency">{{dictionary}}:{{frequency}}</span>
<td>
{{#each kunyomi}}
{{.}}{{#unless @last}}, {{/unless}}
{{/each}} {{/each}}
</td>
</tr>
<tr>
<th>Onyomi:</th>
<td>
{{#each onyomi}}
{{.}}{{#unless @last}}, {{/unless}}
{{/each}}
</td>
</tr>
</table>
</div> </div>
{{/if}}
{{#if tags}}
<div> <div>
{{#each tags}} {{#each tags}}
<span class="label label-default tag-{{category}}" title="{{notes}}">{{name}}</span> <span class="label label-default tag-{{category}}" title="{{notes}}">{{name}}</span>
{{/each}} {{/each}}
</div> </div>
<div class="glossary">
{{#if glossary.[1]}}
<ol>
{{#each glossary}}
<li><span class="glossary-item">{{#multiLine}}{{.}}{{/multiLine}}</span></li>
{{/each}}
</ol>
{{else}}
<div class="glossary-item">{{#multiLine}}{{glossary.[0]}}{{/multiLine}}</div>
{{/if}} {{/if}}
</div>
<table class="table table-condensed glyph-data">
<tr>
<th>Glossary</th>
<th>Readings</th>
<th>Statistics</th>
</tr>
<tr>
<td class="glossary">
{{#if glossary.[1]}}
<ol>{{#each glossary}}<li><span class="glossary-item">{{.}}</span></li>{{/each}}</ol>
{{else}}
<span class="glossary-item">{{glossary.[0]}}</span>
{{/if}}
</td>
<td class="reading">
{{#if onyomi}}<dl>{{#each onyomi}}<dd>{{.}}</dd>{{/each}}</dl>{{/if}}
{{#if kunyomi}}<dl>{{#each kunyomi}}<dd>{{.}}</dd>{{/each}}</dl>{{/if}}
</td>
<td>{{> table data=stats.misc}}</td>
</tr>
<tr>
<th colspan="3">Classifications</th>
</tr>
<tr>
<td colspan="3">{{> table data=stats.class}}</td>
</tr>
<tr>
<th colspan="3">Codepoints</th>
</tr>
<tr>
<td colspan="3">{{> table data=stats.code}}</td>
</tr>
<tr>
<th colspan="3">Dictionary Indices</th>
</tr>
<tr>
<td colspan="3">{{> table data=stats.index}}</td>
</tr>
</table>
{{#if debug}} {{#if debug}}
<pre>{{#dumpObject}}{{{.}}}{{/dumpObject}}</pre> <pre>{{#dumpObject}}{{{.}}}{{/dumpObject}}</pre>
@ -64,5 +96,5 @@
{{> kanji debug=../debug addable=../addable source=../source root=../root}} {{> kanji debug=../debug addable=../addable source=../source root=../root}}
{{/each}} {{/each}}
{{else}} {{else}}
<p class="note">No results found.</p> <p class="note">No results found</p>
{{/if}} {{/if}}

View File

@ -41,6 +41,14 @@
</div> </div>
{{/if}} {{/if}}
{{#if frequencies}}
<div>
{{#each frequencies}}
<span class="label label-default tag-frequency">{{dictionary}}:{{frequency}}</span>
{{/each}}
</div>
{{/if}}
<div class="glossary"> <div class="glossary">
{{#if grouped}} {{#if grouped}}
{{#if definitions.[1]}} {{#if definitions.[1]}}