diff --git a/.gitattributes b/.gitattributes index c575774d..74ed3a99 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,4 @@ -util/data/*dic* filter=lfs diff=lfs merge=lfs -text -ext/bg/data/*dic* filter=lfs diff=lfs merge=lfs -text +ext/bg/data/edict/*.json filter=lfs diff=lfs merge=lfs -text +ext/bg/data/enamdict/*.json filter=lfs diff=lfs merge=lfs -text +ext/bg/data/kanjidic/*.json filter=lfs diff=lfs merge=lfs -text *.ttf filter=lfs diff=lfs merge=lfs -text diff --git a/ext/bg/background.html b/ext/bg/background.html index c35e917d..c490df81 100644 --- a/ext/bg/background.html +++ b/ext/bg/background.html @@ -2,7 +2,9 @@ + + diff --git a/ext/bg/guide.html b/ext/bg/guide.html deleted file mode 100644 index a3fa8221..00000000 --- a/ext/bg/guide.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - - Yomichan Guide - - - - -
- - - -

This is a minimal guide to get you started with Yomichan. For complete documentation, visit the official homepage.

- -
    -
  1. Left-click on the icon to enable or disable Yomichan for the current browser instance.
  2. -
  3. Right-click on the icon and select Options to open the Yomichan options page.
  4. -
  5. Hold down Shift or the middle mouse button as you move your cursor over text to see definitions.
  6. -
  7. Resize the definition window by dragging the bottom-left corner inwards or outwards.
  8. -
  9. Click on Kanji in the definition window to view additional information about that character.
  10. -
- -

Enjoy!

-
- - diff --git a/ext/bg/import.html b/ext/bg/import.html new file mode 100644 index 00000000..b5d2db68 --- /dev/null +++ b/ext/bg/import.html @@ -0,0 +1,62 @@ + + + + + Yomichan Dictionary Import + + + + + +
+ + +

Thank you for downloading this extension! I sincerely hope that it will assist you on your language learning journey.

+ +
+

Dictionary Import

+ +

+ Before it can be used for the first time, Yomichan must import the Japanese dictionary data included with this extension. This process can take a + couple of minutes to finish so please be patient! Please do not completely exit out of your browser until this process completes. +

+ +
+
+
+ +
Dictionary import complete!
+
+ +
+

Quick Guide

+ +

+ Please read the steps outlined below to get quickly get up and running with Yomichan. For complete documentation, + visit the official homepage. +

+ +
    +
  1. Left-click on the icon to enable or disable Yomichan for the current browser instance.
  2. +
  3. Right-click on the icon and select Options to open the Yomichan options page.
  4. +
  5. Hold down Shift or the middle mouse button as you move your cursor over text to see definitions (or Shift + Ctrl for Kanji).
  6. +
  7. Resize the definitions window by dragging the bottom-left corner inwards or outwards.
  8. +
  9. Click on Kanji in the definition window to view additional information about that character.
  10. +
+
+ +
+ +

よろしくね!

+
+ + + + + diff --git a/ext/bg/js/deinflector.js b/ext/bg/js/deinflector.js index 0eabd0f3..8b9f88e2 100644 --- a/ext/bg/js/deinflector.js +++ b/ext/bg/js/deinflector.js @@ -26,32 +26,36 @@ class Deinflection { } validate(validator) { - for (const tags of validator(this.term)) { - if (this.tags.length === 0) { - return true; - } - - for (const tag of this.tags) { - if (tags.indexOf(tag) !== -1) { + return validator(this.term).then(sets => { + for (const tags of sets) { + if (this.tags.length === 0) { return true; } - } - } - return false; + for (const tag of this.tags) { + if (tags.includes(tag)) { + return true; + } + } + } + + return false; + }); } deinflect(validator, rules) { - if (this.validate(validator)) { - const child = new Deinflection(this.term, this.tags); - this.children.push(child); - } + const promises = [ + this.validate(validator).then(valid => { + const child = new Deinflection(this.term, this.tags); + this.children.push(child); + }) + ]; for (const rule in rules) { for (const variant of rules[rule]) { let allowed = this.tags.length === 0; for (const tag of this.tags) { - if (variant.ti.indexOf(tag) !== -1) { + if (variant.ti.includes(tag)) { allowed = true; break; } @@ -62,14 +66,24 @@ class Deinflection { } const term = this.term.slice(0, -variant.ki.length) + variant.ko; - const child = new Deinflection(term, variant.to, rule); - if (child.deinflect(validator, rules)) { - this.children.push(child); + if (term.length === 0) { + continue; } + + const child = new Deinflection(term, variant.to, rule); + promises.push( + child.deinflect(validator, rules).then(valid => { + if (valid) { + this.children.push(child); + } + } + )); } } - return this.children.length > 0; + return Promise.all(promises).then(() => { + return this.children.length > 0; + }); } gather() { @@ -105,10 +119,6 @@ class Deinflector { deinflect(term, validator) { const node = new Deinflection(term); - if (node.deinflect(validator, this.rules)) { - return node.gather(); - } - - return null; + return node.deinflect(validator, this.rules).then(success => success ? node.gather() : []); } } diff --git a/ext/bg/js/dictionary.js b/ext/bg/js/dictionary.js index a6438523..4562c821 100644 --- a/ext/bg/js/dictionary.js +++ b/ext/bg/js/dictionary.js @@ -19,63 +19,199 @@ class Dictionary { constructor() { - this.termDicts = {}; - this.kanjiDicts = {}; + this.db = null; + this.dbVer = 1; + this.entities = null; } - addTermDict(name, dict) { - this.termDicts[name] = dict; + initDb() { + if (this.db !== null) { + return Promise.reject('database already initialized'); + } + + this.db = new Dexie('dict'); + this.db.version(1).stores({ + terms: '++id,expression,reading', + entities: '++,name', + kanji: '++,character', + meta: 'name,value', + }); } - addKanjiDict(name, dict) { - this.kanjiDicts[name] = dict; + prepareDb() { + this.initDb(); + + return this.db.meta.get('version').then(row => { + return row ? row.value : 0; + }).catch(() => { + return 0; + }).then(version => { + if (this.dbVer === version) { + return true; + } + + const db = this.db; + this.db.close(); + this.db = null; + + return db.delete().then(() => { + this.initDb(); + return false; + }); + }); + } + + sealDb() { + if (this.db === null) { + return Promise.reject('database not initialized'); + } + + return this.db.meta.put({name: 'version', value: this.dbVer}); } findTerm(term) { - let results = []; - - for (let name in this.termDicts) { - const dict = this.termDicts[name]; - if (!(term in dict.i)) { - continue; - } - - const indices = dict.i[term].split(' ').map(Number); - results = results.concat( - indices.map(index => { - const [e, r, t, ...g] = dict.d[index]; - return { - expression: e, - reading: r, - tags: t.split(' '), - glossary: g, - entities: dict.e, - id: index - }; - }) - ); + if (this.db === null) { + return Promise.reject('database not initialized'); } - return results; + const results = []; + return this.db.terms.where('expression').equals(term).or('reading').equals(term).each(row => { + results.push({ + expression: row.expression, + reading: row.reading, + tags: row.tags.split(' '), + glossary: row.glossary, + id: row.id + }); + }).then(() => { + return this.getEntities(); + }).then(entities => { + for (const result of results) { + result.entities = entities; + } + + return results; + }); } findKanji(kanji) { - const results = []; - - for (let name in this.kanjiDicts) { - const def = this.kanjiDicts[name].c[kanji]; - if (def) { - const [k, o, t, ...g] = def; - results.push({ - character: kanji, - kunyomi: k.split(' '), - onyomi: o.split(' '), - tags: t.split(' '), - glossary: g - }); - } + if (this.db === null) { + return Promise.reject('database not initialized'); } - return results; + const results = []; + return this.db.kanji.where('character').equals(kanji).each(row => { + results.push({ + character: row.character, + onyomi: row.onyomi.split(' '), + kunyomi: row.kunyomi.split(' '), + tags: row.tags.split(' '), + glossary: row.meanings + }); + }).then(() => results); + } + + getEntities(tags) { + if (this.db === null) { + return Promise.reject('database not initialized'); + } + + if (this.entities !== null) { + return Promise.resolve(this.entities); + } + + return this.db.entities.toArray(rows => { + this.entities = {}; + for (const row of rows) { + this.entities[row.name] = row.value; + } + + return this.entities; + }); + } + + importTermDict(indexUrl, callback) { + if (this.db === null) { + return Promise.reject('database not initialized'); + } + + const indexDir = indexUrl.slice(0, indexUrl.lastIndexOf('/')); + return loadJson(indexUrl).then(index => { + const entities = []; + for (const [name, value] of index.ents) { + entities.push({name, value}); + } + + return this.db.entities.bulkAdd(entities).then(() => { + if (this.entities === null) { + this.entities = {}; + } + + for (const entity of entities) { + this.entities[entity.name] = entity.value; + } + }).then(() => { + const loaders = []; + for (let i = 1; i <= index.banks; ++i) { + const bankUrl = `${indexDir}/bank_${i}.json`; + loaders.push(() => { + return loadJson(bankUrl).then(definitions => { + const rows = []; + for (const [expression, reading, tags, ...glossary] of definitions) { + rows.push({expression, reading, tags, glossary}); + } + + return this.db.terms.bulkAdd(rows).then(() => { + if (callback) { + callback(i, index.banks, indexUrl); + } + }); + }); + }); + } + + let chain = Promise.resolve(); + for (const loader of loaders) { + chain = chain.then(loader); + } + + return chain; + }); + }); + } + + importKanjiDict(indexUrl, callback) { + if (this.db === null) { + return Promise.reject('database not initialized'); + } + + const indexDir = indexUrl.slice(0, indexUrl.lastIndexOf('/')); + return loadJson(indexUrl).then(index => { + const loaders = []; + for (let i = 1; i <= index.banks; ++i) { + const bankUrl = `${indexDir}/bank_${i}.json`; + loaders.push(() => { + return loadJson(bankUrl).then(definitions => { + const rows = []; + for (const [character, onyomi, kunyomi, tags, ...meanings] of definitions) { + rows.push({character, onyomi, kunyomi, tags, meanings}); + } + + return this.db.kanji.bulkAdd(rows).then(() => { + if (callback) { + callback(i, index.banks, indexUrl); + } + }); + }); + }); + } + + let chain = Promise.resolve(); + for (const loader of loaders) { + chain = chain.then(loader); + } + + return chain; + }); } } diff --git a/ext/fg/js/api.js b/ext/bg/js/import.js similarity index 54% rename from ext/fg/js/api.js rename to ext/bg/js/import.js index 643d0360..0601cb9f 100644 --- a/ext/fg/js/api.js +++ b/ext/bg/js/import.js @@ -17,26 +17,20 @@ */ -function bgSendMessage(action, params) { - return new Promise((resolve, reject) => chrome.runtime.sendMessage({action, params}, resolve)); +function api_setProgress(progress) { + $('.progress-bar').css('width', `${progress}%`); + + if (progress === 100.0) { + $('.progress').hide(); + $('.alert').show(); + } } -function bgFindTerm(text) { - return bgSendMessage('findTerm', {text}); -} +chrome.runtime.onMessage.addListener(({action, params}, sender, callback) => { + const method = this['api_' + action]; + if (typeof(method) === 'function') { + method.call(this, params); + } -function bgFindKanji(text) { - return bgSendMessage('findKanji', {text}); -} - -function bgRenderText(data, template) { - return bgSendMessage('renderText', {data, template}); -} - -function bgCanAddDefinitions(definitions, modes) { - return bgSendMessage('canAddDefinitions', {definitions, modes}); -} - -function bgAddDefinition(definition, mode) { - return bgSendMessage('addDefinition', {definition, mode}); -} + callback(); +}); diff --git a/ext/bg/js/options-form.js b/ext/bg/js/options-form.js index 3dab0a87..1cd050b4 100644 --- a/ext/bg/js/options-form.js +++ b/ext/bg/js/options-form.js @@ -32,7 +32,7 @@ function fieldsToDict(selection) { function modelIdToFieldOptKey(id) { return { - 'anki-term-model': 'ankiTermFields', + 'anki-term-model': 'ankiTermFields', 'anki-kanji-model': 'ankiKanjiFields' }[id]; } @@ -60,15 +60,14 @@ function modelIdToMarkers(id) { }[id]; } -function formToOptions(section, callback) { - loadOptions((optsOld) => { +function formToOptions(section) { + return loadOptions().then(optsOld => { const optsNew = $.extend({}, optsOld); switch (section) { case 'general': optsNew.scanLength = parseInt($('#scan-length').val(), 10); optsNew.activateOnStartup = $('#activate-on-startup').prop('checked'); - optsNew.loadEnamDict = $('#load-enamdict').prop('checked'); optsNew.selectMatchedText = $('#select-matched-text').prop('checked'); optsNew.showAdvancedOptions = $('#show-advanced-options').prop('checked'); optsNew.enableAudioPlayback = $('#enable-audio-playback').prop('checked'); @@ -86,7 +85,10 @@ function formToOptions(section, callback) { break; } - callback(sanitizeOptions(optsNew), sanitizeOptions(optsOld)); + return { + optsNew: sanitizeOptions(optsNew), + optsOld: sanitizeOptions(optsOld) + }; }); } @@ -95,9 +97,9 @@ function populateAnkiDeckAndModel(opts) { const ankiDeck = $('.anki-deck'); ankiDeck.find('option').remove(); - yomi.api_getDeckNames({callback: (names) => { + yomi.api_getDeckNames({callback: names => { if (names !== null) { - names.forEach((name) => ankiDeck.append($('