Merge branch 'dev'
This commit is contained in:
commit
39fa11f72b
@ -9,7 +9,7 @@
|
||||
<script src="js/ankinull.js"></script>
|
||||
<script src="js/templates.js"></script>
|
||||
<script src="js/util.js"></script>
|
||||
<script src="js/dictionary.js"></script>
|
||||
<script src="js/database.js"></script>
|
||||
<script src="js/deinflector.js"></script>
|
||||
<script src="js/translator.js"></script>
|
||||
<script src="js/options.js"></script>
|
||||
|
2799
ext/bg/data/deinflect.json
Normal file
2799
ext/bg/data/deinflect.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -2,14 +2,9 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Yomichan Dictionary Import</title>
|
||||
<title>Welcome to Yomichan!</title>
|
||||
<link rel="stylesheet" type="text/css" href="../lib/bootstrap-3.3.6-dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="../lib/bootstrap-3.3.6-dist/css/bootstrap-theme.min.css">
|
||||
<style>
|
||||
div.alert {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
@ -19,40 +14,23 @@
|
||||
|
||||
<p>Thank you for downloading this extension! I sincerely hope that it will assist you on your language learning journey.</p>
|
||||
|
||||
<div>
|
||||
<h2>Dictionary Import</h2>
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-bar-striped" style="width: 0%"></div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-success">Dictionary import complete!</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2>Quick Guide</h2>
|
||||
|
||||
<p>
|
||||
Please read the steps outlined below to get quickly get up and running with Yomichan. For complete documentation,
|
||||
visit the <a href="https://foosoft.net/projects/yomichan-chrome/">official homepage</a>.
|
||||
visit the <a href="https://foosoft.net/projects/yomichan-chrome/" target="_blank">official homepage</a>.
|
||||
</p>
|
||||
|
||||
<ol>
|
||||
<li>Left-click on the <img src="../img/icon16.png" alt> icon to enable or disable Yomichan for the current browser instance.</li>
|
||||
<li>Right-click on the <img src="../img/icon16.png" alt> icon and select <em>Options</em> to open the Yomichan options page.</li>
|
||||
<li>Import any dictionaries (bundled or custom) you wish to use for Kanji and term searches.</li>
|
||||
<li>Hold down <kbd>Shift</kbd> (or the middle mouse button) as you hover over text to see term definitions.</li>
|
||||
<li>Resize the definitions window by dragging the bottom-left corner inwards or outwards.</li>
|
||||
<li>Click on Kanji in the definition window to view additional information about that character.</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="../lib/jquery-2.2.2.min.js"></script>
|
||||
<script src="js/import.js"></script>
|
||||
</body>
|
||||
</html>
|
BIN
ext/bg/img/paypal.gif
Normal file
BIN
ext/bg/img/paypal.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
297
ext/bg/js/database.js
Normal file
297
ext/bg/js/database.js
Normal file
@ -0,0 +1,297 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Alex Yatskov <alex@foosoft.net>
|
||||
* Author: Alex Yatskov <alex@foosoft.net>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
class Database {
|
||||
constructor() {
|
||||
this.db = null;
|
||||
this.tagMetaCache = {};
|
||||
}
|
||||
|
||||
prepare() {
|
||||
if (this.db !== null) {
|
||||
return Promise.reject('database already initialized');
|
||||
}
|
||||
|
||||
this.db = new Dexie('dict');
|
||||
this.db.version(1).stores({
|
||||
terms: '++id,dictionary,expression,reading',
|
||||
kanji: '++,dictionary,character',
|
||||
tagMeta: '++,dictionary',
|
||||
dictionaries: '++,title,version',
|
||||
});
|
||||
|
||||
return this.db.open();
|
||||
}
|
||||
|
||||
purge() {
|
||||
if (this.db === null) {
|
||||
return Promise.reject('database not initialized');
|
||||
}
|
||||
|
||||
this.db.close();
|
||||
return this.db.delete().then(() => {
|
||||
this.db = null;
|
||||
this.tagMetaCache = {};
|
||||
return this.prepare();
|
||||
});
|
||||
}
|
||||
|
||||
findTerm(term, dictionaries) {
|
||||
if (this.db === null) {
|
||||
return Promise.reject('database not initialized');
|
||||
}
|
||||
|
||||
const results = [];
|
||||
return this.db.terms.where('expression').equals(term).or('reading').equals(term).each(row => {
|
||||
if (dictionaries.includes(row.dictionary)) {
|
||||
results.push({
|
||||
expression: row.expression,
|
||||
reading: row.reading,
|
||||
tags: splitField(row.tags),
|
||||
rules: splitField(row.rules),
|
||||
glossary: row.glossary,
|
||||
score: row.score,
|
||||
dictionary: row.dictionary,
|
||||
id: row.id
|
||||
});
|
||||
}
|
||||
}).then(() => {
|
||||
return this.cacheTagMeta(dictionaries);
|
||||
}).then(() => {
|
||||
for (const result of results) {
|
||||
result.tagMeta = this.tagMetaCache[result.dictionary] || {};
|
||||
}
|
||||
|
||||
return results;
|
||||
});
|
||||
}
|
||||
|
||||
findKanji(kanji, dictionaries) {
|
||||
if (this.db === null) {
|
||||
return Promise.reject('database not initialized');
|
||||
}
|
||||
|
||||
const results = [];
|
||||
return this.db.kanji.where('character').equals(kanji).each(row => {
|
||||
if (dictionaries.includes(row.dictionary)) {
|
||||
results.push({
|
||||
character: row.character,
|
||||
onyomi: splitField(row.onyomi),
|
||||
kunyomi: splitField(row.kunyomi),
|
||||
tags: splitField(row.tags),
|
||||
glossary: row.meanings,
|
||||
dictionary: row.dictionary
|
||||
});
|
||||
}
|
||||
}).then(() => {
|
||||
return this.cacheTagMeta(dictionaries);
|
||||
}).then(() => {
|
||||
for (const result of results) {
|
||||
result.tagMeta = this.tagMetaCache[result.dictionary] || {};
|
||||
}
|
||||
|
||||
return results;
|
||||
});
|
||||
}
|
||||
|
||||
cacheTagMeta(dictionaries) {
|
||||
if (this.db === null) {
|
||||
return Promise.reject('database not initialized');
|
||||
}
|
||||
|
||||
const promises = [];
|
||||
for (const dictionary of dictionaries) {
|
||||
if (this.tagMetaCache[dictionary]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const tagMeta = {};
|
||||
promises.push(
|
||||
this.db.tagMeta.where('dictionary').equals(dictionary).each(row => {
|
||||
tagMeta[row.name] = {category: row.category, notes: row.notes, order: row.order};
|
||||
}).then(() => {
|
||||
this.tagMetaCache[dictionary] = tagMeta;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
getDictionaries() {
|
||||
if (this.db === null) {
|
||||
return Promise.reject('database not initialized');
|
||||
}
|
||||
|
||||
return this.db.dictionaries.toArray();
|
||||
}
|
||||
|
||||
deleteDictionary(title, callback) {
|
||||
if (this.db === null) {
|
||||
return Promise.reject('database not initialized');
|
||||
}
|
||||
|
||||
return this.db.dictionaries.where('title').equals(title).first(info => {
|
||||
if (!info) {
|
||||
return;
|
||||
}
|
||||
|
||||
let termCounter = Promise.resolve(0);
|
||||
if (info.hasTerms) {
|
||||
termCounter = this.db.terms.where('dictionary').equals(title).count();
|
||||
}
|
||||
|
||||
let kanjiCounter = Promise.resolve(0);
|
||||
if (info.hasKanji) {
|
||||
kanjiCounter = this.db.kanji.where('dictionary').equals(title).count();
|
||||
}
|
||||
|
||||
return Promise.all([termCounter, kanjiCounter]).then(([termCount, kanjiCount]) => {
|
||||
const rowLimit = 500;
|
||||
const totalCount = termCount + kanjiCount;
|
||||
let deletedCount = 0;
|
||||
|
||||
let termDeleter = Promise.resolve();
|
||||
if (info.hasTerms) {
|
||||
const termDeleterFunc = () => {
|
||||
return this.db.terms.where('dictionary').equals(title).limit(rowLimit).delete().then(count => {
|
||||
if (count === 0) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
deletedCount += count;
|
||||
if (callback) {
|
||||
callback(totalCount, deletedCount);
|
||||
}
|
||||
|
||||
return termDeleterFunc();
|
||||
});
|
||||
};
|
||||
|
||||
termDeleter = termDeleterFunc();
|
||||
}
|
||||
|
||||
let kanjiDeleter = Promise.resolve();
|
||||
if (info.hasKanji) {
|
||||
const kanjiDeleterFunc = () => {
|
||||
return this.db.kanji.where('dictionary').equals(title).limit(rowLimit).delete().then(count => {
|
||||
if (count === 0) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
deletedCount += count;
|
||||
if (callback) {
|
||||
callback(totalCount, deletedCount);
|
||||
}
|
||||
|
||||
return kanjiDeleterFunc();
|
||||
});
|
||||
};
|
||||
|
||||
kanjiDeleter = kanjiDeleterFunc();
|
||||
}
|
||||
|
||||
return Promise.all([termDeleter, kanjiDeleter]);
|
||||
});
|
||||
}).then(() => {
|
||||
return this.db.tagMeta.where('dictionary').equals(title).delete();
|
||||
}).then(() => {
|
||||
return this.db.dictionaries.where('title').equals(title).delete();
|
||||
}).then(() => {
|
||||
delete this.cacheTagMeta[title];
|
||||
});
|
||||
}
|
||||
|
||||
importDictionary(indexUrl, callback) {
|
||||
if (this.db === null) {
|
||||
return Promise.reject('database not initialized');
|
||||
}
|
||||
|
||||
let summary = null;
|
||||
const indexLoaded = (title, version, tagMeta, hasTerms, hasKanji) => {
|
||||
summary = {title, hasTerms, hasKanji, version};
|
||||
return this.db.dictionaries.where('title').equals(title).count().then(count => {
|
||||
if (count > 0) {
|
||||
return Promise.reject(`dictionary "${title}" is already imported`);
|
||||
}
|
||||
|
||||
return this.db.dictionaries.add({title, version, hasTerms, hasKanji}).then(() => {
|
||||
const rows = [];
|
||||
for (const tag in tagMeta || {}) {
|
||||
const meta = tagMeta[tag];
|
||||
const row = sanitizeTag({
|
||||
name: tag,
|
||||
category: meta.category,
|
||||
notes: meta.notes,
|
||||
order: meta.order,
|
||||
dictionary: title
|
||||
});
|
||||
|
||||
rows.push(row);
|
||||
}
|
||||
|
||||
return this.db.tagMeta.bulkAdd(rows);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const termsLoaded = (title, entries, total, current) => {
|
||||
const rows = [];
|
||||
for (const [expression, reading, tags, rules, score, ...glossary] of entries) {
|
||||
rows.push({
|
||||
expression,
|
||||
reading,
|
||||
tags,
|
||||
rules,
|
||||
score,
|
||||
glossary,
|
||||
dictionary: title
|
||||
});
|
||||
}
|
||||
|
||||
return this.db.terms.bulkAdd(rows).then(() => {
|
||||
if (callback) {
|
||||
callback(total, current, indexUrl);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const kanjiLoaded = (title, entries, total, current) => {
|
||||
const rows = [];
|
||||
for (const [character, onyomi, kunyomi, tags, ...meanings] of entries) {
|
||||
rows.push({
|
||||
character,
|
||||
onyomi,
|
||||
kunyomi,
|
||||
tags,
|
||||
meanings,
|
||||
dictionary: title
|
||||
});
|
||||
}
|
||||
|
||||
return this.db.kanji.bulkAdd(rows).then(() => {
|
||||
if (callback) {
|
||||
callback(total, current, indexUrl);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return importJsonDb(indexUrl, indexLoaded, termsLoaded, kanjiLoaded).then(() => summary);
|
||||
}
|
||||
}
|
@ -18,50 +18,47 @@
|
||||
|
||||
|
||||
class Deinflection {
|
||||
constructor(term, tags=[], rule='') {
|
||||
this.children = [];
|
||||
constructor(term, {rules=[], definitions=[], reason=''} = {}) {
|
||||
this.term = term;
|
||||
this.tags = tags;
|
||||
this.rule = rule;
|
||||
this.rules = rules;
|
||||
this.definitions = definitions;
|
||||
this.reason = reason;
|
||||
this.children = [];
|
||||
}
|
||||
|
||||
validate(validator) {
|
||||
return validator(this.term).then(sets => {
|
||||
for (const tags of sets) {
|
||||
if (this.tags.length === 0) {
|
||||
return true;
|
||||
deinflect(definer, reasons) {
|
||||
const define = () => {
|
||||
return definer(this.term).then(definitions => {
|
||||
if (this.rules.length === 0) {
|
||||
this.definitions = definitions;
|
||||
} else {
|
||||
for (const rule of this.rules) {
|
||||
for (const definition of definitions) {
|
||||
if (definition.rules.includes(rule)) {
|
||||
this.definitions.push(definition);
|
||||
}
|
||||
|
||||
for (const tag of this.tags) {
|
||||
if (tags.includes(tag)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return this.definitions.length > 0;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
deinflect(validator, rules) {
|
||||
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.tagsIn.includes(tag)) {
|
||||
allowed = true;
|
||||
const promises = [];
|
||||
for (const reason in reasons) {
|
||||
for (const variant of reasons[reason]) {
|
||||
let accept = this.rules.length === 0;
|
||||
if (!accept) {
|
||||
for (const rule of this.rules) {
|
||||
if (variant.rulesIn.includes(rule)) {
|
||||
accept = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!allowed || !this.term.endsWith(variant.kanaIn)) {
|
||||
if (!accept || !this.term.endsWith(variant.kanaIn)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -70,55 +67,61 @@ class Deinflection {
|
||||
continue;
|
||||
}
|
||||
|
||||
const child = new Deinflection(term, variant.tagsOut, rule);
|
||||
const child = new Deinflection(term, {reason, rules: variant.rulesOut});
|
||||
promises.push(
|
||||
child.deinflect(validator, rules).then(valid => {
|
||||
if (valid) {
|
||||
this.children.push(child);
|
||||
}
|
||||
}
|
||||
));
|
||||
child.deinflect(definer, reasons).then(valid => valid && this.children.push(child))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.all(promises).then(() => {
|
||||
return this.children.length > 0;
|
||||
return Promise.all(promises).then(define).then(valid => {
|
||||
if (valid && this.children.length > 0) {
|
||||
const child = new Deinflection(this.term, {rules: this.rules, definitions: this.definitions});
|
||||
this.children.push(child);
|
||||
}
|
||||
|
||||
return valid || this.children.length > 0;
|
||||
});
|
||||
}
|
||||
|
||||
gather() {
|
||||
if (this.children.length === 0) {
|
||||
return [{root: this.term, tags: this.tags, rules: []}];
|
||||
return [{
|
||||
source: this.term,
|
||||
rules: this.rules,
|
||||
definitions: this.definitions,
|
||||
reasons: [this.reason]
|
||||
}];
|
||||
}
|
||||
|
||||
const paths = [];
|
||||
const results = [];
|
||||
for (const child of this.children) {
|
||||
for (const path of child.gather()) {
|
||||
if (this.rule.length > 0) {
|
||||
path.rules.push(this.rule);
|
||||
for (const result of child.gather()) {
|
||||
if (this.reason.length > 0) {
|
||||
result.reasons.push(this.reason);
|
||||
}
|
||||
|
||||
path.source = this.term;
|
||||
paths.push(path);
|
||||
result.source = this.term;
|
||||
results.push(result);
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Deinflector {
|
||||
constructor() {
|
||||
this.rules = {};
|
||||
this.reasons = {};
|
||||
}
|
||||
|
||||
setRules(rules) {
|
||||
this.rules = rules;
|
||||
setReasons(reasons) {
|
||||
this.reasons = reasons;
|
||||
}
|
||||
|
||||
deinflect(term, validator) {
|
||||
deinflect(term, definer) {
|
||||
const node = new Deinflection(term);
|
||||
return node.deinflect(validator, this.rules).then(success => success ? node.gather() : []);
|
||||
return node.deinflect(definer, this.reasons).then(success => success ? node.gather() : []);
|
||||
}
|
||||
}
|
||||
|
@ -1,217 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Alex Yatskov <alex@foosoft.net>
|
||||
* Author: Alex Yatskov <alex@foosoft.net>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
class Dictionary {
|
||||
constructor() {
|
||||
this.db = null;
|
||||
this.dbVer = 2;
|
||||
this.entities = null;
|
||||
}
|
||||
|
||||
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',
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
if (this.db === null) {
|
||||
return Promise.reject('database not initialized');
|
||||
}
|
||||
|
||||
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: splitField(row.tags),
|
||||
glossary: row.glossary,
|
||||
id: row.id
|
||||
});
|
||||
}).then(() => {
|
||||
return this.getEntities();
|
||||
}).then(entities => {
|
||||
for (const result of results) {
|
||||
result.entities = entities;
|
||||
}
|
||||
|
||||
return results;
|
||||
});
|
||||
}
|
||||
|
||||
findKanji(kanji) {
|
||||
if (this.db === null) {
|
||||
return Promise.reject('database not initialized');
|
||||
}
|
||||
|
||||
const results = [];
|
||||
return this.db.kanji.where('character').equals(kanji).each(row => {
|
||||
results.push({
|
||||
character: row.character,
|
||||
onyomi: splitField(row.onyomi),
|
||||
kunyomi: splitField(row.kunyomi),
|
||||
tags: splitField(row.tags),
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Alex Yatskov <alex@foosoft.net>
|
||||
* Author: Alex Yatskov <alex@foosoft.net>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
function api_setProgress(progress) {
|
||||
$('.progress-bar').css('width', `${progress}%`);
|
||||
|
||||
if (progress === 100.0) {
|
||||
$('.progress').hide();
|
||||
$('.alert').show();
|
||||
}
|
||||
}
|
||||
|
||||
chrome.runtime.onMessage.addListener(({action, params}, sender, callback) => {
|
||||
const method = this['api_' + action];
|
||||
if (typeof(method) === 'function') {
|
||||
method.call(this, params);
|
||||
}
|
||||
|
||||
callback();
|
||||
});
|
@ -16,15 +16,291 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//
|
||||
// General
|
||||
//
|
||||
|
||||
function yomichan() {
|
||||
return chrome.extension.getBackgroundPage().yomichan;
|
||||
}
|
||||
|
||||
function getFormValues() {
|
||||
return loadOptions().then(optsOld => {
|
||||
const optsNew = $.extend({}, optsOld);
|
||||
|
||||
optsNew.activateOnStartup = $('#activate-on-startup').prop('checked');
|
||||
optsNew.enableAudioPlayback = $('#enable-audio-playback').prop('checked');
|
||||
optsNew.showAdvancedOptions = $('#show-advanced-options').prop('checked');
|
||||
optsNew.enableSoftKatakanaSearch = $('#enable-soft-katakana-search').prop('checked');
|
||||
|
||||
optsNew.holdShiftToScan = $('#hold-shift-to-scan').prop('checked');
|
||||
optsNew.selectMatchedText = $('#select-matched-text').prop('checked');
|
||||
optsNew.scanDelay = parseInt($('#scan-delay').val(), 10);
|
||||
optsNew.scanLength = parseInt($('#scan-length').val(), 10);
|
||||
|
||||
optsNew.ankiMethod = $('#anki-method').val();
|
||||
optsNew.ankiUsername = $('#anki-username').val();
|
||||
optsNew.ankiPassword = $('#anki-password').val();
|
||||
optsNew.ankiCardTags = $('#anki-card-tags').val().split(/[,; ]+/);
|
||||
optsNew.sentenceExtent = parseInt($('#sentence-extent').val(), 10);
|
||||
optsNew.ankiTermDeck = $('#anki-term-deck').val();
|
||||
optsNew.ankiTermModel = $('#anki-term-model').val();
|
||||
optsNew.ankiTermFields = fieldsToDict($('#term .anki-field-value'));
|
||||
optsNew.ankiKanjiDeck = $('#anki-kanji-deck').val();
|
||||
optsNew.ankiKanjiModel = $('#anki-kanji-model').val();
|
||||
optsNew.ankiKanjiFields = fieldsToDict($('#kanji .anki-field-value'));
|
||||
|
||||
$('.dict-group').each((index, element) => {
|
||||
const dictionary = $(element);
|
||||
const title = dictionary.data('title');
|
||||
const enableTerms = dictionary.find('.dict-enable-terms').prop('checked');
|
||||
const enableKanji = dictionary.find('.dict-enable-kanji').prop('checked');
|
||||
optsNew.dictionaries[title] = {enableTerms, enableKanji};
|
||||
});
|
||||
|
||||
return {
|
||||
optsNew: sanitizeOptions(optsNew),
|
||||
optsOld: sanitizeOptions(optsOld)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function updateVisibility(opts) {
|
||||
switch (opts.ankiMethod) {
|
||||
case 'ankiweb':
|
||||
$('#anki-general').show();
|
||||
$('.anki-login').show();
|
||||
break;
|
||||
case 'ankiconnect':
|
||||
$('#anki-general').show();
|
||||
$('.anki-login').hide();
|
||||
break;
|
||||
default:
|
||||
$('#anki-general').hide();
|
||||
break;
|
||||
}
|
||||
|
||||
if (opts.showAdvancedOptions) {
|
||||
$('.options-advanced').show();
|
||||
} else {
|
||||
$('.options-advanced').hide();
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(() => {
|
||||
Handlebars.partials = Handlebars.templates;
|
||||
|
||||
loadOptions().then(opts => {
|
||||
$('#activate-on-startup').prop('checked', opts.activateOnStartup);
|
||||
$('#enable-audio-playback').prop('checked', opts.enableAudioPlayback);
|
||||
$('#enable-soft-katakana-search').prop('checked', opts.enableSoftKatakanaSearch);
|
||||
$('#show-advanced-options').prop('checked', opts.showAdvancedOptions);
|
||||
|
||||
$('#hold-shift-to-scan').prop('checked', opts.holdShiftToScan);
|
||||
$('#select-matched-text').prop('checked', opts.selectMatchedText);
|
||||
$('#scan-delay').val(opts.scanDelay);
|
||||
$('#scan-length').val(opts.scanLength);
|
||||
|
||||
$('#anki-method').val(opts.ankiMethod);
|
||||
$('#anki-username').val(opts.ankiUsername);
|
||||
$('#anki-password').val(opts.ankiPassword);
|
||||
$('#anki-card-tags').val(opts.ankiCardTags.join(' '));
|
||||
$('#sentence-extent').val(opts.sentenceExtent);
|
||||
|
||||
$('input, select').not('.anki-model').change(onOptionsChanged);
|
||||
$('.anki-model').change(onAnkiModelChanged);
|
||||
|
||||
$('#dict-purge').click(onDictionaryPurge);
|
||||
$('#dict-importer a').click(onDictionarySetUrl);
|
||||
$('#dict-import').click(onDictionaryImport);
|
||||
$('#dict-url').on('input', onDictionaryUpdateUrl);
|
||||
|
||||
populateDictionaries(opts);
|
||||
populateAnkiDeckAndModel(opts);
|
||||
updateVisibility(opts);
|
||||
});
|
||||
});
|
||||
|
||||
//
|
||||
// Dictionary
|
||||
//
|
||||
|
||||
function database() {
|
||||
return yomichan().translator.database;
|
||||
}
|
||||
|
||||
function showDictionaryError(error) {
|
||||
const dialog = $('#dict-error');
|
||||
if (error) {
|
||||
dialog.show().find('span').text(error);
|
||||
} else {
|
||||
dialog.hide();
|
||||
}
|
||||
}
|
||||
|
||||
function showDictionarySpinner(show) {
|
||||
const spinner = $('#dict-spinner');
|
||||
if (show) {
|
||||
spinner.show();
|
||||
} else {
|
||||
spinner.hide();
|
||||
}
|
||||
}
|
||||
|
||||
function populateDictionaries(opts) {
|
||||
showDictionaryError(null);
|
||||
showDictionarySpinner(true);
|
||||
|
||||
const dictGroups = $('#dict-groups').empty();
|
||||
const dictWarning = $('#dict-warning').hide();
|
||||
|
||||
let dictCount = 0;
|
||||
return database().getDictionaries().then(rows => {
|
||||
rows.forEach(row => {
|
||||
const dictOpts = opts.dictionaries[row.title] || {enableTerms: false, enableKanji: false};
|
||||
const html = Handlebars.templates['dictionary.html']({
|
||||
title: row.title,
|
||||
version: row.version,
|
||||
hasTerms: row.hasTerms,
|
||||
hasKanji: row.hasKanji,
|
||||
enableTerms: dictOpts.enableTerms,
|
||||
enableKanji: dictOpts.enableKanji
|
||||
});
|
||||
|
||||
dictGroups.append($(html));
|
||||
++dictCount;
|
||||
});
|
||||
|
||||
$('.dict-enable-terms, .dict-enable-kanji').change(onOptionsChanged);
|
||||
$('.dict-delete').click(onDictionaryDelete);
|
||||
}).catch(error => {
|
||||
showDictionaryError(error);
|
||||
}).then(() => {
|
||||
showDictionarySpinner(false);
|
||||
if (dictCount === 0) {
|
||||
dictWarning.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function onDictionaryPurge(e) {
|
||||
e.preventDefault();
|
||||
|
||||
showDictionaryError(null);
|
||||
showDictionarySpinner(true);
|
||||
|
||||
const dictControls = $('#dict-importer, #dict-groups').hide();
|
||||
const dictProgress = $('#dict-purge-progress').show();
|
||||
|
||||
return database().purge().catch(error => {
|
||||
showDictionaryError(error);
|
||||
}).then(() => {
|
||||
showDictionarySpinner(false);
|
||||
dictControls.show();
|
||||
dictProgress.hide();
|
||||
return loadOptions().then(opts => populateDictionaries(opts));
|
||||
});
|
||||
}
|
||||
|
||||
function onDictionaryDelete() {
|
||||
showDictionaryError(null);
|
||||
showDictionarySpinner(true);
|
||||
|
||||
const dictGroup = $(this).closest('.dict-group');
|
||||
const dictProgress = dictGroup.find('.dict-delete-progress').show();
|
||||
const dictControls = dictGroup.find('.dict-group-controls').hide();
|
||||
const setProgress = percent => {
|
||||
dictProgress.find('.progress-bar').css('width', `${percent}%`);
|
||||
};
|
||||
|
||||
setProgress(0.0);
|
||||
|
||||
database().deleteDictionary(dictGroup.data('title'), (total, current) => setProgress(current / total * 100.0)).catch(error => {
|
||||
showDictionaryError(error);
|
||||
}).then(() => {
|
||||
showDictionarySpinner(false);
|
||||
dictProgress.hide();
|
||||
dictControls.show();
|
||||
return loadOptions().then(opts => populateDictionaries(opts));
|
||||
});
|
||||
}
|
||||
|
||||
function onDictionaryImport() {
|
||||
showDictionaryError(null);
|
||||
showDictionarySpinner(true);
|
||||
|
||||
const dictUrl = $('#dict-url');
|
||||
const dictImporter = $('#dict-importer').hide();
|
||||
const dictProgress = $('#dict-import-progress').show();
|
||||
const setProgress = percent => {
|
||||
dictProgress.find('.progress-bar').css('width', `${percent}%`);
|
||||
};
|
||||
|
||||
setProgress(0.0);
|
||||
|
||||
loadOptions().then(opts => {
|
||||
database().importDictionary(dictUrl.val(), (total, current) => setProgress(current / total * 100.0)).then(summary => {
|
||||
opts.dictionaries[summary.title] = {enableTerms: summary.hasTerms, enableKanji: summary.hasKanji};
|
||||
return saveOptions(opts).then(() => yomichan().setOptions(opts));
|
||||
}).then(() => {
|
||||
return populateDictionaries(opts);
|
||||
}).catch(error => {
|
||||
showDictionaryError(error);
|
||||
}).then(() => {
|
||||
showDictionarySpinner(false);
|
||||
dictProgress.hide();
|
||||
dictImporter.show();
|
||||
dictUrl.val('');
|
||||
dictUrl.trigger('input');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function onDictionarySetUrl(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const dictUrl = $('#dict-url');
|
||||
const url = $(this).data('url');
|
||||
if (url.includes('/')) {
|
||||
dictUrl.val(url);
|
||||
} else {
|
||||
dictUrl.val(chrome.extension.getURL(`bg/data/${url}/index.json`));
|
||||
}
|
||||
|
||||
dictUrl.trigger('input');
|
||||
}
|
||||
|
||||
function onDictionaryUpdateUrl() {
|
||||
$('#dict-import').prop('disabled', $(this).val().length === 0);
|
||||
}
|
||||
|
||||
//
|
||||
// Anki
|
||||
//
|
||||
|
||||
function anki() {
|
||||
return yomichan().anki;
|
||||
}
|
||||
|
||||
function showAnkiSpinner(show) {
|
||||
const spinner = $('#anki-spinner');
|
||||
if (show) {
|
||||
spinner.show();
|
||||
} else {
|
||||
spinner.hide();
|
||||
}
|
||||
}
|
||||
|
||||
function showAnkiError(error) {
|
||||
const dialog = $('#anki-error');
|
||||
if (error) {
|
||||
dialog.show().find('span').text(error);
|
||||
}
|
||||
else {
|
||||
dialog.hide();
|
||||
}
|
||||
}
|
||||
|
||||
function fieldsToDict(selection) {
|
||||
const result = {};
|
||||
selection.each((index, element) => {
|
||||
@ -65,98 +341,40 @@ function modelIdToMarkers(id) {
|
||||
}[id];
|
||||
}
|
||||
|
||||
function getFormValues() {
|
||||
return loadOptions().then(optsOld => {
|
||||
const optsNew = $.extend({}, optsOld);
|
||||
|
||||
optsNew.activateOnStartup = $('#activate-on-startup').prop('checked');
|
||||
optsNew.enableAudioPlayback = $('#enable-audio-playback').prop('checked');
|
||||
optsNew.showAdvancedOptions = $('#show-advanced-options').prop('checked');
|
||||
optsNew.enableSoftKatakanaSearch = $('#enable-soft-katakana-search').prop('checked');
|
||||
|
||||
optsNew.holdShiftToScan = $('#hold-shift-to-scan').prop('checked');
|
||||
optsNew.selectMatchedText = $('#select-matched-text').prop('checked');
|
||||
optsNew.scanDelay = parseInt($('#scan-delay').val(), 10);
|
||||
optsNew.scanLength = parseInt($('#scan-length').val(), 10);
|
||||
|
||||
optsNew.ankiMethod = $('#anki-method').val();
|
||||
optsNew.ankiUsername = $('#anki-username').val();
|
||||
optsNew.ankiPassword = $('#anki-password').val();
|
||||
optsNew.ankiCardTags = $('#anki-card-tags').val().split(/[,; ]+/);
|
||||
optsNew.sentenceExtent = parseInt($('#sentence-extent').val(), 10);
|
||||
optsNew.ankiTermDeck = $('#anki-term-deck').val();
|
||||
optsNew.ankiTermModel = $('#anki-term-model').val();
|
||||
optsNew.ankiTermFields = fieldsToDict($('#term .anki-field-value'));
|
||||
optsNew.ankiKanjiDeck = $('#anki-kanji-deck').val();
|
||||
optsNew.ankiKanjiModel = $('#anki-kanji-model').val();
|
||||
optsNew.ankiKanjiFields = fieldsToDict($('#kanji .anki-field-value'));
|
||||
|
||||
return {
|
||||
optsNew: sanitizeOptions(optsNew),
|
||||
optsOld: sanitizeOptions(optsOld)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function updateVisibility(opts) {
|
||||
switch (opts.ankiMethod) {
|
||||
case 'ankiweb':
|
||||
$('#anki-general').show();
|
||||
$('.anki-login').show();
|
||||
break;
|
||||
case 'ankiconnect':
|
||||
$('#anki-general').show();
|
||||
$('.anki-login').hide();
|
||||
break;
|
||||
default:
|
||||
$('#anki-general').hide();
|
||||
break;
|
||||
}
|
||||
|
||||
if (opts.showAdvancedOptions) {
|
||||
$('.options-advanced').show();
|
||||
} else {
|
||||
$('.options-advanced').hide();
|
||||
}
|
||||
}
|
||||
|
||||
function populateAnkiDeckAndModel(opts) {
|
||||
const ankiSpinner = $('#anki-spinner');
|
||||
ankiSpinner.show();
|
||||
showAnkiError(null);
|
||||
showAnkiSpinner(true);
|
||||
|
||||
const ankiFormat = $('#anki-format');
|
||||
ankiFormat.hide();
|
||||
const ankiFormat = $('#anki-format').hide();
|
||||
|
||||
return Promise.all([anki().getDeckNames(), anki().getModelNames()]).then(([deckNames, modelNames]) => {
|
||||
const ankiDeck = $('.anki-deck');
|
||||
ankiDeck.find('option').remove();
|
||||
deckNames.forEach(name => ankiDeck.append($('<option/>', {value: name, text: name})));
|
||||
|
||||
$('#anki-term-deck').val(opts.ankiTermDeck);
|
||||
$('#anki-kanji-deck').val(opts.ankiKanjiDeck);
|
||||
|
||||
const ankiModel = $('.anki-model');
|
||||
ankiModel.find('option').remove();
|
||||
modelNames.forEach(name => ankiModel.append($('<option/>', {value: name, text: name})));
|
||||
|
||||
return anki().getDeckNames().then(names => {
|
||||
names.forEach(name => ankiDeck.append($('<option/>', {value: name, text: name})));
|
||||
$('#anki-term-deck').val(opts.ankiTermDeck);
|
||||
$('#anki-kanji-deck').val(opts.ankiKanjiDeck);
|
||||
return Promise.all([
|
||||
populateAnkiFields($('#anki-term-model').val(opts.ankiTermModel), opts),
|
||||
populateAnkiFields($('#anki-kanji-model').val(opts.ankiKanjiModel), opts)
|
||||
]);
|
||||
}).then(() => {
|
||||
return anki().getModelNames();
|
||||
}).then(names => {
|
||||
names.forEach(name => ankiModel.append($('<option/>', {value: name, text: name})));
|
||||
return populateAnkiFields($('#anki-term-model').val(opts.ankiTermModel), opts);
|
||||
}).then(() => {
|
||||
return populateAnkiFields($('#anki-kanji-model').val(opts.ankiKanjiModel), opts);
|
||||
}).then(() => {
|
||||
$('#anki-error').hide();
|
||||
ankiFormat.show();
|
||||
}).catch(error => {
|
||||
$('#anki-error').show().find('span').text(error);
|
||||
showAnkiError(error);
|
||||
}).then(() => {
|
||||
ankiSpinner.hide();
|
||||
showAnkiSpinner(false);
|
||||
});
|
||||
}
|
||||
|
||||
function populateAnkiFields(element, opts) {
|
||||
const table = element.closest('.tab-pane').find('.anki-fields');
|
||||
table.find('tbody').remove();
|
||||
const tab = element.closest('.tab-pane');
|
||||
const container = tab.find('tbody').empty();
|
||||
|
||||
const modelName = element.val();
|
||||
if (modelName === null) {
|
||||
@ -168,65 +386,16 @@ function populateAnkiFields(element, opts) {
|
||||
const markers = modelIdToMarkers(modelId);
|
||||
|
||||
return anki().getModelFieldNames(modelName).then(names => {
|
||||
const tbody = $('<tbody>');
|
||||
names.forEach(name => {
|
||||
const button = $('<button>', {type: 'button', class: 'btn btn-default dropdown-toggle'});
|
||||
button.attr('data-toggle', 'dropdown').dropdown();
|
||||
const html = Handlebars.templates['model.html']({name, markers, value: opts[optKey][name] || ''});
|
||||
container.append($(html));
|
||||
});
|
||||
|
||||
const markerItems = $('<ul>', {class: 'dropdown-menu dropdown-menu-right'});
|
||||
for (const marker of markers) {
|
||||
const link = $('<a>', {href: '#'}).text(`{${marker}}`);
|
||||
link.click(e => {
|
||||
tab.find('.anki-field-value').change(onOptionsChanged);
|
||||
tab.find('.marker-link').click(e => {
|
||||
e.preventDefault();
|
||||
link.closest('.input-group').find('.anki-field-value').val(link.text()).trigger('change');
|
||||
});
|
||||
markerItems.append($('<li>').append(link));
|
||||
}
|
||||
|
||||
const groupBtn = $('<div>', {class: 'input-group-btn'});
|
||||
groupBtn.append(button.append($('<span>', {class: 'caret'})));
|
||||
groupBtn.append(markerItems);
|
||||
|
||||
const group = $('<div>', {class: 'input-group'});
|
||||
group.append($('<input>', {
|
||||
type: 'text',
|
||||
class: 'anki-field-value form-control',
|
||||
value: opts[optKey][name] || ''
|
||||
}).data('field', name).change(onOptionsChanged));
|
||||
group.append(groupBtn);
|
||||
|
||||
const row = $('<tr>');
|
||||
row.append($('<td>', {class: 'col-sm-2'}).text(name));
|
||||
row.append($('<td>', {class: 'col-sm-10'}).append(group));
|
||||
|
||||
tbody.append(row);
|
||||
});
|
||||
|
||||
table.append(tbody);
|
||||
});
|
||||
}
|
||||
|
||||
function onOptionsChanged(e) {
|
||||
if (!e.originalEvent && !e.isTrigger) {
|
||||
return;
|
||||
}
|
||||
|
||||
getFormValues().then(({optsNew, optsOld}) => {
|
||||
saveOptions(optsNew).then(() => {
|
||||
yomichan().setOptions(optsNew);
|
||||
updateVisibility(optsNew);
|
||||
|
||||
const loginChanged =
|
||||
optsNew.ankiUsername !== optsOld.ankiUsername ||
|
||||
optsNew.ankiPassword !== optsOld.ankiPassword;
|
||||
|
||||
if (loginChanged && optsNew.ankiMethod === 'ankiweb') {
|
||||
anki().logout().then(() => populateAnkiDeckAndModel(optsNew)).catch(error => {
|
||||
$('#anki-error').show().find('span').text(error);
|
||||
});
|
||||
} else if (loginChanged || optsNew.ankiMethod !== optsOld.ankiMethod) {
|
||||
populateAnkiDeckAndModel(optsNew);
|
||||
}
|
||||
const link = e.target;
|
||||
$(link).closest('.input-group').find('.anki-field-value').val(`{${link.text}}`).trigger('change');
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -236,45 +405,48 @@ function onAnkiModelChanged(e) {
|
||||
return;
|
||||
}
|
||||
|
||||
showAnkiError(null);
|
||||
showAnkiSpinner(true);
|
||||
|
||||
getFormValues().then(({optsNew, optsOld}) => {
|
||||
optsNew[modelIdToFieldOptKey($(this).id)] = {};
|
||||
|
||||
const ankiSpinner = $('#anki-spinner');
|
||||
ankiSpinner.show();
|
||||
|
||||
populateAnkiFields($(this), optsNew).then(() => {
|
||||
saveOptions(optsNew).then(() => yomichan().setOptions(optsNew));
|
||||
}).catch(error => {
|
||||
$('#anki-error').show().find('span').text(error);
|
||||
showAnkiError(error);
|
||||
}).then(() => {
|
||||
$('#anki-error').hide();
|
||||
ankiSpinner.hide();
|
||||
showAnkiSpinner(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(() => {
|
||||
loadOptions().then(opts => {
|
||||
$('#activate-on-startup').prop('checked', opts.activateOnStartup);
|
||||
$('#enable-audio-playback').prop('checked', opts.enableAudioPlayback);
|
||||
$('#enable-soft-katakana-search').prop('checked', opts.enableSoftKatakanaSearch);
|
||||
$('#show-advanced-options').prop('checked', opts.showAdvancedOptions);
|
||||
function onOptionsChanged(e) {
|
||||
if (!e.originalEvent && !e.isTrigger) {
|
||||
return;
|
||||
}
|
||||
|
||||
$('#hold-shift-to-scan').prop('checked', opts.holdShiftToScan);
|
||||
$('#select-matched-text').prop('checked', opts.selectMatchedText);
|
||||
$('#scan-delay').val(opts.scanDelay);
|
||||
$('#scan-length').val(opts.scanLength);
|
||||
getFormValues().then(({optsNew, optsOld}) => {
|
||||
return saveOptions(optsNew).then(() => {
|
||||
yomichan().setOptions(optsNew);
|
||||
updateVisibility(optsNew);
|
||||
|
||||
$('#anki-method').val(opts.ankiMethod);
|
||||
$('#anki-username').val(opts.ankiUsername);
|
||||
$('#anki-password').val(opts.ankiPassword);
|
||||
$('#anki-card-tags').val(opts.ankiCardTags.join(' '));
|
||||
$('#sentence-extent').val(opts.sentenceExtent);
|
||||
const loginChanged =
|
||||
optsNew.ankiUsername !== optsOld.ankiUsername ||
|
||||
optsNew.ankiPassword !== optsOld.ankiPassword;
|
||||
|
||||
$('input, select').not('.anki-model').change(onOptionsChanged);
|
||||
$('.anki-model').change(onAnkiModelChanged);
|
||||
|
||||
populateAnkiDeckAndModel(opts);
|
||||
updateVisibility(opts);
|
||||
if (loginChanged && optsNew.ankiMethod === 'ankiweb') {
|
||||
showAnkiError(null);
|
||||
showAnkiSpinner(true);
|
||||
return anki().logout().then(() => populateAnkiDeckAndModel(optsNew));
|
||||
} else if (loginChanged || optsNew.ankiMethod !== optsOld.ankiMethod) {
|
||||
showAnkiError(null);
|
||||
showAnkiSpinner(true);
|
||||
return populateAnkiDeckAndModel(optsNew);
|
||||
}
|
||||
});
|
||||
}).catch(error => {
|
||||
showAnkiError(error);
|
||||
}).then(() => {
|
||||
showAnkiSpinner(false);
|
||||
});
|
||||
}
|
||||
|
@ -28,6 +28,8 @@ function sanitizeOptions(options) {
|
||||
scanDelay: 15,
|
||||
scanLength: 20,
|
||||
|
||||
dictionaries: {},
|
||||
|
||||
ankiMethod: 'disabled',
|
||||
ankiUsername: '',
|
||||
ankiPassword: '',
|
||||
|
@ -1,5 +1,36 @@
|
||||
(function() {
|
||||
var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||
templates['dictionary.html'] = template({"1":function(container,depth0,helpers,partials,data) {
|
||||
return "disabled";
|
||||
},"3":function(container,depth0,helpers,partials,data) {
|
||||
return "checked";
|
||||
},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||
var stack1, helper, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
|
||||
|
||||
return "<div class=\"dict-group well well-sm\" data-title=\""
|
||||
+ alias4(((helper = (helper = helpers.title || (depth0 != null ? depth0.title : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"title","hash":{},"data":data}) : helper)))
|
||||
+ "\">\n <h4><span class=\"text-muted glyphicon glyphicon-book\"></span> "
|
||||
+ alias4(((helper = (helper = helpers.title || (depth0 != null ? depth0.title : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"title","hash":{},"data":data}) : helper)))
|
||||
+ " <small>v."
|
||||
+ alias4(((helper = (helper = helpers.version || (depth0 != null ? depth0.version : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"version","hash":{},"data":data}) : helper)))
|
||||
+ "</small></h4>\n\n <!-- <div class=\"row\"> -->\n <!-- <div class=\"col-xs-8\"> -->\n <!-- <h4><span class=\"text-muted glyphicon glyphicon-book\"></span> "
|
||||
+ alias4(((helper = (helper = helpers.title || (depth0 != null ? depth0.title : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"title","hash":{},"data":data}) : helper)))
|
||||
+ " <small>v."
|
||||
+ alias4(((helper = (helper = helpers.version || (depth0 != null ? depth0.version : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"version","hash":{},"data":data}) : helper)))
|
||||
+ "</small></h4> -->\n <!-- </div> -->\n <!-- <div class=\"col-xs-4 text-right disabled\"> -->\n <!-- <button type=\"button\" class=\"dict-group-controls dict-delete btn btn-danger\">Delete</button> -->\n <!-- </div> -->\n <!-- </div> -->\n\n <div class=\"dict-delete-progress\">\n Dictionary data is being deleted, please be patient...\n <div class=\"progress\">\n <div class=\"progress-bar progress-bar-striped progress-bar-danger\" style=\"width: 0%\"></div>\n </div>\n </div>\n\n <div class=\"checkbox dict-group-controls "
|
||||
+ ((stack1 = helpers.unless.call(alias1,(depth0 != null ? depth0.hasTerms : depth0),{"name":"unless","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
|
||||
+ "\">\n <label><input type=\"checkbox\" class=\"dict-enable-terms\" "
|
||||
+ ((stack1 = helpers.unless.call(alias1,(depth0 != null ? depth0.hasTerms : depth0),{"name":"unless","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
|
||||
+ " "
|
||||
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.enableTerms : depth0),{"name":"if","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
|
||||
+ "> Enable term search</label>\n </div>\n <div class=\"checkbox dict-group-controls "
|
||||
+ ((stack1 = helpers.unless.call(alias1,(depth0 != null ? depth0.hasKanji : depth0),{"name":"unless","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
|
||||
+ "\">\n <label><input type=\"checkbox\" class=\"dict-enable-kanji\" "
|
||||
+ ((stack1 = helpers.unless.call(alias1,(depth0 != null ? depth0.hasKanji : depth0),{"name":"unless","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
|
||||
+ " "
|
||||
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.enableKanji : depth0),{"name":"if","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
|
||||
+ "> Enable Kanji search</label>\n </div>\n</div>\n";
|
||||
},"useData":true});
|
||||
templates['footer.html'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||
var helper;
|
||||
|
||||
@ -39,16 +70,28 @@ templates['kanji.html'] = template({"1":function(container,depth0,helpers,partia
|
||||
var helper, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
|
||||
|
||||
return " <span class=\"tag tag-"
|
||||
+ alias4(((helper = (helper = helpers["class"] || (depth0 != null ? depth0["class"] : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"class","hash":{},"data":data}) : helper)))
|
||||
+ alias4(((helper = (helper = helpers.category || (depth0 != null ? depth0.category : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"category","hash":{},"data":data}) : helper)))
|
||||
+ "\" title=\""
|
||||
+ alias4(((helper = (helper = helpers.desc || (depth0 != null ? depth0.desc : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"desc","hash":{},"data":data}) : helper)))
|
||||
+ alias4(((helper = (helper = helpers.notes || (depth0 != null ? depth0.notes : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"notes","hash":{},"data":data}) : helper)))
|
||||
+ "\">"
|
||||
+ alias4(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"name","hash":{},"data":data}) : helper)))
|
||||
+ "</span>\n";
|
||||
},"8":function(container,depth0,helpers,partials,data) {
|
||||
var stack1;
|
||||
|
||||
return " <ol>\n"
|
||||
+ ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.glossary : depth0),{"name":"each","hash":{},"fn":container.program(9, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
|
||||
+ " </ol>\n";
|
||||
},"9":function(container,depth0,helpers,partials,data) {
|
||||
return " <li><span>"
|
||||
+ container.escapeExpression(container.lambda(depth0, depth0))
|
||||
+ "</span></li>\n";
|
||||
},"11":function(container,depth0,helpers,partials,data) {
|
||||
var stack1;
|
||||
|
||||
return " <p>\n "
|
||||
+ container.escapeExpression(container.lambda(((stack1 = (depth0 != null ? depth0.glossary : depth0)) != null ? stack1["0"] : stack1), depth0))
|
||||
+ "\n </p>\n";
|
||||
},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||
var stack1, helper, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
|
||||
|
||||
@ -64,9 +107,9 @@ templates['kanji.html'] = template({"1":function(container,depth0,helpers,partia
|
||||
+ ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.onyomi : depth0),{"name":"each","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
|
||||
+ " </td>\n </tr>\n </table>\n </div>\n\n <div class=\"kanji-tags\">\n"
|
||||
+ ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.tags : depth0),{"name":"each","hash":{},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
|
||||
+ " </div>\n\n <div class=\"kanji-glossary\">\n <ol>\n"
|
||||
+ ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.glossary : depth0),{"name":"each","hash":{},"fn":container.program(8, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
|
||||
+ " </ol>\n </div>\n</div>\n";
|
||||
+ " </div>\n\n <div class=\"kanji-glossary\">\n"
|
||||
+ ((stack1 = helpers["if"].call(alias1,((stack1 = (depth0 != null ? depth0.glossary : depth0)) != null ? stack1["1"] : stack1),{"name":"if","hash":{},"fn":container.program(8, data, 0),"inverse":container.program(11, data, 0),"data":data})) != null ? stack1 : "")
|
||||
+ " </div>\n</div>\n";
|
||||
},"useData":true});
|
||||
templates['kanji-link.html'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||
var helper;
|
||||
@ -78,14 +121,37 @@ templates['kanji-link.html'] = template({"compiler":[7,">= 4.0.0"],"main":functi
|
||||
templates['kanji-list.html'] = template({"1":function(container,depth0,helpers,partials,data,blockParams,depths) {
|
||||
var stack1;
|
||||
|
||||
return ((stack1 = container.invokePartial(partials["kanji.html"],depth0,{"name":"kanji.html","hash":{"sequence":(depths[1] != null ? depths[1].sequence : depths[1]),"options":(depths[1] != null ? depths[1].options : depths[1]),"root":(depths[1] != null ? depths[1].root : depths[1]),"addable":(depths[1] != null ? depths[1].addable : depths[1])},"data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
|
||||
return ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.definitions : depth0),{"name":"each","hash":{},"fn":container.program(2, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "");
|
||||
},"2":function(container,depth0,helpers,partials,data,blockParams,depths) {
|
||||
var stack1;
|
||||
|
||||
return ((stack1 = container.invokePartial(partials["kanji.html"],depth0,{"name":"kanji.html","hash":{"sequence":(depths[1] != null ? depths[1].sequence : depths[1]),"options":(depths[1] != null ? depths[1].options : depths[1]),"root":(depths[1] != null ? depths[1].root : depths[1]),"addable":(depths[1] != null ? depths[1].addable : depths[1])},"data":data,"indent":" ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
|
||||
},"4":function(container,depth0,helpers,partials,data) {
|
||||
return " <p>No results found</p>\n";
|
||||
},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data,blockParams,depths) {
|
||||
var stack1;
|
||||
|
||||
return ((stack1 = container.invokePartial(partials["header.html"],depth0,{"name":"header.html","data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
|
||||
+ ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.definitions : depth0),{"name":"each","hash":{},"fn":container.program(1, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
|
||||
+ ((stack1 = helpers["if"].call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.definitions : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0, blockParams, depths),"inverse":container.program(4, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "")
|
||||
+ ((stack1 = container.invokePartial(partials["footer.html"],depth0,{"name":"footer.html","data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
|
||||
},"usePartial":true,"useData":true,"useDepths":true});
|
||||
templates['model.html'] = template({"1":function(container,depth0,helpers,partials,data) {
|
||||
return " <li><a class=\"marker-link\" href=\"#\">"
|
||||
+ container.escapeExpression(container.lambda(depth0, depth0))
|
||||
+ "</a></li>\n";
|
||||
},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||
var stack1, helper, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
|
||||
|
||||
return "<tr>\n <td class=\"col-sm-2\">"
|
||||
+ 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)))
|
||||
+ "</td>\n <td class=\"col-sm-10\">\n <div class=\"input-group\">\n <input type=\"text\" class=\"anki-field-value form-control\" data-field=\""
|
||||
+ 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)))
|
||||
+ "\" value=\""
|
||||
+ alias4(((helper = (helper = helpers.value || (depth0 != null ? depth0.value : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"value","hash":{},"data":data}) : helper)))
|
||||
+ "\">\n <div class=\"input-group-btn\">\n <button type=\"button\" class=\"btn btn-default dropdown-toggle\" data-toggle=\"dropdown\">\n <span class=\"caret\"></span>\n </button>\n <ul class=\"dropdown-menu dropdown-menu-right\">\n"
|
||||
+ ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.markers : depth0),{"name":"each","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
|
||||
+ " </ul>\n </div>\n </div>\n </td>\n</tr>\n";
|
||||
},"useData":true});
|
||||
templates['term.html'] = template({"1":function(container,depth0,helpers,partials,data) {
|
||||
var helper, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
|
||||
|
||||
@ -129,7 +195,7 @@ templates['term.html'] = template({"1":function(container,depth0,helpers,partial
|
||||
},"10":function(container,depth0,helpers,partials,data) {
|
||||
var stack1;
|
||||
|
||||
return " <span class=\"rule\">"
|
||||
return " <span class=\"reasons\">"
|
||||
+ container.escapeExpression(container.lambda(depth0, depth0))
|
||||
+ "</span> "
|
||||
+ ((stack1 = helpers.unless.call(depth0 != null ? depth0 : {},(data && data.last),{"name":"unless","hash":{},"fn":container.program(11, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
|
||||
@ -140,16 +206,28 @@ templates['term.html'] = template({"1":function(container,depth0,helpers,partial
|
||||
var helper, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
|
||||
|
||||
return " <span class=\"tag tag-"
|
||||
+ alias4(((helper = (helper = helpers["class"] || (depth0 != null ? depth0["class"] : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"class","hash":{},"data":data}) : helper)))
|
||||
+ alias4(((helper = (helper = helpers.category || (depth0 != null ? depth0.category : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"category","hash":{},"data":data}) : helper)))
|
||||
+ "\" title=\""
|
||||
+ alias4(((helper = (helper = helpers.desc || (depth0 != null ? depth0.desc : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"desc","hash":{},"data":data}) : helper)))
|
||||
+ alias4(((helper = (helper = helpers.notes || (depth0 != null ? depth0.notes : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"notes","hash":{},"data":data}) : helper)))
|
||||
+ "\">"
|
||||
+ alias4(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"name","hash":{},"data":data}) : helper)))
|
||||
+ "</span>\n";
|
||||
},"15":function(container,depth0,helpers,partials,data) {
|
||||
var stack1;
|
||||
|
||||
return " <ol>\n"
|
||||
+ ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.glossary : depth0),{"name":"each","hash":{},"fn":container.program(16, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
|
||||
+ " </ol>\n";
|
||||
},"16":function(container,depth0,helpers,partials,data) {
|
||||
return " <li><span>"
|
||||
+ container.escapeExpression(container.lambda(depth0, depth0))
|
||||
+ "</span></li>\n";
|
||||
},"18":function(container,depth0,helpers,partials,data) {
|
||||
var stack1;
|
||||
|
||||
return " <p>"
|
||||
+ container.escapeExpression(container.lambda(((stack1 = (depth0 != null ? depth0.glossary : depth0)) != null ? stack1["0"] : stack1), depth0))
|
||||
+ "</p>\n";
|
||||
},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||
var stack1, helper, alias1=depth0 != null ? depth0 : {};
|
||||
|
||||
@ -160,23 +238,29 @@ templates['term.html'] = template({"1":function(container,depth0,helpers,partial
|
||||
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.addable : depth0),{"name":"if","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
|
||||
+ " </div>\n\n"
|
||||
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.reading : depth0),{"name":"if","hash":{},"fn":container.program(5, data, 0),"inverse":container.program(8, data, 0),"data":data})) != null ? stack1 : "")
|
||||
+ "\n <div class=\"term-rules\">\n"
|
||||
+ ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.rules : depth0),{"name":"each","hash":{},"fn":container.program(10, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
|
||||
+ "\n <div class=\"term-reasons\">\n"
|
||||
+ ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.reasons : depth0),{"name":"each","hash":{},"fn":container.program(10, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
|
||||
+ " </div>\n\n <div class=\"term-tags\">\n"
|
||||
+ ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.tags : depth0),{"name":"each","hash":{},"fn":container.program(13, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
|
||||
+ " </div>\n\n <div class=\"term-glossary\">\n <ol>\n"
|
||||
+ ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.glossary : depth0),{"name":"each","hash":{},"fn":container.program(15, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
|
||||
+ " </ol>\n </div>\n</div>\n";
|
||||
+ " </div>\n\n <div class=\"term-glossary\">\n"
|
||||
+ ((stack1 = helpers["if"].call(alias1,((stack1 = (depth0 != null ? depth0.glossary : depth0)) != null ? stack1["1"] : stack1),{"name":"if","hash":{},"fn":container.program(15, data, 0),"inverse":container.program(18, data, 0),"data":data})) != null ? stack1 : "")
|
||||
+ " </div>\n</div>\n";
|
||||
},"useData":true});
|
||||
templates['term-list.html'] = template({"1":function(container,depth0,helpers,partials,data,blockParams,depths) {
|
||||
var stack1;
|
||||
|
||||
return ((stack1 = container.invokePartial(partials["term.html"],depth0,{"name":"term.html","hash":{"sequence":(depths[1] != null ? depths[1].sequence : depths[1]),"options":(depths[1] != null ? depths[1].options : depths[1]),"root":(depths[1] != null ? depths[1].root : depths[1]),"addable":(depths[1] != null ? depths[1].addable : depths[1])},"data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
|
||||
return ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.definitions : depth0),{"name":"each","hash":{},"fn":container.program(2, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "");
|
||||
},"2":function(container,depth0,helpers,partials,data,blockParams,depths) {
|
||||
var stack1;
|
||||
|
||||
return ((stack1 = container.invokePartial(partials["term.html"],depth0,{"name":"term.html","hash":{"sequence":(depths[1] != null ? depths[1].sequence : depths[1]),"options":(depths[1] != null ? depths[1].options : depths[1]),"root":(depths[1] != null ? depths[1].root : depths[1]),"addable":(depths[1] != null ? depths[1].addable : depths[1])},"data":data,"indent":" ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
|
||||
},"4":function(container,depth0,helpers,partials,data) {
|
||||
return " <p>No results found</p>\n";
|
||||
},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data,blockParams,depths) {
|
||||
var stack1;
|
||||
|
||||
return ((stack1 = container.invokePartial(partials["header.html"],depth0,{"name":"header.html","data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
|
||||
+ ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.definitions : depth0),{"name":"each","hash":{},"fn":container.program(1, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
|
||||
+ ((stack1 = helpers["if"].call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.definitions : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0, blockParams, depths),"inverse":container.program(4, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "")
|
||||
+ ((stack1 = container.invokePartial(partials["footer.html"],depth0,{"name":"footer.html","data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
|
||||
},"usePartial":true,"useData":true,"useDepths":true});
|
||||
})();
|
@ -20,96 +20,33 @@
|
||||
class Translator {
|
||||
constructor() {
|
||||
this.loaded = false;
|
||||
this.tagMeta = null;
|
||||
this.dictionary = new Dictionary();
|
||||
this.ruleMeta = null;
|
||||
this.database = new Database();
|
||||
this.deinflector = new Deinflector();
|
||||
}
|
||||
|
||||
loadData(callback) {
|
||||
prepare() {
|
||||
if (this.loaded) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return loadJson('bg/data/rules.json').then(rules => {
|
||||
this.deinflector.setRules(rules);
|
||||
return loadJson('bg/data/tags.json');
|
||||
}).then(tagMeta => {
|
||||
this.tagMeta = tagMeta;
|
||||
return this.dictionary.prepareDb();
|
||||
}).then(exists => {
|
||||
if (exists) {
|
||||
return;
|
||||
}
|
||||
const promises = [
|
||||
loadJsonInt('bg/data/deinflect.json'),
|
||||
this.database.prepare()
|
||||
];
|
||||
|
||||
if (callback) {
|
||||
callback({state: 'begin', progress: 0});
|
||||
}
|
||||
|
||||
const banks = {};
|
||||
const bankCallback = (loaded, total, indexUrl) => {
|
||||
banks[indexUrl] = {loaded, total};
|
||||
|
||||
let percent = 0.0;
|
||||
for (const url in banks) {
|
||||
percent += banks[url].loaded / banks[url].total;
|
||||
}
|
||||
|
||||
percent /= 3.0;
|
||||
|
||||
if (callback) {
|
||||
callback({state: 'update', progress: Math.ceil(100.0 * percent)});
|
||||
}
|
||||
};
|
||||
|
||||
return Promise.all([
|
||||
this.dictionary.importTermDict('bg/data/edict/index.json', bankCallback),
|
||||
this.dictionary.importTermDict('bg/data/enamdict/index.json', bankCallback),
|
||||
this.dictionary.importKanjiDict('bg/data/kanjidic/index.json', bankCallback),
|
||||
]).then(() => {
|
||||
return this.dictionary.sealDb();
|
||||
}).then(() => {
|
||||
if (callback) {
|
||||
callback({state: 'end', progress: 100.0});
|
||||
}
|
||||
});
|
||||
}).then(() => {
|
||||
return Promise.all(promises).then(([reasons]) => {
|
||||
this.deinflector.setReasons(reasons);
|
||||
this.loaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
findTermGroups(text) {
|
||||
const deinflectGroups = {};
|
||||
const deinflectPromises = [];
|
||||
|
||||
for (let i = text.length; i > 0; --i) {
|
||||
deinflectPromises.push(
|
||||
this.deinflector.deinflect(text.slice(0, i), term => {
|
||||
return this.dictionary.findTerm(term).then(definitions => definitions.map(definition => definition.tags));
|
||||
}).then(deinflects => {
|
||||
const processPromises = [];
|
||||
for (const deinflect of deinflects) {
|
||||
processPromises.push(this.processTerm(
|
||||
deinflectGroups,
|
||||
deinflect.source,
|
||||
deinflect.tags,
|
||||
deinflect.rules,
|
||||
deinflect.root
|
||||
));
|
||||
}
|
||||
|
||||
return Promise.all(processPromises);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return Promise.all(deinflectPromises).then(() => deinflectGroups);
|
||||
}
|
||||
|
||||
findTerm(text, enableSoftKatakanaSearch) {
|
||||
return this.findTermGroups(text).then(groups => {
|
||||
findTerm(text, dictionaries, enableSoftKatakanaSearch) {
|
||||
const cache = {};
|
||||
return this.findDeinflectionGroups(text, dictionaries, cache).then(groups => {
|
||||
const textHiragana = wanakana._katakanaToHiragana(text);
|
||||
if (text !== textHiragana && enableSoftKatakanaSearch) {
|
||||
return this.findTermGroups(textHiragana).then(groupsHiragana => {
|
||||
return this.findDeinflectionGroups(textHiragana, dictionaries, cache).then(groupsHiragana => {
|
||||
for (const key in groupsHiragana) {
|
||||
groups[key] = groups[key] || groupsHiragana[key];
|
||||
}
|
||||
@ -137,13 +74,11 @@ class Translator {
|
||||
});
|
||||
}
|
||||
|
||||
findKanji(text) {
|
||||
const processed = {};
|
||||
const promises = [];
|
||||
|
||||
findKanji(text, dictionaries) {
|
||||
const processed = {}, promises = [];
|
||||
for (const c of text) {
|
||||
if (!processed[c]) {
|
||||
promises.push(this.dictionary.findKanji(c).then((definitions) => definitions));
|
||||
promises.push(this.database.findKanji(c, dictionaries));
|
||||
processed[c] = true;
|
||||
}
|
||||
}
|
||||
@ -151,73 +86,53 @@ class Translator {
|
||||
return Promise.all(promises).then(sets => this.processKanji(sets.reduce((a, b) => a.concat(b), [])));
|
||||
}
|
||||
|
||||
processTerm(groups, source, tags, rules, root) {
|
||||
return this.dictionary.findTerm(root).then(definitions => {
|
||||
findDeinflectionGroups(text, dictionaries, cache) {
|
||||
const definer = term => {
|
||||
if (cache.hasOwnProperty(term)) {
|
||||
return Promise.resolve(cache[term]);
|
||||
}
|
||||
|
||||
return this.database.findTerm(term, dictionaries).then(definitions => cache[term] = definitions);
|
||||
};
|
||||
|
||||
const groups = {}, promises = [];
|
||||
for (let i = text.length; i > 0; --i) {
|
||||
promises.push(
|
||||
this.deinflector.deinflect(text.slice(0, i), definer).then(deinflections => {
|
||||
for (const deinflection of deinflections) {
|
||||
this.processDeinflection(groups, deinflection);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return Promise.all(promises).then(() => groups);
|
||||
}
|
||||
|
||||
processDeinflection(groups, {source, rules, reasons, definitions}, dictionaries) {
|
||||
for (const definition of definitions) {
|
||||
if (definition.id in groups) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let matched = tags.length === 0;
|
||||
for (const tag of tags) {
|
||||
if (definition.tags.includes(tag)) {
|
||||
matched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!matched) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const tagItems = [];
|
||||
for (const tag of definition.tags) {
|
||||
const tagItem = {
|
||||
name: tag,
|
||||
class: 'default',
|
||||
order: Number.MAX_SAFE_INTEGER,
|
||||
score: 0,
|
||||
desc: definition.entities[tag] || '',
|
||||
};
|
||||
|
||||
applyTagMeta(tagItem, this.tagMeta);
|
||||
tagItems.push(tagItem);
|
||||
}
|
||||
|
||||
let score = 0;
|
||||
for (const tagItem of tagItems) {
|
||||
score += tagItem.score;
|
||||
}
|
||||
|
||||
const tags = definition.tags.map(tag => buildTag(tag, definition.tagMeta));
|
||||
groups[definition.id] = {
|
||||
score,
|
||||
source,
|
||||
rules,
|
||||
reasons,
|
||||
score: definition.score,
|
||||
dictionary: definition.dictionary,
|
||||
expression: definition.expression,
|
||||
reading: definition.reading,
|
||||
glossary: definition.glossary,
|
||||
tags: sortTags(tagItems)
|
||||
tags: sortTags(tags)
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
processKanji(definitions) {
|
||||
for (const definition of definitions) {
|
||||
const tagItems = [];
|
||||
for (const tag of definition.tags) {
|
||||
const tagItem = {
|
||||
name: tag,
|
||||
class: 'default',
|
||||
order: Number.MAX_SAFE_INTEGER,
|
||||
desc: '',
|
||||
};
|
||||
|
||||
applyTagMeta(tagItem, this.tagMeta);
|
||||
tagItems.push(tagItem);
|
||||
}
|
||||
|
||||
definition.tags = sortTags(tagItems);
|
||||
const tags = definition.tags.map(tag => buildTag(tag, definition.tagMeta));
|
||||
definition.tags = sortTags(tags);
|
||||
}
|
||||
|
||||
return definitions;
|
||||
|
@ -39,19 +39,11 @@ function promiseCallback(promise, callback) {
|
||||
return promise.then(result => {
|
||||
callback({result});
|
||||
}).catch(error => {
|
||||
console.log(error);
|
||||
callback({error});
|
||||
});
|
||||
}
|
||||
|
||||
function loadJson(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.addEventListener('load', () => resolve(JSON.parse(xhr.responseText)));
|
||||
xhr.open('GET', chrome.extension.getURL(url));
|
||||
xhr.send();
|
||||
});
|
||||
}
|
||||
|
||||
function sortTags(tags) {
|
||||
return tags.sort((v1, v2) => {
|
||||
const order1 = v1.order;
|
||||
@ -92,8 +84,8 @@ function sortTermDefs(definitions) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const rl1 = v1.rules.length;
|
||||
const rl2 = v2.rules.length;
|
||||
const rl1 = v1.reasons.length;
|
||||
const rl2 = v2.reasons.length;
|
||||
if (rl1 < rl2) {
|
||||
return -1;
|
||||
} else if (rl1 > rl2) {
|
||||
@ -104,15 +96,97 @@ function sortTermDefs(definitions) {
|
||||
});
|
||||
}
|
||||
|
||||
function applyTagMeta(tag, meta) {
|
||||
const symbol = tag.name.split(':')[0];
|
||||
function buildTag(name, meta) {
|
||||
const tag = {name};
|
||||
const symbol = name.split(':')[0];
|
||||
for (const prop in meta[symbol] || {}) {
|
||||
tag[prop] = meta[symbol][prop];
|
||||
}
|
||||
|
||||
return sanitizeTag(tag);
|
||||
}
|
||||
|
||||
function sanitizeTag(tag) {
|
||||
tag.name = tag.name || 'untitled';
|
||||
tag.category = tag.category || 'default';
|
||||
tag.notes = tag.notes || '';
|
||||
tag.order = tag.order || 0;
|
||||
return tag;
|
||||
}
|
||||
|
||||
function splitField(field) {
|
||||
return field.length === 0 ? [] : field.split(' ');
|
||||
}
|
||||
|
||||
function loadJson(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.addEventListener('load', () => resolve(xhr.responseText));
|
||||
xhr.addEventListener('error', () => reject('failed to execute network request'));
|
||||
xhr.open('GET', url);
|
||||
xhr.send();
|
||||
}).then(responseText => {
|
||||
try {
|
||||
return JSON.parse(responseText);
|
||||
}
|
||||
catch (e) {
|
||||
return Promise.reject('invalid JSON response');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadJsonInt(url) {
|
||||
return loadJson(chrome.extension.getURL(url));
|
||||
}
|
||||
|
||||
function importJsonDb(indexUrl, indexLoaded, termsLoaded, kanjiLoaded) {
|
||||
const indexDir = indexUrl.slice(0, indexUrl.lastIndexOf('/'));
|
||||
return loadJson(indexUrl).then(index => {
|
||||
if (!index.title || !index.version) {
|
||||
return Promise.reject('unrecognized dictionary format');
|
||||
}
|
||||
|
||||
if (indexLoaded !== null) {
|
||||
return indexLoaded(
|
||||
index.title,
|
||||
index.version,
|
||||
index.tagMeta,
|
||||
index.termBanks > 0,
|
||||
index.kanjiBanks > 0
|
||||
).then(() => index);
|
||||
}
|
||||
|
||||
return index;
|
||||
}).then(index => {
|
||||
const loaders = [];
|
||||
const banksTotal = index.termBanks + index.kanjiBanks;
|
||||
let banksLoaded = 0;
|
||||
|
||||
for (let i = 1; i <= index.termBanks; ++i) {
|
||||
const bankUrl = `${indexDir}/term_bank_${i}.json`;
|
||||
loaders.push(() => loadJson(bankUrl).then(entries => termsLoaded(
|
||||
index.title,
|
||||
entries,
|
||||
banksTotal,
|
||||
banksLoaded++
|
||||
)));
|
||||
}
|
||||
|
||||
for (let i = 1; i <= index.kanjiBanks; ++i) {
|
||||
const bankUrl = `${indexDir}/kanji_bank_${i}.json`;
|
||||
loaders.push(() => loadJson(bankUrl).then(entries => kanjiLoaded(
|
||||
index.title,
|
||||
entries,
|
||||
banksTotal,
|
||||
banksLoaded++
|
||||
)));
|
||||
}
|
||||
|
||||
let chain = Promise.resolve();
|
||||
for (const loader of loaders) {
|
||||
chain = chain.then(loader);
|
||||
}
|
||||
|
||||
return chain;
|
||||
});
|
||||
}
|
||||
|
@ -25,11 +25,11 @@ class Yomichan {
|
||||
this.translator = new Translator();
|
||||
this.anki = new AnkiNull();
|
||||
this.options = null;
|
||||
this.importTabId = null;
|
||||
this.setState('disabled');
|
||||
|
||||
chrome.runtime.onMessage.addListener(this.onMessage.bind(this));
|
||||
chrome.browserAction.onClicked.addListener(this.onBrowserAction.bind(this));
|
||||
chrome.runtime.onInstalled.addListener(this.onInstalled.bind(this));
|
||||
|
||||
loadOptions().then(opts => {
|
||||
this.setOptions(opts);
|
||||
@ -39,17 +39,9 @@ class Yomichan {
|
||||
});
|
||||
}
|
||||
|
||||
onImport({state, progress}) {
|
||||
if (state === 'begin') {
|
||||
chrome.tabs.create({url: chrome.extension.getURL('bg/import.html')}, tab => this.importTabId = tab.id);
|
||||
}
|
||||
|
||||
if (this.importTabId !== null) {
|
||||
this.tabInvoke(this.importTabId, 'setProgress', progress);
|
||||
}
|
||||
|
||||
if (state === 'end') {
|
||||
this.importTabId = null;
|
||||
onInstalled(details) {
|
||||
if (details.reason === 'install') {
|
||||
chrome.tabs.create({url: chrome.extension.getURL('bg/guide.html')});
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,7 +83,7 @@ class Yomichan {
|
||||
break;
|
||||
case 'loading':
|
||||
chrome.browserAction.setBadgeText({text: '...'});
|
||||
this.translator.loadData(this.onImport.bind(this)).then(() => this.setState('enabled'));
|
||||
this.translator.prepare().then(this.setState('enabled'));
|
||||
break;
|
||||
}
|
||||
|
||||
@ -239,11 +231,31 @@ class Yomichan {
|
||||
}
|
||||
|
||||
api_findKanji({text, callback}) {
|
||||
promiseCallback(this.translator.findKanji(text), callback);
|
||||
const dictionaries = [];
|
||||
for (const title in this.options.dictionaries) {
|
||||
if (this.options.dictionaries[title].enableKanji) {
|
||||
dictionaries.push(title);
|
||||
}
|
||||
}
|
||||
|
||||
promiseCallback(
|
||||
this.translator.findKanji(text, dictionaries),
|
||||
callback
|
||||
);
|
||||
}
|
||||
|
||||
api_findTerm({text, callback}) {
|
||||
promiseCallback(this.translator.findTerm(text, this.options.enableSoftKatakanaSearch), callback);
|
||||
const dictionaries = [];
|
||||
for (const title in this.options.dictionaries) {
|
||||
if (this.options.dictionaries[title].enableTerms) {
|
||||
dictionaries.push(title);
|
||||
}
|
||||
}
|
||||
|
||||
promiseCallback(
|
||||
this.translator.findTerm(text, dictionaries, this.options.enableSoftKatakanaSearch),
|
||||
callback
|
||||
);
|
||||
}
|
||||
|
||||
api_renderText({template, data, callback}) {
|
||||
|
@ -6,7 +6,9 @@
|
||||
<link rel="stylesheet" type="text/css" href="../lib/bootstrap-3.3.6-dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="../lib/bootstrap-3.3.6-dist/css/bootstrap-theme.min.css">
|
||||
<style>
|
||||
#anki-spinner, #anki-general, #anki-error, #options-advanced {
|
||||
#anki-spinner, #anki-general, #anki-error,
|
||||
#dict-spinner, #dict-error, #dict-warning, #dict-purge-progress, #dict-import-progress, .dict-delete-progress,
|
||||
#options-advanced {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@ -62,12 +64,71 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<img src="img/spinner.gif" class="pull-right" id="dict-spinner" alt>
|
||||
<h3>Dictionaries</h3>
|
||||
</div>
|
||||
|
||||
<p class="help-block">
|
||||
Yomichan can utilize both bundled and custom (see the <a href="https://foosoft.net/projects/yomichan-import">Yomichan Import</a>
|
||||
page for details) dictionaries. You can disable dictionaries that you no longer wish to use, or you can simply
|
||||
<a href="#" id="dict-purge">purge the database</a> to delete everything. Please make sure to wait for import
|
||||
and delete operations to complete before closing this page.
|
||||
</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, use the importer below to install packaged and external dictionaries</span>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-danger" id="dict-error">
|
||||
<strong>Error:</strong>
|
||||
<span></span>
|
||||
</div>
|
||||
|
||||
<div id="dict-groups"></div>
|
||||
|
||||
<div id="dict-import-progress">
|
||||
Dictionary data is being imported, please be patient...
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-bar-striped" style="width: 0%"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="input-group" id="dict-importer">
|
||||
<div class="input-group-btn">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown"><span class="caret"></span></button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="#" data-url="edict">JMdict</a></li>
|
||||
<li><a href="#" data-url="enamdict">JMnedict</a></li>
|
||||
<li><a href="#" data-url="kanjidic">KANJIDIC2</a></li>
|
||||
<li role="separator" class="divider"></li>
|
||||
<li><a href="#" data-url="http://localhost:9876/index.json">Local dictionary</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<input type="text" id="dict-url" class="form-control" placeholder="Dictionary import URL">
|
||||
<div class="input-group-btn">
|
||||
<button type="button" id="dict-import" class="btn btn-primary" disabled>Import</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<img src="img/spinner.gif" class="pull-right" id="anki-spinner" alt>
|
||||
<h3>Anki Options</h3>
|
||||
</div>
|
||||
|
||||
<p class="help-block">
|
||||
Yomichan features automatic flashcard creation for <a href="http://ankisrs.net/">Anki</a>, a free application
|
||||
designed to help you retain knowledge. While the <a href="https://foosoft.net/projects/anki-connect/">AnkiConnect</a> plugin
|
||||
offers the best experience, it is also possible to create flashcards through <a href="https://ankiweb.net/">AnkiWeb</a>,
|
||||
provided you already have an account.
|
||||
</p>
|
||||
|
||||
<div class="alert alert-danger" id="anki-error">
|
||||
<strong>Error:</strong>
|
||||
<span></span>
|
||||
@ -80,12 +141,6 @@
|
||||
<option value="ankiconnect">AnkiConnect (requires the AnkiConnect plugin)</option>
|
||||
<option value="ankiweb">AnkiWeb (requires an account on AnkiWeb)</option>
|
||||
</select>
|
||||
<p class="help-block">
|
||||
Yomichan features automatic flashcard creation for <a href="http://ankisrs.net/">Anki</a>, a free application
|
||||
designed to help you retain knowledge. While the <a href="https://foosoft.net/projects/anki-connect/">AnkiConnect</a> plugin
|
||||
offers the best experience, it is also possible to create flashcards through <a href="https://ankiweb.net/">AnkiWeb</a>,
|
||||
provided you already have an account.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div id="anki-general">
|
||||
@ -158,6 +213,19 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3>Support Development</h3>
|
||||
|
||||
<p class="help-block">
|
||||
If you find Yomichan useful, please consider making a small donation as a way to show your appreciation for the countless hours
|
||||
that I have devoted to this extension. Seeing that people care about my work is great motivation for continuing to
|
||||
improve Yomichan!
|
||||
</p>
|
||||
<p>
|
||||
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=4DBTN9A3CUAFN"><img src="img/paypal.gif" alt></a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pull-right">
|
||||
@ -167,6 +235,8 @@
|
||||
|
||||
<script src="../lib/jquery-2.2.2.min.js"></script>
|
||||
<script src="../lib/bootstrap-3.3.6-dist/js/bootstrap.min.js"></script>
|
||||
<script src="../lib/handlebars.min.js"></script>
|
||||
<script src="js/templates.js"></script>
|
||||
<script src="js/options.js"></script>
|
||||
<script src="js/options-form.js"></script>
|
||||
</body>
|
||||
|
@ -100,7 +100,7 @@ body {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.term-rules {
|
||||
.term-reasons {
|
||||
color: #777;
|
||||
display: inline-block;
|
||||
}
|
||||
@ -117,6 +117,11 @@ body {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.term-glossary p {
|
||||
overflow-x: auto;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
/* kanji styles */
|
||||
|
||||
.kanji-glyph {
|
||||
@ -153,3 +158,8 @@ body {
|
||||
.kanji-glossary li span {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.kanji-glossary p {
|
||||
overflow-x: auto;
|
||||
white-space: pre;
|
||||
}
|
||||
|
5
ext/lib/dexie.min.js
vendored
5
ext/lib/dexie.min.js
vendored
File diff suppressed because one or more lines are too long
@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"name": "Yomichan",
|
||||
"version": "0.997",
|
||||
"version": "0.998",
|
||||
|
||||
"description": "Japanese dictionary with Anki integration",
|
||||
"icons": {"16": "img/icon16.png", "48": "img/icon48.png", "128": "img/icon128.png"},
|
||||
|
26
tmpl/dictionary.html
Normal file
26
tmpl/dictionary.html
Normal file
@ -0,0 +1,26 @@
|
||||
<div class="dict-group well well-sm" data-title="{{title}}">
|
||||
<h4><span class="text-muted glyphicon glyphicon-book"></span> {{title}} <small>v.{{version}}</small></h4>
|
||||
|
||||
<!-- <div class="row"> -->
|
||||
<!-- <div class="col-xs-8"> -->
|
||||
<!-- <h4><span class="text-muted glyphicon glyphicon-book"></span> {{title}} <small>v.{{version}}</small></h4> -->
|
||||
<!-- </div> -->
|
||||
<!-- <div class="col-xs-4 text-right disabled"> -->
|
||||
<!-- <button type="button" class="dict-group-controls dict-delete btn btn-danger">Delete</button> -->
|
||||
<!-- </div> -->
|
||||
<!-- </div> -->
|
||||
|
||||
<div class="dict-delete-progress">
|
||||
Dictionary data is being deleted, please be patient...
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-bar-striped progress-bar-danger" style="width: 0%"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="checkbox dict-group-controls {{#unless hasTerms}}disabled{{/unless}}">
|
||||
<label><input type="checkbox" class="dict-enable-terms" {{#unless hasTerms}}disabled{{/unless}} {{#if enableTerms}}checked{{/if}}> Enable term search</label>
|
||||
</div>
|
||||
<div class="checkbox dict-group-controls {{#unless hasKanji}}disabled{{/unless}}">
|
||||
<label><input type="checkbox" class="dict-enable-kanji" {{#unless hasKanji}}disabled{{/unless}} {{#if enableKanji}}checked{{/if}}> Enable Kanji search</label>
|
||||
</div>
|
||||
</div>
|
@ -1,5 +1,9 @@
|
||||
{{> header.html}}
|
||||
{{#if definitions}}
|
||||
{{#each definitions}}
|
||||
{{> kanji.html addable=../addable root=../root options=../options sequence=../sequence}}
|
||||
{{/each}}
|
||||
{{else}}
|
||||
<p>No results found</p>
|
||||
{{/if}}
|
||||
{{> footer.html}}
|
||||
|
@ -30,15 +30,21 @@
|
||||
|
||||
<div class="kanji-tags">
|
||||
{{#each tags}}
|
||||
<span class="tag tag-{{class}}" title="{{desc}}">{{name}}</span>
|
||||
<span class="tag tag-{{category}}" title="{{notes}}">{{name}}</span>
|
||||
{{/each}}
|
||||
</div>
|
||||
|
||||
<div class="kanji-glossary">
|
||||
{{#if glossary.[1]}}
|
||||
<ol>
|
||||
{{#each glossary}}
|
||||
<li><span>{{.}}</span></li>
|
||||
{{/each}}
|
||||
</ol>
|
||||
{{else}}
|
||||
<p>
|
||||
{{glossary.[0]}}
|
||||
</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
18
tmpl/model.html
Normal file
18
tmpl/model.html
Normal file
@ -0,0 +1,18 @@
|
||||
<tr>
|
||||
<td class="col-sm-2">{{name}}</td>
|
||||
<td class="col-sm-10">
|
||||
<div class="input-group">
|
||||
<input type="text" class="anki-field-value form-control" data-field="{{name}}" value="{{value}}">
|
||||
<div class="input-group-btn">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right">
|
||||
{{#each markers}}
|
||||
<li><a class="marker-link" href="#">{{.}}</a></li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
@ -1,5 +1,9 @@
|
||||
{{> header.html}}
|
||||
{{#if definitions}}
|
||||
{{#each definitions}}
|
||||
{{> term.html addable=../addable root=../root options=../options sequence=../sequence}}
|
||||
{{/each}}
|
||||
{{else}}
|
||||
<p>No results found</p>
|
||||
{{/if}}
|
||||
{{> footer.html}}
|
||||
|
@ -15,23 +15,27 @@
|
||||
<div class="term-expression">{{#kanjiLinks}}{{expression}}{{/kanjiLinks}}</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="term-rules">
|
||||
{{#each rules}}
|
||||
<span class="rule">{{.}}</span> {{#unless @last}}«{{/unless}}
|
||||
<div class="term-reasons">
|
||||
{{#each reasons}}
|
||||
<span class="reasons">{{.}}</span> {{#unless @last}}«{{/unless}}
|
||||
{{/each}}
|
||||
</div>
|
||||
|
||||
<div class="term-tags">
|
||||
{{#each tags}}
|
||||
<span class="tag tag-{{class}}" title="{{desc}}">{{name}}</span>
|
||||
<span class="tag tag-{{category}}" title="{{notes}}">{{name}}</span>
|
||||
{{/each}}
|
||||
</div>
|
||||
|
||||
<div class="term-glossary">
|
||||
{{#if glossary.[1]}}
|
||||
<ol>
|
||||
{{#each glossary}}
|
||||
<li><span>{{.}}</span></li>
|
||||
{{/each}}
|
||||
</ol>
|
||||
{{else}}
|
||||
<p>{{glossary.[0]}}</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user