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