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 @@
-
-
-
-
+
+
+
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.
+
+
+
+ - Left-click on the icon to enable or disable Yomichan for the current browser instance.
+ - Right-click on the icon and select Options to open the Yomichan options page.
+ - Hold down Shift or the middle mouse button as you move your cursor over text to see definitions (or Shift + Ctrl for Kanji).
+ - Resize the definitions window by dragging the bottom-left corner inwards or outwards.
+ - Click on Kanji in the definition window to view additional information about that character.
+
+
+
+
+
+
よろしくね!
+
+
+
+
+
+
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($('