Merge branch 'dev'
This commit is contained in:
commit
39fa11f72b
@ -9,7 +9,7 @@
|
|||||||
<script src="js/ankinull.js"></script>
|
<script src="js/ankinull.js"></script>
|
||||||
<script src="js/templates.js"></script>
|
<script src="js/templates.js"></script>
|
||||||
<script src="js/util.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/deinflector.js"></script>
|
||||||
<script src="js/translator.js"></script>
|
<script src="js/translator.js"></script>
|
||||||
<script src="js/options.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">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<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.min.css">
|
||||||
<link rel="stylesheet" type="text/css" href="../lib/bootstrap-3.3.6-dist/css/bootstrap-theme.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>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<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>
|
<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>
|
<div>
|
||||||
<h2>Quick Guide</h2>
|
<h2>Quick Guide</h2>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Please read the steps outlined below to get quickly get up and running with Yomichan. For complete documentation,
|
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>
|
</p>
|
||||||
|
|
||||||
<ol>
|
<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>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>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>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>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>
|
<li>Click on Kanji in the definition window to view additional information about that character.</li>
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="../lib/jquery-2.2.2.min.js"></script>
|
|
||||||
<script src="js/import.js"></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</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 {
|
class Deinflection {
|
||||||
constructor(term, tags=[], rule='') {
|
constructor(term, {rules=[], definitions=[], reason=''} = {}) {
|
||||||
this.children = [];
|
|
||||||
this.term = term;
|
this.term = term;
|
||||||
this.tags = tags;
|
this.rules = rules;
|
||||||
this.rule = rule;
|
this.definitions = definitions;
|
||||||
|
this.reason = reason;
|
||||||
|
this.children = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
validate(validator) {
|
deinflect(definer, reasons) {
|
||||||
return validator(this.term).then(sets => {
|
const define = () => {
|
||||||
for (const tags of sets) {
|
return definer(this.term).then(definitions => {
|
||||||
if (this.tags.length === 0) {
|
if (this.rules.length === 0) {
|
||||||
return true;
|
this.definitions = definitions;
|
||||||
}
|
} else {
|
||||||
|
for (const rule of this.rules) {
|
||||||
for (const tag of this.tags) {
|
for (const definition of definitions) {
|
||||||
if (tags.includes(tag)) {
|
if (definition.rules.includes(rule)) {
|
||||||
return true;
|
this.definitions.push(definition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!allowed || !this.term.endsWith(variant.kanaIn)) {
|
return this.definitions.length > 0;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
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 (!accept || !this.term.endsWith(variant.kanaIn)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,55 +67,61 @@ class Deinflection {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const child = new Deinflection(term, variant.tagsOut, rule);
|
const child = new Deinflection(term, {reason, rules: variant.rulesOut});
|
||||||
promises.push(
|
promises.push(
|
||||||
child.deinflect(validator, rules).then(valid => {
|
child.deinflect(definer, reasons).then(valid => valid && this.children.push(child))
|
||||||
if (valid) {
|
);
|
||||||
this.children.push(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all(promises).then(() => {
|
return Promise.all(promises).then(define).then(valid => {
|
||||||
return this.children.length > 0;
|
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() {
|
gather() {
|
||||||
if (this.children.length === 0) {
|
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 child of this.children) {
|
||||||
for (const path of child.gather()) {
|
for (const result of child.gather()) {
|
||||||
if (this.rule.length > 0) {
|
if (this.reason.length > 0) {
|
||||||
path.rules.push(this.rule);
|
result.reasons.push(this.reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
path.source = this.term;
|
result.source = this.term;
|
||||||
paths.push(path);
|
results.push(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return paths;
|
return results;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class Deinflector {
|
class Deinflector {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.rules = {};
|
this.reasons = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
setRules(rules) {
|
setReasons(reasons) {
|
||||||
this.rules = rules;
|
this.reasons = reasons;
|
||||||
}
|
}
|
||||||
|
|
||||||
deinflect(term, validator) {
|
deinflect(term, definer) {
|
||||||
const node = new Deinflection(term);
|
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/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
//
|
||||||
|
// General
|
||||||
|
//
|
||||||
|
|
||||||
function yomichan() {
|
function yomichan() {
|
||||||
return chrome.extension.getBackgroundPage().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() {
|
function anki() {
|
||||||
return yomichan().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) {
|
function fieldsToDict(selection) {
|
||||||
const result = {};
|
const result = {};
|
||||||
selection.each((index, element) => {
|
selection.each((index, element) => {
|
||||||
@ -65,98 +341,40 @@ function modelIdToMarkers(id) {
|
|||||||
}[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) {
|
function populateAnkiDeckAndModel(opts) {
|
||||||
const ankiSpinner = $('#anki-spinner');
|
showAnkiError(null);
|
||||||
ankiSpinner.show();
|
showAnkiSpinner(true);
|
||||||
|
|
||||||
const ankiFormat = $('#anki-format');
|
const ankiFormat = $('#anki-format').hide();
|
||||||
ankiFormat.hide();
|
|
||||||
|
|
||||||
const ankiDeck = $('.anki-deck');
|
return Promise.all([anki().getDeckNames(), anki().getModelNames()]).then(([deckNames, modelNames]) => {
|
||||||
ankiDeck.find('option').remove();
|
const ankiDeck = $('.anki-deck');
|
||||||
|
ankiDeck.find('option').remove();
|
||||||
|
deckNames.forEach(name => ankiDeck.append($('<option/>', {value: name, text: name})));
|
||||||
|
|
||||||
const ankiModel = $('.anki-model');
|
|
||||||
ankiModel.find('option').remove();
|
|
||||||
|
|
||||||
return anki().getDeckNames().then(names => {
|
|
||||||
names.forEach(name => ankiDeck.append($('<option/>', {value: name, text: name})));
|
|
||||||
$('#anki-term-deck').val(opts.ankiTermDeck);
|
$('#anki-term-deck').val(opts.ankiTermDeck);
|
||||||
$('#anki-kanji-deck').val(opts.ankiKanjiDeck);
|
$('#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 Promise.all([
|
||||||
|
populateAnkiFields($('#anki-term-model').val(opts.ankiTermModel), opts),
|
||||||
|
populateAnkiFields($('#anki-kanji-model').val(opts.ankiKanjiModel), opts)
|
||||||
|
]);
|
||||||
}).then(() => {
|
}).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();
|
ankiFormat.show();
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
$('#anki-error').show().find('span').text(error);
|
showAnkiError(error);
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
ankiSpinner.hide();
|
showAnkiSpinner(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function populateAnkiFields(element, opts) {
|
function populateAnkiFields(element, opts) {
|
||||||
const table = element.closest('.tab-pane').find('.anki-fields');
|
const tab = element.closest('.tab-pane');
|
||||||
table.find('tbody').remove();
|
const container = tab.find('tbody').empty();
|
||||||
|
|
||||||
const modelName = element.val();
|
const modelName = element.val();
|
||||||
if (modelName === null) {
|
if (modelName === null) {
|
||||||
@ -168,65 +386,16 @@ function populateAnkiFields(element, opts) {
|
|||||||
const markers = modelIdToMarkers(modelId);
|
const markers = modelIdToMarkers(modelId);
|
||||||
|
|
||||||
return anki().getModelFieldNames(modelName).then(names => {
|
return anki().getModelFieldNames(modelName).then(names => {
|
||||||
const tbody = $('<tbody>');
|
|
||||||
names.forEach(name => {
|
names.forEach(name => {
|
||||||
const button = $('<button>', {type: 'button', class: 'btn btn-default dropdown-toggle'});
|
const html = Handlebars.templates['model.html']({name, markers, value: opts[optKey][name] || ''});
|
||||||
button.attr('data-toggle', 'dropdown').dropdown();
|
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 => {
|
|
||||||
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);
|
tab.find('.anki-field-value').change(onOptionsChanged);
|
||||||
});
|
tab.find('.marker-link').click(e => {
|
||||||
}
|
e.preventDefault();
|
||||||
|
const link = e.target;
|
||||||
function onOptionsChanged(e) {
|
$(link).closest('.input-group').find('.anki-field-value').val(`{${link.text}}`).trigger('change');
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -236,45 +405,48 @@ function onAnkiModelChanged(e) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showAnkiError(null);
|
||||||
|
showAnkiSpinner(true);
|
||||||
|
|
||||||
getFormValues().then(({optsNew, optsOld}) => {
|
getFormValues().then(({optsNew, optsOld}) => {
|
||||||
optsNew[modelIdToFieldOptKey($(this).id)] = {};
|
optsNew[modelIdToFieldOptKey($(this).id)] = {};
|
||||||
|
|
||||||
const ankiSpinner = $('#anki-spinner');
|
|
||||||
ankiSpinner.show();
|
|
||||||
|
|
||||||
populateAnkiFields($(this), optsNew).then(() => {
|
populateAnkiFields($(this), optsNew).then(() => {
|
||||||
saveOptions(optsNew).then(() => yomichan().setOptions(optsNew));
|
saveOptions(optsNew).then(() => yomichan().setOptions(optsNew));
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
$('#anki-error').show().find('span').text(error);
|
showAnkiError(error);
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
$('#anki-error').hide();
|
showAnkiSpinner(false);
|
||||||
ankiSpinner.hide();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(() => {
|
function onOptionsChanged(e) {
|
||||||
loadOptions().then(opts => {
|
if (!e.originalEvent && !e.isTrigger) {
|
||||||
$('#activate-on-startup').prop('checked', opts.activateOnStartup);
|
return;
|
||||||
$('#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);
|
getFormValues().then(({optsNew, optsOld}) => {
|
||||||
$('#select-matched-text').prop('checked', opts.selectMatchedText);
|
return saveOptions(optsNew).then(() => {
|
||||||
$('#scan-delay').val(opts.scanDelay);
|
yomichan().setOptions(optsNew);
|
||||||
$('#scan-length').val(opts.scanLength);
|
updateVisibility(optsNew);
|
||||||
|
|
||||||
$('#anki-method').val(opts.ankiMethod);
|
const loginChanged =
|
||||||
$('#anki-username').val(opts.ankiUsername);
|
optsNew.ankiUsername !== optsOld.ankiUsername ||
|
||||||
$('#anki-password').val(opts.ankiPassword);
|
optsNew.ankiPassword !== optsOld.ankiPassword;
|
||||||
$('#anki-card-tags').val(opts.ankiCardTags.join(' '));
|
|
||||||
$('#sentence-extent').val(opts.sentenceExtent);
|
|
||||||
|
|
||||||
$('input, select').not('.anki-model').change(onOptionsChanged);
|
if (loginChanged && optsNew.ankiMethod === 'ankiweb') {
|
||||||
$('.anki-model').change(onAnkiModelChanged);
|
showAnkiError(null);
|
||||||
|
showAnkiSpinner(true);
|
||||||
populateAnkiDeckAndModel(opts);
|
return anki().logout().then(() => populateAnkiDeckAndModel(optsNew));
|
||||||
updateVisibility(opts);
|
} 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,
|
scanDelay: 15,
|
||||||
scanLength: 20,
|
scanLength: 20,
|
||||||
|
|
||||||
|
dictionaries: {},
|
||||||
|
|
||||||
ankiMethod: 'disabled',
|
ankiMethod: 'disabled',
|
||||||
ankiUsername: '',
|
ankiUsername: '',
|
||||||
ankiPassword: '',
|
ankiPassword: '',
|
||||||
|
@ -1,5 +1,36 @@
|
|||||||
(function() {
|
(function() {
|
||||||
var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
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) {
|
templates['footer.html'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
var helper;
|
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;
|
var helper, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
|
||||||
|
|
||||||
return " <span class=\"tag tag-"
|
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=\""
|
+ "\" 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)))
|
+ 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";
|
+ "</span>\n";
|
||||||
},"8":function(container,depth0,helpers,partials,data) {
|
},"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>"
|
return " <li><span>"
|
||||||
+ container.escapeExpression(container.lambda(depth0, depth0))
|
+ container.escapeExpression(container.lambda(depth0, depth0))
|
||||||
+ "</span></li>\n";
|
+ "</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) {
|
},"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;
|
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 : "")
|
+ ((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"
|
+ " </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 : "")
|
+ ((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"
|
+ " </div>\n\n <div class=\"kanji-glossary\">\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 : "")
|
+ ((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 : "")
|
||||||
+ " </ol>\n </div>\n</div>\n";
|
+ " </div>\n</div>\n";
|
||||||
},"useData":true});
|
},"useData":true});
|
||||||
templates['kanji-link.html'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
|
templates['kanji-link.html'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
var helper;
|
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) {
|
templates['kanji-list.html'] = template({"1":function(container,depth0,helpers,partials,data,blockParams,depths) {
|
||||||
var stack1;
|
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) {
|
},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data,blockParams,depths) {
|
||||||
var stack1;
|
var stack1;
|
||||||
|
|
||||||
return ((stack1 = container.invokePartial(partials["header.html"],depth0,{"name":"header.html","data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? 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 : "");
|
+ ((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});
|
},"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) {
|
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;
|
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) {
|
},"10":function(container,depth0,helpers,partials,data) {
|
||||||
var stack1;
|
var stack1;
|
||||||
|
|
||||||
return " <span class=\"rule\">"
|
return " <span class=\"reasons\">"
|
||||||
+ container.escapeExpression(container.lambda(depth0, depth0))
|
+ container.escapeExpression(container.lambda(depth0, depth0))
|
||||||
+ "</span> "
|
+ "</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 : "")
|
+ ((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;
|
var helper, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
|
||||||
|
|
||||||
return " <span class=\"tag tag-"
|
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=\""
|
+ "\" 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)))
|
+ 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";
|
+ "</span>\n";
|
||||||
},"15":function(container,depth0,helpers,partials,data) {
|
},"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>"
|
return " <li><span>"
|
||||||
+ container.escapeExpression(container.lambda(depth0, depth0))
|
+ container.escapeExpression(container.lambda(depth0, depth0))
|
||||||
+ "</span></li>\n";
|
+ "</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) {
|
},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
var stack1, helper, alias1=depth0 != null ? depth0 : {};
|
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 : "")
|
+ ((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"
|
+ " </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 : "")
|
+ ((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"
|
+ "\n <div class=\"term-reasons\">\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 : "")
|
+ ((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"
|
+ " </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 : "")
|
+ ((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"
|
+ " </div>\n\n <div class=\"term-glossary\">\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 : "")
|
+ ((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 : "")
|
||||||
+ " </ol>\n </div>\n</div>\n";
|
+ " </div>\n</div>\n";
|
||||||
},"useData":true});
|
},"useData":true});
|
||||||
templates['term-list.html'] = template({"1":function(container,depth0,helpers,partials,data,blockParams,depths) {
|
templates['term-list.html'] = template({"1":function(container,depth0,helpers,partials,data,blockParams,depths) {
|
||||||
var stack1;
|
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) {
|
},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data,blockParams,depths) {
|
||||||
var stack1;
|
var stack1;
|
||||||
|
|
||||||
return ((stack1 = container.invokePartial(partials["header.html"],depth0,{"name":"header.html","data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? 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 : "");
|
+ ((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});
|
},"usePartial":true,"useData":true,"useDepths":true});
|
||||||
})();
|
})();
|
@ -20,96 +20,33 @@
|
|||||||
class Translator {
|
class Translator {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.loaded = false;
|
this.loaded = false;
|
||||||
this.tagMeta = null;
|
this.ruleMeta = null;
|
||||||
this.dictionary = new Dictionary();
|
this.database = new Database();
|
||||||
this.deinflector = new Deinflector();
|
this.deinflector = new Deinflector();
|
||||||
}
|
}
|
||||||
|
|
||||||
loadData(callback) {
|
prepare() {
|
||||||
if (this.loaded) {
|
if (this.loaded) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
return loadJson('bg/data/rules.json').then(rules => {
|
const promises = [
|
||||||
this.deinflector.setRules(rules);
|
loadJsonInt('bg/data/deinflect.json'),
|
||||||
return loadJson('bg/data/tags.json');
|
this.database.prepare()
|
||||||
}).then(tagMeta => {
|
];
|
||||||
this.tagMeta = tagMeta;
|
|
||||||
return this.dictionary.prepareDb();
|
|
||||||
}).then(exists => {
|
|
||||||
if (exists) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (callback) {
|
return Promise.all(promises).then(([reasons]) => {
|
||||||
callback({state: 'begin', progress: 0});
|
this.deinflector.setReasons(reasons);
|
||||||
}
|
|
||||||
|
|
||||||
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(() => {
|
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
findTermGroups(text) {
|
findTerm(text, dictionaries, enableSoftKatakanaSearch) {
|
||||||
const deinflectGroups = {};
|
const cache = {};
|
||||||
const deinflectPromises = [];
|
return this.findDeinflectionGroups(text, dictionaries, cache).then(groups => {
|
||||||
|
|
||||||
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 => {
|
|
||||||
const textHiragana = wanakana._katakanaToHiragana(text);
|
const textHiragana = wanakana._katakanaToHiragana(text);
|
||||||
if (text !== textHiragana && enableSoftKatakanaSearch) {
|
if (text !== textHiragana && enableSoftKatakanaSearch) {
|
||||||
return this.findTermGroups(textHiragana).then(groupsHiragana => {
|
return this.findDeinflectionGroups(textHiragana, dictionaries, cache).then(groupsHiragana => {
|
||||||
for (const key in groupsHiragana) {
|
for (const key in groupsHiragana) {
|
||||||
groups[key] = groups[key] || groupsHiragana[key];
|
groups[key] = groups[key] || groupsHiragana[key];
|
||||||
}
|
}
|
||||||
@ -137,13 +74,11 @@ class Translator {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
findKanji(text) {
|
findKanji(text, dictionaries) {
|
||||||
const processed = {};
|
const processed = {}, promises = [];
|
||||||
const promises = [];
|
|
||||||
|
|
||||||
for (const c of text) {
|
for (const c of text) {
|
||||||
if (!processed[c]) {
|
if (!processed[c]) {
|
||||||
promises.push(this.dictionary.findKanji(c).then((definitions) => definitions));
|
promises.push(this.database.findKanji(c, dictionaries));
|
||||||
processed[c] = true;
|
processed[c] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -151,73 +86,53 @@ class Translator {
|
|||||||
return Promise.all(promises).then(sets => this.processKanji(sets.reduce((a, b) => a.concat(b), [])));
|
return Promise.all(promises).then(sets => this.processKanji(sets.reduce((a, b) => a.concat(b), [])));
|
||||||
}
|
}
|
||||||
|
|
||||||
processTerm(groups, source, tags, rules, root) {
|
findDeinflectionGroups(text, dictionaries, cache) {
|
||||||
return this.dictionary.findTerm(root).then(definitions => {
|
const definer = term => {
|
||||||
for (const definition of definitions) {
|
if (cache.hasOwnProperty(term)) {
|
||||||
if (definition.id in groups) {
|
return Promise.resolve(cache[term]);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
groups[definition.id] = {
|
|
||||||
score,
|
|
||||||
source,
|
|
||||||
rules,
|
|
||||||
expression: definition.expression,
|
|
||||||
reading: definition.reading,
|
|
||||||
glossary: definition.glossary,
|
|
||||||
tags: sortTags(tagItems)
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tags = definition.tags.map(tag => buildTag(tag, definition.tagMeta));
|
||||||
|
groups[definition.id] = {
|
||||||
|
source,
|
||||||
|
reasons,
|
||||||
|
score: definition.score,
|
||||||
|
dictionary: definition.dictionary,
|
||||||
|
expression: definition.expression,
|
||||||
|
reading: definition.reading,
|
||||||
|
glossary: definition.glossary,
|
||||||
|
tags: sortTags(tags)
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
processKanji(definitions) {
|
processKanji(definitions) {
|
||||||
for (const definition of definitions) {
|
for (const definition of definitions) {
|
||||||
const tagItems = [];
|
const tags = definition.tags.map(tag => buildTag(tag, definition.tagMeta));
|
||||||
for (const tag of definition.tags) {
|
definition.tags = sortTags(tags);
|
||||||
const tagItem = {
|
|
||||||
name: tag,
|
|
||||||
class: 'default',
|
|
||||||
order: Number.MAX_SAFE_INTEGER,
|
|
||||||
desc: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
applyTagMeta(tagItem, this.tagMeta);
|
|
||||||
tagItems.push(tagItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
definition.tags = sortTags(tagItems);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return definitions;
|
return definitions;
|
||||||
|
@ -39,19 +39,11 @@ function promiseCallback(promise, callback) {
|
|||||||
return promise.then(result => {
|
return promise.then(result => {
|
||||||
callback({result});
|
callback({result});
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
|
console.log(error);
|
||||||
callback({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) {
|
function sortTags(tags) {
|
||||||
return tags.sort((v1, v2) => {
|
return tags.sort((v1, v2) => {
|
||||||
const order1 = v1.order;
|
const order1 = v1.order;
|
||||||
@ -92,8 +84,8 @@ function sortTermDefs(definitions) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const rl1 = v1.rules.length;
|
const rl1 = v1.reasons.length;
|
||||||
const rl2 = v2.rules.length;
|
const rl2 = v2.reasons.length;
|
||||||
if (rl1 < rl2) {
|
if (rl1 < rl2) {
|
||||||
return -1;
|
return -1;
|
||||||
} else if (rl1 > rl2) {
|
} else if (rl1 > rl2) {
|
||||||
@ -104,15 +96,97 @@ function sortTermDefs(definitions) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyTagMeta(tag, meta) {
|
function buildTag(name, meta) {
|
||||||
const symbol = tag.name.split(':')[0];
|
const tag = {name};
|
||||||
|
const symbol = name.split(':')[0];
|
||||||
for (const prop in meta[symbol] || {}) {
|
for (const prop in meta[symbol] || {}) {
|
||||||
tag[prop] = meta[symbol][prop];
|
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;
|
return tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
function splitField(field) {
|
function splitField(field) {
|
||||||
return field.length === 0 ? [] : field.split(' ');
|
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.translator = new Translator();
|
||||||
this.anki = new AnkiNull();
|
this.anki = new AnkiNull();
|
||||||
this.options = null;
|
this.options = null;
|
||||||
this.importTabId = null;
|
|
||||||
this.setState('disabled');
|
this.setState('disabled');
|
||||||
|
|
||||||
chrome.runtime.onMessage.addListener(this.onMessage.bind(this));
|
chrome.runtime.onMessage.addListener(this.onMessage.bind(this));
|
||||||
chrome.browserAction.onClicked.addListener(this.onBrowserAction.bind(this));
|
chrome.browserAction.onClicked.addListener(this.onBrowserAction.bind(this));
|
||||||
|
chrome.runtime.onInstalled.addListener(this.onInstalled.bind(this));
|
||||||
|
|
||||||
loadOptions().then(opts => {
|
loadOptions().then(opts => {
|
||||||
this.setOptions(opts);
|
this.setOptions(opts);
|
||||||
@ -39,17 +39,9 @@ class Yomichan {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onImport({state, progress}) {
|
onInstalled(details) {
|
||||||
if (state === 'begin') {
|
if (details.reason === 'install') {
|
||||||
chrome.tabs.create({url: chrome.extension.getURL('bg/import.html')}, tab => this.importTabId = tab.id);
|
chrome.tabs.create({url: chrome.extension.getURL('bg/guide.html')});
|
||||||
}
|
|
||||||
|
|
||||||
if (this.importTabId !== null) {
|
|
||||||
this.tabInvoke(this.importTabId, 'setProgress', progress);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state === 'end') {
|
|
||||||
this.importTabId = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,7 +83,7 @@ class Yomichan {
|
|||||||
break;
|
break;
|
||||||
case 'loading':
|
case 'loading':
|
||||||
chrome.browserAction.setBadgeText({text: '...'});
|
chrome.browserAction.setBadgeText({text: '...'});
|
||||||
this.translator.loadData(this.onImport.bind(this)).then(() => this.setState('enabled'));
|
this.translator.prepare().then(this.setState('enabled'));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,11 +231,31 @@ class Yomichan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
api_findKanji({text, callback}) {
|
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}) {
|
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}) {
|
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.min.css">
|
||||||
<link rel="stylesheet" type="text/css" href="../lib/bootstrap-3.3.6-dist/css/bootstrap-theme.min.css">
|
<link rel="stylesheet" type="text/css" href="../lib/bootstrap-3.3.6-dist/css/bootstrap-theme.min.css">
|
||||||
<style>
|
<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;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,12 +64,71 @@
|
|||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
<div>
|
<div>
|
||||||
<img src="img/spinner.gif" class="pull-right" id="anki-spinner" alt>
|
<img src="img/spinner.gif" class="pull-right" id="anki-spinner" alt>
|
||||||
<h3>Anki Options</h3>
|
<h3>Anki Options</h3>
|
||||||
</div>
|
</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">
|
<div class="alert alert-danger" id="anki-error">
|
||||||
<strong>Error:</strong>
|
<strong>Error:</strong>
|
||||||
<span></span>
|
<span></span>
|
||||||
@ -80,12 +141,6 @@
|
|||||||
<option value="ankiconnect">AnkiConnect (requires the AnkiConnect plugin)</option>
|
<option value="ankiconnect">AnkiConnect (requires the AnkiConnect plugin)</option>
|
||||||
<option value="ankiweb">AnkiWeb (requires an account on AnkiWeb)</option>
|
<option value="ankiweb">AnkiWeb (requires an account on AnkiWeb)</option>
|
||||||
</select>
|
</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>
|
||||||
|
|
||||||
<div id="anki-general">
|
<div id="anki-general">
|
||||||
@ -158,6 +213,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
@ -167,6 +235,8 @@
|
|||||||
|
|
||||||
<script src="../lib/jquery-2.2.2.min.js"></script>
|
<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/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.js"></script>
|
||||||
<script src="js/options-form.js"></script>
|
<script src="js/options-form.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
@ -100,7 +100,7 @@ body {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.term-rules {
|
.term-reasons {
|
||||||
color: #777;
|
color: #777;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
@ -117,6 +117,11 @@ body {
|
|||||||
color: #000;
|
color: #000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.term-glossary p {
|
||||||
|
overflow-x: auto;
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
|
||||||
/* kanji styles */
|
/* kanji styles */
|
||||||
|
|
||||||
.kanji-glyph {
|
.kanji-glyph {
|
||||||
@ -153,3 +158,8 @@ body {
|
|||||||
.kanji-glossary li span {
|
.kanji-glossary li span {
|
||||||
color: #000;
|
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,
|
"manifest_version": 2,
|
||||||
"name": "Yomichan",
|
"name": "Yomichan",
|
||||||
"version": "0.997",
|
"version": "0.998",
|
||||||
|
|
||||||
"description": "Japanese dictionary with Anki integration",
|
"description": "Japanese dictionary with Anki integration",
|
||||||
"icons": {"16": "img/icon16.png", "48": "img/icon48.png", "128": "img/icon128.png"},
|
"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}}
|
{{> header.html}}
|
||||||
{{#each definitions}}
|
{{#if definitions}}
|
||||||
{{> kanji.html addable=../addable root=../root options=../options sequence=../sequence}}
|
{{#each definitions}}
|
||||||
{{/each}}
|
{{> kanji.html addable=../addable root=../root options=../options sequence=../sequence}}
|
||||||
|
{{/each}}
|
||||||
|
{{else}}
|
||||||
|
<p>No results found</p>
|
||||||
|
{{/if}}
|
||||||
{{> footer.html}}
|
{{> footer.html}}
|
||||||
|
@ -30,15 +30,21 @@
|
|||||||
|
|
||||||
<div class="kanji-tags">
|
<div class="kanji-tags">
|
||||||
{{#each tags}}
|
{{#each tags}}
|
||||||
<span class="tag tag-{{class}}" title="{{desc}}">{{name}}</span>
|
<span class="tag tag-{{category}}" title="{{notes}}">{{name}}</span>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="kanji-glossary">
|
<div class="kanji-glossary">
|
||||||
|
{{#if glossary.[1]}}
|
||||||
<ol>
|
<ol>
|
||||||
{{#each glossary}}
|
{{#each glossary}}
|
||||||
<li><span>{{.}}</span></li>
|
<li><span>{{.}}</span></li>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</ol>
|
</ol>
|
||||||
|
{{else}}
|
||||||
|
<p>
|
||||||
|
{{glossary.[0]}}
|
||||||
|
</p>
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</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}}
|
{{> header.html}}
|
||||||
{{#each definitions}}
|
{{#if definitions}}
|
||||||
{{> term.html addable=../addable root=../root options=../options sequence=../sequence}}
|
{{#each definitions}}
|
||||||
{{/each}}
|
{{> term.html addable=../addable root=../root options=../options sequence=../sequence}}
|
||||||
|
{{/each}}
|
||||||
|
{{else}}
|
||||||
|
<p>No results found</p>
|
||||||
|
{{/if}}
|
||||||
{{> footer.html}}
|
{{> footer.html}}
|
||||||
|
@ -15,23 +15,27 @@
|
|||||||
<div class="term-expression">{{#kanjiLinks}}{{expression}}{{/kanjiLinks}}</div>
|
<div class="term-expression">{{#kanjiLinks}}{{expression}}{{/kanjiLinks}}</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<div class="term-rules">
|
<div class="term-reasons">
|
||||||
{{#each rules}}
|
{{#each reasons}}
|
||||||
<span class="rule">{{.}}</span> {{#unless @last}}«{{/unless}}
|
<span class="reasons">{{.}}</span> {{#unless @last}}«{{/unless}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="term-tags">
|
<div class="term-tags">
|
||||||
{{#each tags}}
|
{{#each tags}}
|
||||||
<span class="tag tag-{{class}}" title="{{desc}}">{{name}}</span>
|
<span class="tag tag-{{category}}" title="{{notes}}">{{name}}</span>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="term-glossary">
|
<div class="term-glossary">
|
||||||
|
{{#if glossary.[1]}}
|
||||||
<ol>
|
<ol>
|
||||||
{{#each glossary}}
|
{{#each glossary}}
|
||||||
<li><span>{{.}}</span></li>
|
<li><span>{{.}}</span></li>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</ol>
|
</ol>
|
||||||
|
{{else}}
|
||||||
|
<p>{{glossary.[0]}}</p>
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user