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)
* **[JMnedict](http://www.edrdg.org/enamdict/enamdict_doc.html)** (Japanese names)
* [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.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 ##
@ -240,6 +242,12 @@ exact versions used for distribution.
## 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!**
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) {
this.remoteVersion = await this.ankiInvoke('version');
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 ?
translator.findTermsGrouped.bind(translator) :
translator.findTerms.bind(translator);
translator.findTermsSplit.bind(translator);
const {definitions, length} = await searcher(
text,

View File

@ -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);

View File

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

View File

@ -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,69 +114,199 @@ 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 count = await this.db.dictionaries.where('title').equals(title).count();
if (count > 0) {
throw `dictionary "${title}" is already imported`;
const indexDataLoaded = async summary => {
if (summary.version > 2) {
throw 'Unsupported dictionary version';
}
await this.db.dictionaries.add({title, version, revision, hasTerms, hasKanji});
const count = await this.db.dictionaries.where('title').equals(summary.title).count();
if (count > 0) {
throw 'Dictionary is already imported';
}
await this.db.dictionaries.add(summary);
};
const termDataLoaded = async (summary, entries, total, current) => {
if (callback) {
callback(total, current);
}
const rows = [];
for (const tag in tagMeta || {}) {
const meta = tagMeta[tag];
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.terms.bulkAdd(rows);
};
const termMetaDataLoaded = async (summary, entries, total, current) => {
if (callback) {
callback(total, current);
}
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) {
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 kanjiMetaDataLoaded = async (summary, entries, total, current) => {
if (callback) {
callback(total, current);
}
const rows = [];
for (const [character, mode, data] of entries) {
rows.push({
character,
mode,
data,
dictionary: summary.title
});
}
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: tag,
category: meta.category,
notes: meta.notes,
order: meta.order,
dictionary: title
name,
category,
order,
notes,
dictionary: summary.title
});
rows.push(row);
@ -182,94 +315,103 @@ class Database {
await this.db.tagMeta.bulkAdd(rows);
};
const termsLoaded = async (title, entries, total, current) => {
if (callback) {
callback(total, current);
}
const rows = [];
for (const [expression, reading, tags, rules, score, ...glossary] of entries) {
rows.push({
expression,
reading,
tags,
rules,
score,
glossary,
dictionary: title
});
}
await this.db.terms.bulkAdd(rows);
};
const kanjiLoaded = async (title, entries, total, current) => {
if (callback) {
callback(total, current);
}
const rows = [];
for (const [character, onyomi, kunyomi, tags, ...meanings] of entries) {
rows.push({
character,
onyomi,
kunyomi,
tags,
meanings,
dictionary: title
});
}
await this.db.kanji.bulkAdd(rows);
};
await Database.importDictionaryZip(archive, indexLoaded, termsLoaded, kanjiLoaded);
return summary;
return await Database.importDictionaryZip(
archive,
indexDataLoaded,
termDataLoaded,
termMetaDataLoaded,
kanjiDataLoaded,
kanjiMetaDataLoaded,
tagDataLoaded
);
}
static async importDictionaryZip(archive, indexLoaded, termsLoaded, kanjiLoaded) {
const files = (await JSZip.loadAsync(archive)).files;
static async importDictionaryZip(
archive,
indexDataLoaded,
termDataLoaded,
termMetaDataLoaded,
kanjiDataLoaded,
kanjiMetaDataLoaded,
tagDataLoaded
) {
const zip = await JSZip.loadAsync(archive);
const indexFile = files['index.json'];
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
};
const banksTotal = index.termBanks + index.kanjiBanks;
let banksLoaded = 0;
if (indexDataLoaded) {
await indexDataLoaded(summary);
}
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 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`;
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++);
}
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';
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;
}
}

View File

@ -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';

View File

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

View File

@ -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');
}
});
}

View File

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

View File

@ -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();

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";
},"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;

View File

@ -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,88 @@ 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 = [];
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;
}
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);
}
function utilDatabaseGetDictionaries() {
return utilBackend().translator.database.getDictionaries();
function utilDatabaseGetTitles() {
return utilBackend().translator.database.getTitles();
}
function utilDatabasePurge() {

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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}`);
}
}

View File

@ -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;
if (this.pendingLookup || this.popup.containsPoint(point)) {
return;
}
const textSource = docRangeFromPoint(point);
let hideResults = !textSource || !textSource.containsPoint(point);
try {
if (this.pendingLookup) {
return;
}
textSource = docRangeFromPoint(point);
if (!textSource || !textSource.containsPoint(point)) {
docImposterDestroy();
return;
}
if (this.textSourceLast && this.textSourceLast.equals(textSource)) {
return;
}
this.pendingLookup = true;
if (!await this.searchTerms(textSource)) {
await this.searchKanji(textSource);
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;
}
}

View File

@ -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});

View File

@ -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;
}
}

View File

@ -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"},

View File

@ -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;
}

View File

@ -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);

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"}}
<div class="entry" data-type="kanji">
<div class="actions">
@ -13,44 +28,61 @@
<div class="glyph">{{character}}</div>
<div class="reading">
<table>
<tr>
<th>Kunyomi:</th>
<td>
{{#each kunyomi}}
{{.}}{{#unless @last}}, {{/unless}}
{{/each}}
</td>
</tr>
<tr>
<th>Onyomi:</th>
<td>
{{#each onyomi}}
{{.}}{{#unless @last}}, {{/unless}}
{{/each}}
</td>
</tr>
</table>
{{#if frequencies}}
<div>
{{#each frequencies}}
<span class="label label-default tag-frequency">{{dictionary}}:{{frequency}}</span>
{{/each}}
</div>
{{/if}}
{{#if tags}}
<div>
{{#each tags}}
<span class="label label-default tag-{{category}}" title="{{notes}}">{{name}}</span>
{{/each}}
</div>
{{/if}}
<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}}
</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}}
<pre>{{#dumpObject}}{{{.}}}{{/dumpObject}}</pre>
@ -64,5 +96,5 @@
{{> kanji debug=../debug addable=../addable source=../source root=../root}}
{{/each}}
{{else}}
<p class="note">No results found.</p>
<p class="note">No results found</p>
{{/if}}

View File

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