aboutsummaryrefslogtreecommitdiff
path: root/ext
diff options
context:
space:
mode:
Diffstat (limited to 'ext')
-rw-r--r--ext/bg/js/ankiweb.js180
-rw-r--r--ext/bg/js/options-form.js7
-rw-r--r--ext/bg/js/options.js14
-rw-r--r--ext/bg/js/templates.js355
-rw-r--r--ext/bg/js/translator.js21
-rw-r--r--ext/bg/js/util.js154
-rw-r--r--ext/bg/js/yomichan.js96
-rw-r--r--ext/bg/options.html4
-rw-r--r--ext/fg/css/frame.css69
-rw-r--r--ext/fg/frame.html19
-rw-r--r--ext/fg/img/spinner.gifbin0 -> 7358 bytes
-rw-r--r--ext/fg/js/driver.js228
-rw-r--r--ext/fg/js/frame.js168
-rw-r--r--ext/fg/js/popup.js63
-rw-r--r--ext/fg/js/util.js22
-rw-r--r--ext/manifest.json2
16 files changed, 861 insertions, 541 deletions
diff --git a/ext/bg/js/ankiweb.js b/ext/bg/js/ankiweb.js
new file mode 100644
index 00000000..69a1b44d
--- /dev/null
+++ b/ext/bg/js/ankiweb.js
@@ -0,0 +1,180 @@
+/*
+ * 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 AnkiWeb {
+ constructor(username, password) {
+ this.username = username;
+ this.password = password;
+ this.noteInfo = null;
+
+ chrome.webRequest.onBeforeSendHeaders.addListener(
+ details => {
+ details.requestHeaders.push({name: 'Origin', value: 'https://ankiweb.net'});
+ return {requestHeaders: details.requestHeaders};
+ },
+ {urls: ['https://ankiweb.net/*']},
+ ['blocking', 'requestHeaders']
+ );
+ }
+
+ addNote(note) {
+ return this.retrieve().then(info => {
+ const model = info.models.find(m => m.name === note.modelName);
+ if (!model) {
+ return Promise.reject('cannot add note model provided');
+ }
+
+ const fields = [];
+ for (const field of model.fields) {
+ fields.push(note.fields[field]);
+ }
+
+ const data = {
+ data: JSON.stringify([fields, note.tags.join(' ')]),
+ mid: model.id,
+ deck: note.deckName,
+ csrf_token: info.token
+ };
+
+ return AnkiWeb.loadAccountPage('https://ankiweb.net/edit/save', data, this.username, this.password);
+ }).then(response => response !== '0');
+ }
+
+ canAddNotes(notes) {
+ return Promise.resolve(new Array(notes.length).fill(true));
+ }
+
+ getDeckNames() {
+ return this.retrieve().then(info => info.deckNames);
+ }
+
+ getModelNames() {
+ return this.retrieve().then(info => info.models.map(m => m.name));
+ }
+
+ getModelFieldNames(modelName) {
+ return this.retrieve().then(info => {
+ const model = info.models.find(m => m.name === modelName);
+ return model ? model.fields : [];
+ });
+ }
+
+ retrieve() {
+ if (this.noteInfo !== null) {
+ return Promise.resolve(this.noteInfo);
+ }
+
+ return AnkiWeb.scrape(this.username, this.password).then(({deckNames, models, token}) => {
+ this.noteInfo = {deckNames, models, token};
+ return this.noteInfo;
+ });
+ }
+
+ logout() {
+ return AnkiWeb.loadPage('https://ankiweb.net/account/logout', null);
+ }
+
+ static scrape(username, password) {
+ return AnkiWeb.loadAccountPage('https://ankiweb.net/edit/', null, username, password).then(response => {
+ const modelsMatch = /editor\.models = (.*}]);/.exec(response);
+ if (modelsMatch === null) {
+ return Promise.reject('failed to scrape model data');
+ }
+
+ const decksMatch = /editor\.decks = (.*}});/.exec(response);
+ if (decksMatch === null) {
+ return Promise.reject('failed to scrape deck data');
+ }
+
+ const tokenMatch = /editor\.csrf_token = \'(.*)\';/.exec(response);
+ if (tokenMatch === null) {
+ return Promise.reject('failed to acquire csrf_token');
+ }
+
+ const modelsJson = JSON.parse(modelsMatch[1]);
+ const decksJson = JSON.parse(decksMatch[1]);
+ const token = tokenMatch[1];
+
+ const deckNames = Object.keys(decksJson).map(d => decksJson[d].name);
+ const models = [];
+ for (const modelJson of modelsJson) {
+ models.push({
+ name: modelJson.name,
+ id: modelJson.id,
+ fields: modelJson.flds.map(f => f.name)
+ });
+ }
+
+ return {deckNames, models, token};
+ });
+ }
+
+ static login(username, password, token) {
+ if (username.length === 0 || password.length === 0) {
+ return Promise.reject('login credentials not specified');
+ }
+
+ const data = {username, password, csrf_token: token, submitted: 1};
+ return AnkiWeb.loadPage('https://ankiweb.net/account/login', data).then(response => {
+ if (!response.includes('class="mitem"')) {
+ return Promise.reject('failed to authenticate');
+ }
+ });
+ }
+
+ static loadAccountPage(url, data, username, password) {
+ return AnkiWeb.loadPage(url, data).then(response => {
+ if (response.includes('name="password"')) {
+ const tokenMatch = /name="csrf_token" value="(.*)"/.exec(response);
+ if (tokenMatch === null) {
+ return Promise.reject('failed to acquire csrf_token');
+ }
+
+ return AnkiWeb.login(username, password, tokenMatch[1]).then(() => AnkiWeb.loadPage(url, data));
+ } else {
+ return response;
+ }
+ });
+ }
+
+ static loadPage(url, data) {
+ return new Promise((resolve, reject) => {
+ let dataEnc = null;
+ if (data) {
+ const params = [];
+ for (const key in data) {
+ params.push(`${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`);
+ }
+
+ dataEnc = params.join('&');
+ }
+
+ const xhr = new XMLHttpRequest();
+ xhr.addEventListener('error', () => reject('failed to execute network request'));
+ xhr.addEventListener('load', () => resolve(xhr.responseText));
+ if (dataEnc) {
+ xhr.open('POST', url);
+ xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+ xhr.send(dataEnc);
+ } else {
+ xhr.open('GET', url);
+ xhr.send();
+ }
+ });
+ }
+}
diff --git a/ext/bg/js/options-form.js b/ext/bg/js/options-form.js
index 8216f158..8cffb2f7 100644
--- a/ext/bg/js/options-form.js
+++ b/ext/bg/js/options-form.js
@@ -30,8 +30,9 @@ function getFormValues() {
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.groupTermResults = $('#group-term-results').prop('checked');
+ optsNew.showAdvancedOptions = $('#show-advanced-options').prop('checked');
optsNew.holdShiftToScan = $('#hold-shift-to-scan').prop('checked');
optsNew.selectMatchedText = $('#select-matched-text').prop('checked');
@@ -87,6 +88,7 @@ $(document).ready(() => {
$('#activate-on-startup').prop('checked', opts.activateOnStartup);
$('#enable-audio-playback').prop('checked', opts.enableAudioPlayback);
$('#enable-soft-katakana-search').prop('checked', opts.enableSoftKatakanaSearch);
+ $('#group-term-results').prop('checked', opts.groupTermResults);
$('#show-advanced-options').prop('checked', opts.showAdvancedOptions);
$('#hold-shift-to-scan').prop('checked', opts.holdShiftToScan);
@@ -313,6 +315,7 @@ function modelIdToMarkers(id) {
return {
'anki-term-model': [
'audio',
+ 'dictionary',
'expression',
'expression-furigana',
'glossary',
@@ -324,6 +327,7 @@ function modelIdToMarkers(id) {
],
'anki-kanji-model': [
'character',
+ 'dictionary',
'glossary',
'glossary-list',
'kunyomi',
@@ -338,7 +342,6 @@ function populateAnkiDeckAndModel(opts) {
showAnkiSpinner(true);
const ankiFormat = $('#anki-format').hide();
-
return Promise.all([anki().getDeckNames(), anki().getModelNames()]).then(([deckNames, modelNames]) => {
const ankiDeck = $('.anki-deck');
ankiDeck.find('option').remove();
diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js
index f1fe0dac..2f0bd189 100644
--- a/ext/bg/js/options.js
+++ b/ext/bg/js/options.js
@@ -17,11 +17,25 @@
*/
+function versionOptions(options) {
+ const version = options.version || 0;
+ const fixups = [
+ () => {}
+ ];
+
+ if (version < fixups.length) {
+ fixups[version]();
+ ++options.version;
+ versionOptions(options);
+ }
+}
+
function sanitizeOptions(options) {
const defaults = {
activateOnStartup: true,
enableAudioPlayback: true,
enableSoftKatakanaSearch: true,
+ groupTermResults: true,
showAdvancedOptions: false,
selectMatchedText: true,
holdShiftToScan: true,
diff --git a/ext/bg/js/templates.js b/ext/bg/js/templates.js
index f6ff8467..89719a24 100644
--- a/ext/bg/js/templates.js
+++ b/ext/bg/js/templates.js
@@ -31,112 +31,104 @@ templates['dictionary.html'] = template({"1":function(container,depth0,helpers,p
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.enableKanji : depth0),{"name":"if","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "> Enable Kanji search</label>\n </div>\n</div>\n";
},"useData":true});
-templates['footer.html'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
- var helper;
-
- return " <script src=\""
- + container.escapeExpression(((helper = (helper = helpers.root || (depth0 != null ? depth0.root : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : {},{"name":"root","hash":{},"data":data}) : helper)))
- + "/js/frame.js\"></script>\n </body>\n</html>\n";
-},"useData":true});
-templates['header.html'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
- var helper, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
+templates['kanji-list.html'] = template({"1":function(container,depth0,helpers,partials,data) {
+ var stack1, helper, alias1=depth0 != null ? depth0 : {};
- return "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\">\n <title></title>\n <style>\n @font-face {\n font-family: kanji-stroke-orders;\n src: url('"
- + alias4(((helper = (helper = helpers.root || (depth0 != null ? depth0.root : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"root","hash":{},"data":data}) : helper)))
- + "/ttf/kanji-stroke-orders.ttf');\n }\n @font-face {\n font-family: vl-gothic-regular;\n src: url('"
- + alias4(((helper = (helper = helpers.root || (depth0 != null ? depth0.root : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"root","hash":{},"data":data}) : helper)))
- + "/ttf/vl-gothic-regular.ttf');\n }\n </style>\n <link rel=\"stylesheet\" href=\""
- + alias4(((helper = (helper = helpers.root || (depth0 != null ? depth0.root : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"root","hash":{},"data":data}) : helper)))
- + "/css/frame.css\">\n </head>\n <body>\n";
-},"useData":true});
-templates['kanji-link.html'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
+ return "<div class=\"kanji-definition\">\n <div class=\"action-bar\">\n"
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.addable : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + " </div>\n\n <div class=\"kanji-glyph\">"
+ + container.escapeExpression(((helper = (helper = helpers.character || (depth0 != null ? depth0.character : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(alias1,{"name":"character","hash":{},"data":data}) : helper)))
+ + "</div>\n\n <div class=\"kanji-reading\">\n <table>\n <tr>\n <th>Kunyomi:</th>\n <td>\n"
+ + ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.kunyomi : depth0),{"name":"each","hash":{},"fn":container.program(4, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + " </td>\n </tr>\n <tr>\n <th>Onyomi:</th>\n <td>\n"
+ + ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.onyomi : depth0),{"name":"each","hash":{},"fn":container.program(4, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + " </td>\n </tr>\n </table>\n </div>\n\n <div class=\"kanji-tags\">\n"
+ + ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.tags : depth0),{"name":"each","hash":{},"fn":container.program(7, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + " </div>\n\n <div class=\"kanji-glossary\">\n"
+ + ((stack1 = helpers["if"].call(alias1,((stack1 = (depth0 != null ? depth0.glossary : depth0)) != null ? stack1["1"] : stack1),{"name":"if","hash":{},"fn":container.program(9, data, 0),"inverse":container.program(13, data, 0),"data":data})) != null ? stack1 : "")
+ + " </div>\n</div>\n";
+},"2":function(container,depth0,helpers,partials,data) {
var helper;
- return "<a href=\"#\" class=\"kanji-link\">"
- + container.escapeExpression(((helper = (helper = helpers.kanji || (depth0 != null ? depth0.kanji : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : {},{"name":"kanji","hash":{},"data":data}) : helper)))
- + "</a>\n";
-},"useData":true});
-templates['kanji-list.html'] = template({"1":function(container,depth0,helpers,partials,data,blockParams,depths) {
- var 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 : "");
+ return " <a href=\"#\" title=\"Add Kanji\" class=\"action-add-note pending disabled\" data-mode=\"kanji\" data-index=\""
+ + container.escapeExpression(((helper = (helper = helpers.index || (data && data.index)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : {},{"name":"index","hash":{},"data":data}) : helper)))
+ + "\"><img src=\"img/add_kanji.png\"></a>\n";
},"4":function(container,depth0,helpers,partials,data) {
- return " <p>No results found</p>\n";
-},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data,blockParams,depths) {
- var stack1;
-
- return ((stack1 = container.invokePartial(partials["header.html"],depth0,{"name":"header.html","data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
- + ((stack1 = helpers["if"].call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.definitions : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0, blockParams, depths),"inverse":container.program(4, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "")
- + ((stack1 = container.invokePartial(partials["footer.html"],depth0,{"name":"footer.html","data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
-},"usePartial":true,"useData":true,"useDepths":true});
-templates['kanji.html'] = template({"1":function(container,depth0,helpers,partials,data) {
- var helper, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
-
- return " <a href=\"#\" title=\"Add Kanji\" class=\"action-add-note disabled\" data-mode=\"kanji\" data-index=\""
- + alias4(((helper = (helper = helpers.index || (data && data.index)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"index","hash":{},"data":data}) : helper)))
- + "\"><img src=\""
- + alias4(((helper = (helper = helpers.root || (depth0 != null ? depth0.root : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"root","hash":{},"data":data}) : helper)))
- + "/img/add_kanji.png\"></a>\n";
-},"3":function(container,depth0,helpers,partials,data) {
var stack1;
- return " "
+ return " "
+ container.escapeExpression(container.lambda(depth0, depth0))
- + ((stack1 = helpers.unless.call(depth0 != null ? depth0 : {},(data && data.last),{"name":"unless","hash":{},"fn":container.program(4, 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(5, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "\n";
-},"4":function(container,depth0,helpers,partials,data) {
+},"5":function(container,depth0,helpers,partials,data) {
return ", ";
-},"6":function(container,depth0,helpers,partials,data) {
+},"7":function(container,depth0,helpers,partials,data) {
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.category || (depth0 != null ? depth0.category : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"category","hash":{},"data":data}) : helper)))
+ "\" title=\""
+ alias4(((helper = (helper = helpers.notes || (depth0 != null ? depth0.notes : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"notes","hash":{},"data":data}) : helper)))
+ "\">"
+ alias4(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"name","hash":{},"data":data}) : helper)))
+ "</span>\n";
-},"8":function(container,depth0,helpers,partials,data) {
+},"9":function(container,depth0,helpers,partials,data) {
var stack1;
- return " <ol>\n"
- + ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.glossary : depth0),{"name":"each","hash":{},"fn":container.program(9, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + " </ol>\n";
-},"9":function(container,depth0,helpers,partials,data) {
- return " <li><span>"
- + container.escapeExpression(container.lambda(depth0, depth0))
- + "</span></li>\n";
+ return " <ol \"kanji-glossary-group\">\n"
+ + ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.glossary : depth0),{"name":"each","hash":{},"fn":container.program(10, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + " </ol>\n";
+},"10":function(container,depth0,helpers,partials,data) {
+ var stack1, helper, options, buffer =
+ " <li><span class=\"kanji-glossary-item\">";
+ stack1 = ((helper = (helper = helpers.multiLine || (depth0 != null ? depth0.multiLine : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"multiLine","hash":{},"fn":container.program(11, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : {},options) : helper));
+ if (!helpers.multiLine) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
+ if (stack1 != null) { buffer += stack1; }
+ return buffer + "</span></li>\n";
},"11":function(container,depth0,helpers,partials,data) {
+ return container.escapeExpression(container.lambda(depth0, depth0));
+},"13":function(container,depth0,helpers,partials,data) {
+ var stack1, helper, options, buffer =
+ " <div class=\"kanji-glossary-group kanji-glossary-item\">";
+ stack1 = ((helper = (helper = helpers.multiLine || (depth0 != null ? depth0.multiLine : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"multiLine","hash":{},"fn":container.program(14, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : {},options) : helper));
+ if (!helpers.multiLine) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
+ if (stack1 != null) { buffer += stack1; }
+ return buffer + "</div>\n";
+},"14":function(container,depth0,helpers,partials,data) {
var stack1;
- return " <p>\n "
- + container.escapeExpression(container.lambda(((stack1 = (depth0 != null ? depth0.glossary : depth0)) != null ? stack1["0"] : stack1), depth0))
- + "\n </p>\n";
-},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
- var stack1, helper, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
+ return container.escapeExpression(container.lambda(((stack1 = (depth0 != null ? depth0.glossary : depth0)) != null ? stack1["0"] : stack1), depth0));
+},"16":function(container,depth0,helpers,partials,data,blockParams,depths) {
+ var stack1;
- return "<div class=\"kanji-definition\">\n <div class=\"action-bar\" data-sequence=\""
- + alias4(((helper = (helper = helpers.sequence || (depth0 != null ? depth0.sequence : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"sequence","hash":{},"data":data}) : helper)))
- + "\">\n"
- + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.addable : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + " </div>\n\n <div class=\"kanji-glyph\">"
- + alias4(((helper = (helper = helpers.character || (depth0 != null ? depth0.character : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"character","hash":{},"data":data}) : helper)))
- + "</div>\n\n <div class=\"kanji-reading\">\n <table>\n <tr>\n <th>Kunyomi:</th>\n <td>\n"
- + ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.kunyomi : depth0),{"name":"each","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + " </td>\n </tr>\n <tr>\n <th>Onyomi:</th>\n <td>\n"
- + ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.onyomi : depth0),{"name":"each","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + " </td>\n </tr>\n </table>\n </div>\n\n <div class=\"kanji-tags\">\n"
- + ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.tags : depth0),{"name":"each","hash":{},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + " </div>\n\n <div class=\"kanji-glossary\">\n"
- + ((stack1 = helpers["if"].call(alias1,((stack1 = (depth0 != null ? depth0.glossary : depth0)) != null ? stack1["1"] : stack1),{"name":"if","hash":{},"fn":container.program(8, data, 0),"inverse":container.program(11, data, 0),"data":data})) != null ? stack1 : "")
- + " </div>\n</div>\n";
-},"useData":true});
+ return ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.definitions : depth0),{"name":"each","hash":{},"fn":container.program(17, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "");
+},"17":function(container,depth0,helpers,partials,data,blockParams,depths) {
+ var stack1;
+
+ return " "
+ + ((stack1 = helpers.unless.call(depth0 != null ? depth0 : {},(data && data.first),{"name":"unless","hash":{},"fn":container.program(18, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + "\n"
+ + ((stack1 = container.invokePartial(partials.kanji,depth0,{"name":"kanji","hash":{"root":(depths[1] != null ? depths[1].root : depths[1]),"addable":(depths[1] != null ? depths[1].addable : depths[1])},"data":data,"indent":" ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
+},"18":function(container,depth0,helpers,partials,data) {
+ return "<hr>";
+},"20":function(container,depth0,helpers,partials,data) {
+ return " <p>No results found</p>\n";
+},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data,blockParams,depths) {
+ var stack1;
+
+ return "\n"
+ + ((stack1 = helpers["if"].call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.definitions : depth0),{"name":"if","hash":{},"fn":container.program(16, data, 0, blockParams, depths),"inverse":container.program(20, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "");
+},"main_d": function(fn, props, container, depth0, data, blockParams, depths) {
+
+ var decorators = container.decorators;
+
+ fn = decorators.inline(fn,props,container,{"name":"inline","hash":{},"fn":container.program(1, data, 0, blockParams, depths),"inverse":container.noop,"args":["kanji"],"data":data}) || fn;
+ return fn;
+ }
+
+,"useDecorators":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=\"#\">"
+ 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) {
@@ -152,115 +144,168 @@ templates['model.html'] = template({"1":function(container,depth0,helpers,partia
+ ((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-list.html'] = template({"1":function(container,depth0,helpers,partials,data,blockParams,depths) {
+templates['term-list.html'] = template({"1":function(container,depth0,helpers,partials,data) {
+ var stack1, alias1=depth0 != null ? depth0 : {};
+
+ return ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.tags : depth0),{"name":"if","hash":{},"fn":container.program(2, 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(5, data, 0),"inverse":container.program(9, data, 0),"data":data})) != null ? stack1 : "");
+},"2":function(container,depth0,helpers,partials,data) {
var 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) {
+ return " <div class=\"term-tags\">\n"
+ + ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.tags : depth0),{"name":"each","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + " </div>\n";
+},"3":function(container,depth0,helpers,partials,data) {
+ var helper, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
+
+ return " <span class=\"tag tag-"
+ + alias4(((helper = (helper = helpers.category || (depth0 != null ? depth0.category : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"category","hash":{},"data":data}) : helper)))
+ + "\" title=\""
+ + alias4(((helper = (helper = helpers.notes || (depth0 != null ? depth0.notes : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"notes","hash":{},"data":data}) : helper)))
+ + "\">"
+ + alias4(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"name","hash":{},"data":data}) : helper)))
+ + "</span>\n";
+},"5":function(container,depth0,helpers,partials,data) {
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) {
+ return " <ul class=\"term-glossary-group\">\n"
+ + ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.glossary : depth0),{"name":"each","hash":{},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + " </ul>\n";
+},"6":function(container,depth0,helpers,partials,data) {
+ var stack1, helper, options, buffer =
+ " <li><span class=\"term-glossary-item\">";
+ stack1 = ((helper = (helper = helpers.multiLine || (depth0 != null ? depth0.multiLine : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"multiLine","hash":{},"fn":container.program(7, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : {},options) : helper));
+ if (!helpers.multiLine) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
+ if (stack1 != null) { buffer += stack1; }
+ return buffer + "</span></li>\n";
+},"7":function(container,depth0,helpers,partials,data) {
+ return container.escapeExpression(container.lambda(depth0, depth0));
+},"9":function(container,depth0,helpers,partials,data) {
+ var stack1, helper, options, buffer =
+ " <div class=\"term-glossary-group term-glossary-item\">";
+ stack1 = ((helper = (helper = helpers.multiLine || (depth0 != null ? depth0.multiLine : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"multiLine","hash":{},"fn":container.program(10, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : {},options) : helper));
+ if (!helpers.multiLine) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
+ if (stack1 != null) { buffer += stack1; }
+ return buffer + "</div>\n";
+},"10":function(container,depth0,helpers,partials,data) {
var stack1;
- return ((stack1 = container.invokePartial(partials["header.html"],depth0,{"name":"header.html","data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
- + ((stack1 = helpers["if"].call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.definitions : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0, blockParams, depths),"inverse":container.program(4, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "")
- + ((stack1 = container.invokePartial(partials["footer.html"],depth0,{"name":"footer.html","data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
-},"usePartial":true,"useData":true,"useDepths":true});
-templates['term.html'] = template({"1":function(container,depth0,helpers,partials,data) {
- var helper, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
+ return container.escapeExpression(container.lambda(((stack1 = (depth0 != null ? depth0.glossary : depth0)) != null ? stack1["0"] : stack1), depth0));
+},"12":function(container,depth0,helpers,partials,data) {
+ var stack1, alias1=depth0 != null ? depth0 : {};
- return " <a href=\"#\" title=\"Play audio\" class=\"action-play-audio\" data-index=\""
- + alias4(((helper = (helper = helpers.index || (data && data.index)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"index","hash":{},"data":data}) : helper)))
- + "\"><img src=\""
- + alias4(((helper = (helper = helpers.root || (depth0 != null ? depth0.root : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"root","hash":{},"data":data}) : helper)))
- + "/img/play_audio.png\"></a>\n";
-},"3":function(container,depth0,helpers,partials,data) {
+ return "<div class=\"term-definition\">\n <div class=\"action-bar\">\n"
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.addable : depth0),{"name":"if","hash":{},"fn":container.program(13, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.playback : depth0),{"name":"if","hash":{},"fn":container.program(15, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + " </div>\n\n"
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.reading : depth0),{"name":"if","hash":{},"fn":container.program(17, data, 0),"inverse":container.program(20, data, 0),"data":data})) != null ? stack1 : "")
+ + "\n"
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.reasons : depth0),{"name":"if","hash":{},"fn":container.program(22, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + "\n <div class=\"term-glossary\">\n"
+ + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.grouped : depth0),{"name":"if","hash":{},"fn":container.program(26, data, 0),"inverse":container.program(32, data, 0),"data":data})) != null ? stack1 : "")
+ + " </div>\n</div>\n";
+},"13":function(container,depth0,helpers,partials,data) {
var helper, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
- return " <a href=\"#\" title=\"Add term as expression\" class=\"action-add-note disabled\" data-mode=\"term_kanji\" data-index=\""
+ return " <a href=\"#\" title=\"Add term as expression\" class=\"action-add-note pending disabled\" data-mode=\"term_kanji\" data-index=\""
+ alias4(((helper = (helper = helpers.index || (data && data.index)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"index","hash":{},"data":data}) : helper)))
- + "\"><img src=\""
- + alias4(((helper = (helper = helpers.root || (depth0 != null ? depth0.root : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"root","hash":{},"data":data}) : helper)))
- + "/img/add_term_kanji.png\"></a>\n <a href=\"#\" title=\"Add term as reading\" class=\"action-add-note disabled\" data-mode=\"term_kana\" data-index=\""
+ + "\"><img src=\"img/add_term_kanji.png\"></a>\n <a href=\"#\" title=\"Add term as reading\" class=\"action-add-note pending disabled\" data-mode=\"term_kana\" data-index=\""
+ alias4(((helper = (helper = helpers.index || (data && data.index)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"index","hash":{},"data":data}) : helper)))
- + "\"><img src=\""
- + alias4(((helper = (helper = helpers.root || (depth0 != null ? depth0.root : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"root","hash":{},"data":data}) : helper)))
- + "/img/add_term_kana.png\"></a>\n";
-},"5":function(container,depth0,helpers,partials,data) {
+ + "\"><img src=\"img/add_term_kana.png\"></a>\n";
+},"15":function(container,depth0,helpers,partials,data) {
+ var helper;
+
+ return " <a href=\"#\" title=\"Play audio\" class=\"action-play-audio\" data-index=\""
+ + container.escapeExpression(((helper = (helper = helpers.index || (data && data.index)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : {},{"name":"index","hash":{},"data":data}) : helper)))
+ + "\"><img src=\"img/play_audio.png\"></a>\n";
+},"17":function(container,depth0,helpers,partials,data) {
var stack1, helper, options, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function", buffer =
- " <div class=\"term-expression\"><ruby>";
- stack1 = ((helper = (helper = helpers.kanjiLinks || (depth0 != null ? depth0.kanjiLinks : depth0)) != null ? helper : alias2),(options={"name":"kanjiLinks","hash":{},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data}),(typeof helper === alias3 ? helper.call(alias1,options) : helper));
+ " <div class=\"term-expression\"><ruby>";
+ stack1 = ((helper = (helper = helpers.kanjiLinks || (depth0 != null ? depth0.kanjiLinks : depth0)) != null ? helper : alias2),(options={"name":"kanjiLinks","hash":{},"fn":container.program(18, data, 0),"inverse":container.noop,"data":data}),(typeof helper === alias3 ? helper.call(alias1,options) : helper));
if (!helpers.kanjiLinks) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
if (stack1 != null) { buffer += stack1; }
return buffer + "<rt>"
+ container.escapeExpression(((helper = (helper = helpers.reading || (depth0 != null ? depth0.reading : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"reading","hash":{},"data":data}) : helper)))
+ "</rt></ruby></div>\n";
-},"6":function(container,depth0,helpers,partials,data) {
+},"18":function(container,depth0,helpers,partials,data) {
var helper;
return container.escapeExpression(((helper = (helper = helpers.expression || (depth0 != null ? depth0.expression : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : {},{"name":"expression","hash":{},"data":data}) : helper)));
-},"8":function(container,depth0,helpers,partials,data) {
+},"20":function(container,depth0,helpers,partials,data) {
var stack1, helper, options, buffer =
- " <div class=\"term-expression\">";
- stack1 = ((helper = (helper = helpers.kanjiLinks || (depth0 != null ? depth0.kanjiLinks : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"kanjiLinks","hash":{},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : {},options) : helper));
+ " <div class=\"term-expression\">";
+ stack1 = ((helper = (helper = helpers.kanjiLinks || (depth0 != null ? depth0.kanjiLinks : depth0)) != null ? helper : helpers.helperMissing),(options={"name":"kanjiLinks","hash":{},"fn":container.program(18, data, 0),"inverse":container.noop,"data":data}),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : {},options) : helper));
if (!helpers.kanjiLinks) { stack1 = helpers.blockHelperMissing.call(depth0,stack1,options)}
if (stack1 != null) { buffer += stack1; }
return buffer + "</div>\n";
-},"10":function(container,depth0,helpers,partials,data) {
+},"22":function(container,depth0,helpers,partials,data) {
+ var stack1;
+
+ return " <div class=\"term-reasons\">\n"
+ + ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.reasons : depth0),{"name":"each","hash":{},"fn":container.program(23, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + " </div>\n";
+},"23":function(container,depth0,helpers,partials,data) {
var stack1;
- return " <span class=\"reasons\">"
+ return " <span class=\"reasons\">"
+ container.escapeExpression(container.lambda(depth0, depth0))
+ "</span> "
- + ((stack1 = helpers.unless.call(depth0 != null ? depth0 : {},(data && data.last),{"name":"unless","hash":{},"fn":container.program(11, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + ((stack1 = helpers.unless.call(depth0 != null ? depth0 : {},(data && data.last),{"name":"unless","hash":{},"fn":container.program(24, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ "\n";
-},"11":function(container,depth0,helpers,partials,data) {
+},"24":function(container,depth0,helpers,partials,data) {
return "&laquo;";
-},"13":function(container,depth0,helpers,partials,data) {
- var helper, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression;
+},"26":function(container,depth0,helpers,partials,data) {
+ var stack1;
- return " <span class=\"tag tag-"
- + alias4(((helper = (helper = helpers.category || (depth0 != null ? depth0.category : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"category","hash":{},"data":data}) : helper)))
- + "\" title=\""
- + alias4(((helper = (helper = helpers.notes || (depth0 != null ? depth0.notes : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"notes","hash":{},"data":data}) : helper)))
- + "\">"
- + alias4(((helper = (helper = helpers.name || (depth0 != null ? depth0.name : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"name","hash":{},"data":data}) : helper)))
- + "</span>\n";
-},"15":function(container,depth0,helpers,partials,data) {
+ return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : {},((stack1 = (depth0 != null ? depth0.definitions : depth0)) != null ? stack1["1"] : stack1),{"name":"if","hash":{},"fn":container.program(27, data, 0),"inverse":container.program(30, data, 0),"data":data})) != null ? stack1 : "");
+},"27":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 : "")
+ + ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.definitions : depth0),{"name":"each","hash":{},"fn":container.program(28, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ " </ol>\n";
-},"16":function(container,depth0,helpers,partials,data) {
- return " <li><span>"
- + container.escapeExpression(container.lambda(depth0, depth0))
- + "</span></li>\n";
-},"18":function(container,depth0,helpers,partials,data) {
+},"28":function(container,depth0,helpers,partials,data) {
var stack1;
- return " <p>"
- + container.escapeExpression(container.lambda(((stack1 = (depth0 != null ? depth0.glossary : depth0)) != null ? stack1["0"] : stack1), depth0))
- + "</p>\n";
-},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
- var stack1, helper, alias1=depth0 != null ? depth0 : {};
+ return " <li>"
+ + ((stack1 = container.invokePartial(partials.definition,depth0,{"name":"definition","data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "")
+ + "</li>\n";
+},"30":function(container,depth0,helpers,partials,data) {
+ var stack1;
- return "<div class=\"term-definition\">\n <div class=\"action-bar\" data-sequence=\""
- + container.escapeExpression(((helper = (helper = helpers.sequence || (depth0 != null ? depth0.sequence : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(alias1,{"name":"sequence","hash":{},"data":data}) : helper)))
- + "\">\n"
- + ((stack1 = helpers["if"].call(alias1,((stack1 = (depth0 != null ? depth0.options : depth0)) != null ? stack1.enableAudioPlayback : stack1),{"name":"if","hash":{},"fn":container.program(1, 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"
- + ((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-reasons\">\n"
- + ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.reasons : depth0),{"name":"each","hash":{},"fn":container.program(10, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + " </div>\n\n <div class=\"term-tags\">\n"
- + ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.tags : depth0),{"name":"each","hash":{},"fn":container.program(13, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
- + " </div>\n\n <div class=\"term-glossary\">\n"
- + ((stack1 = helpers["if"].call(alias1,((stack1 = (depth0 != null ? depth0.glossary : depth0)) != null ? stack1["1"] : stack1),{"name":"if","hash":{},"fn":container.program(15, data, 0),"inverse":container.program(18, data, 0),"data":data})) != null ? stack1 : "")
- + " </div>\n</div>\n";
-},"useData":true});
+ return ((stack1 = container.invokePartial(partials.definition,((stack1 = (depth0 != null ? depth0.definitions : depth0)) != null ? stack1["0"] : stack1),{"name":"definition","data":data,"indent":" ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
+},"32":function(container,depth0,helpers,partials,data) {
+ var stack1;
+
+ return ((stack1 = container.invokePartial(partials.definition,depth0,{"name":"definition","data":data,"indent":" ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
+},"34":function(container,depth0,helpers,partials,data,blockParams,depths) {
+ var stack1;
+
+ return ((stack1 = helpers.each.call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.definitions : depth0),{"name":"each","hash":{},"fn":container.program(35, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "");
+},"35":function(container,depth0,helpers,partials,data,blockParams,depths) {
+ var stack1;
+
+ return " "
+ + ((stack1 = helpers.unless.call(depth0 != null ? depth0 : {},(data && data.first),{"name":"unless","hash":{},"fn":container.program(36, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ + "\n"
+ + ((stack1 = container.invokePartial(partials.term,depth0,{"name":"term","hash":{"playback":(depths[1] != null ? depths[1].playback : depths[1]),"addable":(depths[1] != null ? depths[1].addable : depths[1]),"grouped":(depths[1] != null ? depths[1].grouped : depths[1])},"data":data,"indent":" ","helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : "");
+},"36":function(container,depth0,helpers,partials,data) {
+ return "<hr>";
+},"38":function(container,depth0,helpers,partials,data) {
+ return " <p>No results found</p>\n";
+},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data,blockParams,depths) {
+ var stack1;
+
+ return "\n\n"
+ + ((stack1 = helpers["if"].call(depth0 != null ? depth0 : {},(depth0 != null ? depth0.definitions : depth0),{"name":"if","hash":{},"fn":container.program(34, data, 0, blockParams, depths),"inverse":container.program(38, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "");
+},"main_d": function(fn, props, container, depth0, data, blockParams, depths) {
+
+ var decorators = container.decorators;
+
+ fn = decorators.inline(fn,props,container,{"name":"inline","hash":{},"fn":container.program(1, data, 0, blockParams, depths),"inverse":container.noop,"args":["definition"],"data":data}) || fn;
+ fn = decorators.inline(fn,props,container,{"name":"inline","hash":{},"fn":container.program(12, data, 0, blockParams, depths),"inverse":container.noop,"args":["term"],"data":data}) || fn;
+ return fn;
+ }
+
+,"useDecorators":true,"usePartial":true,"useData":true,"useDepths":true});
})(); \ No newline at end of file
diff --git a/ext/bg/js/translator.js b/ext/bg/js/translator.js
index 2b6b87c1..8710f568 100644
--- a/ext/bg/js/translator.js
+++ b/ext/bg/js/translator.js
@@ -54,6 +54,8 @@ class Translator {
let definitions = [];
for (const deinflection of deinflections) {
for (const definition of deinflection.definitions) {
+ const tags = definition.tags.map(tag => buildTag(tag, definition.tagMeta));
+ tags.push(buildDictTag(definition.dictionary));
definitions.push({
source: deinflection.source,
reasons: deinflection.reasons,
@@ -63,7 +65,7 @@ class Translator {
expression: definition.expression,
reading: definition.reading,
glossary: definition.glossary,
- tags: sortTags(definition.tags.map(tag => buildTag(tag, definition.tagMeta)))
+ tags: sortTags(tags)
});
}
}
@@ -80,6 +82,12 @@ class Translator {
});
}
+ findTermGrouped(text, dictionaries, enableSoftKatakanaSearch) {
+ return this.findTerm(text, dictionaries, enableSoftKatakanaSearch).then(({length, definitions}) => {
+ return {length, definitions: groupTermDefs(definitions)};
+ });
+ }
+
findKanji(text, dictionaries) {
const processed = {}, promises = [];
for (const c of text) {
@@ -89,7 +97,16 @@ class Translator {
}
}
- return Promise.all(promises).then(sets => this.processKanji(sets.reduce((a, b) => a.concat(b), [])));
+ return Promise.all(promises).then(defSets => {
+ const definitions = defSets.reduce((a, b) => a.concat(b), []);
+ for (const definition of definitions) {
+ const tags = definition.tags.map(tag => buildTag(tag, definition.tagMeta));
+ tags.push(buildDictTag(definition.dictionary));
+ definition.tags = sortTags(tags);
+ }
+
+ return definitions;
+ });
}
findTermDeinflections(text, dictionaries, cache) {
diff --git a/ext/bg/js/util.js b/ext/bg/js/util.js
index 059f3160..504deeda 100644
--- a/ext/bg/js/util.js
+++ b/ext/bg/js/util.js
@@ -21,7 +21,7 @@ function kanjiLinks(options) {
let result = '';
for (const c of options.fn(this)) {
if (isKanji(c)) {
- result += Handlebars.templates['kanji-link.html']({kanji: c}).trim();
+ result += `<a href="#" class="kanji-link">${c}</a>`;
} else {
result += c;
}
@@ -30,6 +30,10 @@ function kanjiLinks(options) {
return result;
}
+function multiLine(options) {
+ return options.fn(this).split('\n').join('<br>');
+}
+
function isKanji(c) {
const code = c.charCodeAt(0);
return code >= 0x4e00 && code < 0x9fb0 || code >= 0x3400 && code < 0x4dc0;
@@ -44,28 +48,6 @@ function promiseCallback(promise, callback) {
});
}
-function sortTags(tags) {
- return tags.sort((v1, v2) => {
- const order1 = v1.order;
- const order2 = v2.order;
- if (order1 < order2) {
- return -1;
- } else if (order1 > order2) {
- return 1;
- }
-
- const name1 = v1.name;
- const name2 = v2.name;
- if (name1 < name2) {
- return -1;
- } else if (name1 > name2) {
- return 1;
- }
-
- return 0;
- });
-}
-
function sortTermDefs(definitions) {
return definitions.sort((v1, v2) => {
const sl1 = v1.source.length;
@@ -113,6 +95,43 @@ function undupeTermDefs(definitions) {
return definitionsUnique;
}
+function groupTermDefs(definitions) {
+ const groups = {};
+ for (const definition of definitions) {
+ const key = [definition.source, definition.expression].concat(definition.reasons);
+ if (definition.reading) {
+ key.push(definition.reading);
+ }
+
+ const group = groups[key];
+ if (group) {
+ group.push(definition);
+ } else {
+ groups[key] = [definition];
+ }
+ }
+
+ const results = [];
+ for (const key in groups) {
+ const groupDefs = groups[key];
+ const firstDef = groupDefs[0];
+ results.push({
+ definitions: groupDefs,
+ expression: firstDef.expression,
+ reading: firstDef.reading,
+ reasons: firstDef.reasons,
+ score: groupDefs.reduce((x, y) => x > y ? x : y, Number.MIN_SAFE_INTEGER),
+ source: firstDef.source,
+ });
+ }
+
+ return sortTermDefs(results);
+}
+
+function buildDictTag(name) {
+ return sanitizeTag({name, category: 'dictionary', order: 100});
+}
+
function buildTag(name, meta) {
const tag = {name};
const symbol = name.split(':')[0];
@@ -135,6 +154,95 @@ function splitField(field) {
return field.length === 0 ? [] : field.split(' ');
}
+function sortTags(tags) {
+ return tags.sort((v1, v2) => {
+ const order1 = v1.order;
+ const order2 = v2.order;
+ if (order1 < order2) {
+ return -1;
+ } else if (order1 > order2) {
+ return 1;
+ }
+
+ const name1 = v1.name;
+ const name2 = v2.name;
+ if (name1 < name2) {
+ return -1;
+ } else if (name1 > name2) {
+ return 1;
+ }
+
+ return 0;
+ });
+}
+
+function formatField(field, definition, mode) {
+ const markers = [
+ 'audio',
+ 'character',
+ 'dictionary',
+ 'expression',
+ 'expression-furigana',
+ 'glossary',
+ 'glossary-list',
+ 'kunyomi',
+ 'onyomi',
+ 'reading',
+ 'sentence',
+ 'tags',
+ 'url',
+ ];
+
+ for (const marker of markers) {
+ let value = definition[marker] || null;
+ switch (marker) {
+ case 'expression':
+ if (mode === 'term_kana' && definition.reading) {
+ value = definition.reading;
+ }
+ break;
+ case 'expression-furigana':
+ if (mode === 'term_kana' && definition.reading) {
+ value = definition.reading;
+ } else {
+ value = `<ruby>${definition.expression}<rt>${definition.reading}</rt></ruby>`;
+ }
+ break;
+ case 'reading':
+ if (mode === 'term_kana') {
+ value = null;
+ }
+ break;
+ case 'glossary-list':
+ if (definition.glossary) {
+ if (definition.glossary.length > 1) {
+ value = '<ol style="white-space: pre; text-align: left; overflow-x: auto;">';
+ for (const gloss of definition.glossary) {
+ value += `<li>${gloss}</li>`;
+ }
+ value += '</ol>';
+ } else {
+ value = `<p style="white-space: pre; overflow-x: auto;">${definition.glossary.join('')}</p>`;
+ }
+ }
+ break;
+ case 'tags':
+ if (definition.tags) {
+ value = definition.tags.map(t => t.name);
+ }
+ break;
+ }
+
+ if (value !== null && typeof(value) !== 'string') {
+ value = value.join(', ');
+ }
+
+ field = field.replace(`{${marker}}`, value || '');
+ }
+
+ return field;
+}
+
function loadJson(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
diff --git a/ext/bg/js/yomichan.js b/ext/bg/js/yomichan.js
index 12dd89ac..2cdcf1c8 100644
--- a/ext/bg/js/yomichan.js
+++ b/ext/bg/js/yomichan.js
@@ -21,6 +21,7 @@ class Yomichan {
constructor() {
Handlebars.partials = Handlebars.templates;
Handlebars.registerHelper('kanjiLinks', kanjiLinks);
+ Handlebars.registerHelper('multiLine', multiLine);
this.translator = new Translator();
this.anki = new AnkiNull();
@@ -117,75 +118,6 @@ class Yomichan {
chrome.tabs.sendMessage(tabId, {action, params}, () => null);
}
- formatField(field, definition, mode) {
- const markers = [
- 'audio',
- 'character',
- 'expression',
- 'expression-furigana',
- 'glossary',
- 'glossary-list',
- 'kunyomi',
- 'onyomi',
- 'reading',
- 'sentence',
- 'tags',
- 'url',
- ];
-
- for (const marker of markers) {
- let value = definition[marker] || null;
- switch (marker) {
- case 'audio':
- value = '';
- break;
- case 'expression':
- if (mode === 'term_kana' && definition.reading) {
- value = definition.reading;
- }
- break;
- case 'expression-furigana':
- if (mode === 'term_kana' && definition.reading) {
- value = definition.reading;
- } else {
- value = `<ruby>${definition.expression}<rt>${definition.reading}</rt></ruby>`;
- }
- break;
- case 'reading':
- if (mode === 'term_kana') {
- value = null;
- }
- break;
- case 'glossary-list':
- if (definition.glossary) {
- if (definition.glossary.length > 1) {
- value = '<ol style="white-space: pre; text-align: left; overflow-x: auto;">';
- for (const gloss of definition.glossary) {
- value += `<li>${gloss}</li>`;
- }
- value += '</ol>';
- } else {
- value = `<p style="white-space: pre; overflow-x: auto;">${definition.glossary.join('')}</p>`;
- }
- }
- break;
- case 'tags':
- if (definition.tags) {
- value = definition.tags.map(t => t.name);
- }
- break;
- }
-
- if (value !== null && typeof(value) !== 'string') {
- value = value.join(', ');
- }
-
- field = field.replace(`{${marker}}`, value || '');
- }
-
- return field;
- }
-
formatNote(definition, mode) {
const note = {fields: {}, tags: this.options.ankiCardTags};
@@ -217,7 +149,7 @@ class Yomichan {
}
for (const name in fields) {
- note.fields[name] = this.formatField(fields[name], definition, mode);
+ note.fields[name] = formatField(fields[name], definition, mode);
}
return note;
@@ -254,7 +186,29 @@ class Yomichan {
}
promiseCallback(
- this.translator.findTerm(text, dictionaries, this.options.enableSoftKatakanaSearch),
+ this.translator.findTerm(
+ text,
+ dictionaries,
+ this.options.enableSoftKatakanaSearch
+ ),
+ callback
+ );
+ }
+
+ api_findTermGrouped({text, callback}) {
+ const dictionaries = [];
+ for (const title in this.options.dictionaries) {
+ if (this.options.dictionaries[title].enableTerms) {
+ dictionaries.push(title);
+ }
+ }
+
+ promiseCallback(
+ this.translator.findTermGrouped(
+ text,
+ dictionaries,
+ this.options.enableSoftKatakanaSearch
+ ),
callback
);
}
diff --git a/ext/bg/options.html b/ext/bg/options.html
index 0ef65abb..6bf6fb7b 100644
--- a/ext/bg/options.html
+++ b/ext/bg/options.html
@@ -38,6 +38,10 @@
</div>
<div class="checkbox">
+ <label><input type="checkbox" id="group-term-results"> Group term results</label>
+ </div>
+
+ <div class="checkbox">
<label><input type="checkbox" id="show-advanced-options"> Show advanced options</label>
</div>
</div>
diff --git a/ext/fg/css/frame.css b/ext/fg/css/frame.css
index 67caf152..7e9c3b19 100644
--- a/ext/fg/css/frame.css
+++ b/ext/fg/css/frame.css
@@ -18,6 +18,15 @@
/* common styles */
+@font-face {
+ font-family: kanji-stroke-orders;
+ src: url('../ttf/kanji-stroke-orders.ttf');
+}
+@font-face {
+ font-family: vl-gothic-regular;
+ src: url('../ttf/vl-gothic-regular.ttf');
+}
+
body {
background-color: #fff;
color: #333;
@@ -66,6 +75,10 @@ body {
background-color: #d9534f;
}
+.tag-dictionary {
+ background-color: #aa66cc;
+}
+
.action-bar {
float: right;
}
@@ -86,6 +99,26 @@ body {
opacity: 0.25;
}
+.action-bar .pending {
+ visibility: hidden;
+}
+
+.spinner {
+ bottom: 5px;
+ display: none;
+ position: fixed;
+ right: 5px;
+}
+
+hr {
+ background-color: #fff;
+ border: none;
+ border-top: 1px dotted #ccc;
+ margin-top: 0.8em;
+ margin-bottom: 0.8em;
+ height: 1px;
+}
+
/* term styles */
.term-expression {
@@ -105,11 +138,7 @@ body {
display: inline-block;
}
-.term-glossary {
- font-family: vl-gothic-regular;
-}
-
-.term-glossary ol {
+.term-glossary ol, .term-glossary ul {
padding-left: 1.4em;
}
@@ -117,15 +146,14 @@ body {
color: #777;
}
-.term-glossary li span {
- color: #000;
- overflow-x: auto;
- white-space: pre;
+.term-glossary-group {
+ margin-top: 0.4em;
+ margin-bottom: 0.4em;
}
-.term-glossary p {
- overflow-x: auto;
- white-space: pre;
+.term-glossary-item {
+ color: #000;
+ font-family: vl-gothic-regular;
}
/* kanji styles */
@@ -153,10 +181,6 @@ body {
font-family: vl-gothic-regular;
}
-.kanji-glossary {
- font-family: vl-gothic-regular;
-}
-
.kanji-glossary ol {
padding-left: 1.4em;
}
@@ -165,13 +189,12 @@ body {
color: #777;
}
-.kanji-glossary li span {
- color: #000;
- overflow-x: auto;
- white-space: pre;
+.kanji-glossary-group {
+ padding-bottom: 0.7em;
+ padding-top: 0.7em;
}
-.kanji-glossary p {
- overflow-x: auto;
- white-space: pre;
+.kanji-glossary-item {
+ color: #000;
+ font-family: vl-gothic-regular;
}
diff --git a/ext/fg/frame.html b/ext/fg/frame.html
new file mode 100644
index 00000000..8246787b
--- /dev/null
+++ b/ext/fg/frame.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="UTF-8">
+ <title></title>
+ <link rel="stylesheet" href="css/frame.css">
+ </head>
+ <body>
+ <div class="spinner">
+ <img src="img/spinner.gif">
+ </div>
+
+ <div class="content"></div>
+
+ <script src="../lib/jquery-2.2.2.min.js"></script>
+ <script src="js/util.js"></script>
+ <script src="js/frame.js"></script>
+ </body>
+</html>
diff --git a/ext/fg/img/spinner.gif b/ext/fg/img/spinner.gif
new file mode 100644
index 00000000..8ed30cb6
--- /dev/null
+++ b/ext/fg/img/spinner.gif
Binary files differ
diff --git a/ext/fg/js/driver.js b/ext/fg/js/driver.js
index 9aab7950..ef7db481 100644
--- a/ext/fg/js/driver.js
+++ b/ext/fg/js/driver.js
@@ -21,27 +21,22 @@ class Driver {
constructor() {
this.popup = new Popup();
this.popupTimer = null;
- this.audio = {};
this.lastMousePos = null;
this.lastTextSource = null;
this.pendingLookup = false;
this.enabled = false;
this.options = null;
- this.definitions = null;
- this.sequence = 0;
- this.fgRoot = chrome.extension.getURL('fg');
chrome.runtime.onMessage.addListener(this.onBgMessage.bind(this));
- window.addEventListener('message', this.onFrameMessage.bind(this));
window.addEventListener('mouseover', this.onMouseOver.bind(this));
window.addEventListener('mousedown', this.onMouseDown.bind(this));
window.addEventListener('mousemove', this.onMouseMove.bind(this));
window.addEventListener('keydown', this.onKeyDown.bind(this));
- window.addEventListener('resize', e => this.hidePopup());
+ window.addEventListener('resize', e => this.searchClear());
getOptions().then(opts => {
this.options = opts;
- return getEnabled();
+ return isEnabled();
}).then(enabled => {
this.enabled = enabled;
});
@@ -65,7 +60,7 @@ class Driver {
if (this.enabled && this.lastMousePos !== null && e.keyCode === 16 /* shift */) {
this.searchAt(this.lastMousePos, true);
} else if (e.keyCode === 27 /* esc */) {
- this.hidePopup();
+ this.searchClear();
}
}
@@ -92,7 +87,7 @@ class Driver {
}
const searcher = () => this.searchAt(this.lastMousePos, false);
- if (!this.popup.visible() || e.shiftKey || e.which === 2 /* mmb */) {
+ if (!this.popup.isVisible() || e.shiftKey || e.which === 2 /* mmb */) {
searcher();
} else {
this.popupTimerSet(searcher);
@@ -102,7 +97,7 @@ class Driver {
onMouseDown(e) {
this.lastMousePos = {x: e.clientX, y: e.clientY};
this.popupTimerClear();
- this.hidePopup();
+ this.searchClear();
}
onBgMessage({action, params}, sender, callback) {
@@ -114,20 +109,46 @@ class Driver {
callback();
}
- onFrameMessage(e) {
- const {action, params} = e.data, method = this['api_' + action];
- if (typeof(method) === 'function') {
- method.call(this, params);
+ searchAt(point, hideNotFound) {
+ if (this.pendingLookup) {
+ return;
+ }
+
+ const textSource = textSourceFromPoint(point);
+ if (textSource === null || !textSource.containsPoint(point)) {
+ if (hideNotFound) {
+ this.searchClear();
+ }
+
+ return;
+ }
+
+ if (this.lastTextSource !== null && this.lastTextSource.equals(textSource)) {
+ return;
}
+
+ this.pendingLookup = true;
+ this.searchTerms(textSource).then(found => {
+ if (!found) {
+ this.searchKanji(textSource).then(found => {
+ if (!found && hideNotFound) {
+ this.searchClear();
+ }
+ });
+ }
+ }).catch(error => {
+ window.alert('Error: ' + error);
+ }).then(() => {
+ this.pendingLookup = false;
+ });
}
searchTerms(textSource) {
textSource.setEndOffset(this.options.scanLength);
- this.pendingLookup = true;
- return findTerm(textSource.text()).then(({definitions, length}) => {
+ const findFunc = this.options.groupTermResults ? findTermGrouped : findTerm;
+ return findFunc(textSource.text()).then(({definitions, length}) => {
if (definitions.length === 0) {
- this.pendingLookup = false;
return false;
} else {
textSource.setEndOffset(length);
@@ -138,119 +159,46 @@ class Driver {
definition.sentence = sentence;
});
- const sequence = ++this.sequence;
- const context = {
- definitions,
- sequence,
- addable: this.options.ankiMethod !== 'disabled',
- root: this.fgRoot,
- options: this.options
- };
-
- return renderText(context, 'term-list.html').then(content => {
- this.definitions = definitions;
- this.pendingLookup = false;
- this.showPopup(textSource, content);
- return canAddDefinitions(definitions, ['term_kanji', 'term_kana']);
- }).then(states => {
- if (states !== null) {
- states.forEach((state, index) => this.popup.invokeApi(
- 'setActionState',
- {index, state, sequence}
- ));
- }
+ this.popup.showNextTo(textSource.getRect());
+ this.popup.showTermDefs(definitions, this.options);
+ this.lastTextSource = textSource;
+ if (this.options.selectMatchedText) {
+ textSource.select();
+ }
- return true;
- });
+ return true;
}
}).catch(error => {
- alert('Error: ' + error);
+ window.alert('Error: ' + error);
+ return false;
});
}
searchKanji(textSource) {
textSource.setEndOffset(1);
- this.pendingLookup = true;
return findKanji(textSource.text()).then(definitions => {
if (definitions.length === 0) {
- this.pendingLookup = false;
return false;
} else {
definitions.forEach(definition => definition.url = window.location.href);
- const sequence = ++this.sequence;
- const context = {
- definitions,
- sequence,
- addable: this.options.ankiMethod !== 'disabled',
- root: this.fgRoot,
- options: this.options
- };
-
- return renderText(context, 'kanji-list.html').then(content => {
- this.definitions = definitions;
- this.pendingLookup = false;
- this.showPopup(textSource, content);
- return canAddDefinitions(definitions, ['kanji']);
- }).then(states => {
- if (states !== null) {
- states.forEach((state, index) => this.popup.invokeApi(
- 'setActionState',
- {index, state, sequence}
- ));
- }
-
- return true;
- });
- }
- }).catch(error => {
- alert('Error: ' + error);
- });
- }
-
- searchAt(point, hideNotFound) {
- if (this.pendingLookup) {
- return;
- }
-
- const textSource = textSourceFromPoint(point);
- if (textSource === null || !textSource.containsPoint(point)) {
- if (hideNotFound) {
- this.hidePopup();
- }
-
- return;
- }
-
- if (this.lastTextSource !== null && this.lastTextSource.equals(textSource)) {
- return true;
- }
+ this.popup.showNextTo(textSource.getRect());
+ this.popup.showKanjiDefs(definitions, this.options);
+ this.lastTextSource = textSource;
+ if (this.options.selectMatchedText) {
+ textSource.select();
+ }
- this.searchTerms(textSource).then(found => {
- if (!found) {
- this.searchKanji(textSource).then(found => {
- if (!found && hideNotFound) {
- this.hidePopup();
- }
- });
+ return true;
}
}).catch(error => {
- alert('Error: ' + error);
+ window.alert('Error: ' + error);
+ return false;
});
}
- showPopup(textSource, content) {
- this.popup.showNextTo(textSource.getRect(), content);
-
- if (this.options.selectMatchedText) {
- textSource.select();
- }
-
- this.lastTextSource = textSource;
- }
-
- hidePopup() {
+ searchClear() {
this.popup.hide();
if (this.options.selectMatchedText && this.lastTextSource !== null) {
@@ -258,7 +206,6 @@ class Driver {
}
this.lastTextSource = null;
- this.definitions = null;
}
api_setOptions(opts) {
@@ -267,68 +214,9 @@ class Driver {
api_setEnabled(enabled) {
if (!(this.enabled = enabled)) {
- this.hidePopup();
+ this.searchClear();
}
}
-
- api_addNote({index, mode}) {
- const state = {[mode]: false};
- addDefinition(this.definitions[index], mode).then(success => {
- if (success) {
- this.popup.invokeApi('setActionState', {index, state, sequence: this.sequence});
- } else {
- alert('Note could not be added');
- }
- }).catch(error => {
- alert('Error: ' + error);
- });
- }
-
- api_playAudio(index) {
- const definition = this.definitions[index];
-
- let url = `https://assets.languagepod101.com/dictionary/japanese/audiomp3.php?kanji=${encodeURIComponent(definition.expression)}`;
- if (definition.reading) {
- url += `&kana=${encodeURIComponent(definition.reading)}`;
- }
-
- for (const key in this.audio) {
- this.audio[key].pause();
- }
-
- const audio = this.audio[url] || new Audio(url);
- audio.currentTime = 0;
- audio.play();
-
- this.audio[url] = audio;
- }
-
- api_displayKanji(kanji) {
- findKanji(kanji).then(definitions => {
- definitions.forEach(definition => definition.url = window.location.href);
-
- const sequence = ++this.sequence;
- const context = {
- definitions,
- sequence,
- addable: this.options.ankiMethod !== 'disabled',
- root: this.fgRoot,
- options: this.options
- };
-
- return renderText(context, 'kanji-list.html').then(content => {
- this.definitions = definitions;
- this.popup.setContent(content, definitions);
- return canAddDefinitions(definitions, ['kanji']);
- }).then(states => {
- if (states !== null) {
- states.forEach((state, index) => this.popup.invokeApi('setActionState', {index, state, sequence}));
- }
- });
- }).catch(error => {
- alert('Error: ' + error);
- });
- }
}
window.driver = new Driver();
diff --git a/ext/fg/js/frame.js b/ext/fg/js/frame.js
index 8a99a405..4295dbb3 100644
--- a/ext/fg/js/frame.js
+++ b/ext/fg/js/frame.js
@@ -16,65 +16,145 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+class Frame {
+ constructor() {
+ this.definitions = [];
+ this.audioCache = {};
+ this.sequence = 0;
-function invokeApi(action, params, target) {
- target.postMessage({action, params}, '*');
-}
+ $(window).on('message', e => {
+ const {action, params} = e.originalEvent.data, method = this['api_' + action];
+ if (typeof(method) === 'function') {
+ method.call(this, params);
+ }
+ });
+ }
+
+ api_showTermDefs({definitions, options}) {
+ const sequence = ++this.sequence;
+ const context = {
+ definitions,
+ grouped: options.groupTermResults,
+ addable: options.ankiMethod !== 'disabled',
+ playback: options.enableAudioPlayback
+ };
+
+ this.definitions = definitions;
+ this.showSpinner(false);
+ window.scrollTo(0, 0);
+
+ renderText(context, 'term-list.html').then(content => {
+ $('.content').html(content);
+ $('.action-add-note').click(this.onAddNote.bind(this));
+
+ $('.kanji-link').click(e => {
+ e.preventDefault();
+ findKanji($(e.target).text()).then(kdefs => this.api_showKanjiDefs({options, definitions: kdefs}));
+ });
+
+ $('.action-play-audio').click(e => {
+ e.preventDefault();
+ const index = $(e.currentTarget).data('index');
+ this.playAudio(this.definitions[index]);
+ });
-function registerKanjiLinks() {
- for (const link of Array.from(document.getElementsByClassName('kanji-link'))) {
- link.addEventListener('click', e => {
- e.preventDefault();
- invokeApi('displayKanji', e.target.innerHTML, window.parent);
+ this.updateAddNoteButtons(['term_kanji', 'term_kana'], sequence);
});
}
-}
-function registerAddNoteLinks() {
- for (const link of Array.from(document.getElementsByClassName('action-add-note'))) {
- link.addEventListener('click', e => {
- e.preventDefault();
- const ds = e.currentTarget.dataset;
- invokeApi('addNote', {index: ds.index, mode: ds.mode}, window.parent);
+ api_showKanjiDefs({definitions, options}) {
+ const sequence = ++this.sequence;
+ const context = {
+ definitions,
+ addable: options.ankiMethod !== 'disabled'
+ };
+
+ this.definitions = definitions;
+ this.showSpinner(false);
+ window.scrollTo(0, 0);
+
+ renderText(context, 'kanji-list.html').then(content => {
+ $('.content').html(content);
+ $('.action-add-note').click(this.onAddNote.bind(this));
+
+ this.updateAddNoteButtons(['kanji'], sequence);
});
}
-}
-function registerAudioLinks() {
- for (const link of Array.from(document.getElementsByClassName('action-play-audio'))) {
- link.addEventListener('click', e => {
- e.preventDefault();
- const ds = e.currentTarget.dataset;
- invokeApi('playAudio', ds.index, window.parent);
+ findAddNoteButton(index, mode) {
+ return $(`.action-add-note[data-index="${index}"][data-mode="${mode}"]`);
+ }
+
+ onAddNote(e) {
+ e.preventDefault();
+ this.showSpinner(true);
+
+ const link = $(e.currentTarget);
+ const index = link.data('index');
+ const mode = link.data('mode');
+
+ addDefinition(this.definitions[index], mode).then(success => {
+ if (success) {
+ const button = this.findAddNoteButton(index, mode);
+ button.addClass('disabled');
+ } else {
+ window.alert('Note could not be added');
+ }
+ }).catch(error => {
+ window.alert('Error: ' + error);
+ }).then(() => {
+ this.showSpinner(false);
});
}
-}
-function api_setActionState({index, state, sequence}) {
- for (const mode in state) {
- const matches = document.querySelectorAll(`.action-bar[data-sequence="${sequence}"] .action-add-note[data-index="${index}"][data-mode="${mode}"]`);
- if (matches.length === 0) {
- return;
- }
+ updateAddNoteButtons(modes, sequence) {
+ canAddDefinitions(this.definitions, modes).then(states => {
+ if (states === null) {
+ return;
+ }
- const classes = matches[0].classList;
- if (state[mode]) {
- classes.remove('disabled');
+ if (sequence !== this.sequence) {
+ return;
+ }
+
+ states.forEach((state, index) => {
+ for (const mode in state) {
+ const button = this.findAddNoteButton(index, mode);
+ if (state[mode]) {
+ button.removeClass('disabled');
+ } else {
+ button.addClass('disabled');
+ }
+
+ button.removeClass('pending');
+ }
+ });
+ });
+ }
+
+ showSpinner(show) {
+ const spinner = $('.spinner');
+ if (show) {
+ spinner.show();
} else {
- classes.add('disabled');
+ spinner.hide();
}
}
-}
-document.addEventListener('DOMContentLoaded', () => {
- registerKanjiLinks();
- registerAddNoteLinks();
- registerAudioLinks();
-});
+ playAudio(definition) {
+ let url = `https://assets.languagepod101.com/dictionary/japanese/audiomp3.php?kanji=${encodeURIComponent(definition.expression)}`;
+ if (definition.reading) {
+ url += `&kana=${encodeURIComponent(definition.reading)}`;
+ }
-window.addEventListener('message', e => {
- const {action, params} = e.data, method = window['api_' + action];
- if (typeof(method) === 'function') {
- method(params);
+ for (const key in this.audioCache) {
+ this.audioCache[key].pause();
+ }
+
+ const audio = this.audioCache[url] || new Audio(url);
+ audio.currentTime = 0;
+ audio.play();
}
-});
+}
+
+window.frame = new Frame();
diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js
index 8e71fefa..d47ab4ae 100644
--- a/ext/fg/js/popup.js
+++ b/ext/fg/js/popup.js
@@ -19,25 +19,26 @@
class Popup {
constructor() {
- this.container = null;
this.offset = 10;
- }
- show(rect, content) {
- this.inject();
+ this.container = document.createElement('iframe');
+ this.container.id = 'yomichan-popup';
+ this.container.addEventListener('mousedown', e => e.stopPropagation());
+ this.container.addEventListener('scroll', e => e.stopPropagation());
+ this.container.setAttribute('src', chrome.extension.getURL('fg/frame.html'));
+
+ document.body.appendChild(this.container);
+ }
+ showAt(rect) {
this.container.style.left = rect.x + 'px';
this.container.style.top = rect.y + 'px';
this.container.style.height = rect.height + 'px';
this.container.style.width = rect.width + 'px';
this.container.style.visibility = 'visible';
-
- this.setContent(content);
}
- showNextTo(elementRect, content) {
- this.inject();
-
+ showNextTo(elementRect) {
const containerStyle = window.getComputedStyle(this.container);
const containerHeight = parseInt(containerStyle.height);
const containerWidth = parseInt(containerStyle.width);
@@ -56,48 +57,26 @@ class Popup {
y = elementRect.top - height - this.offset;
}
- this.show({x, y, width, height}, content);
- }
-
- visible() {
- return this.container !== null && this.container.style.visibility !== 'hidden';
+ this.showAt({x, y, width, height});
}
hide() {
- if (this.container !== null) {
- this.container.style.visibility = 'hidden';
- }
+ this.container.style.visibility = 'hidden';
}
- setContent(content) {
- if (this.container === null) {
- return;
- }
-
- this.container.contentWindow.scrollTo(0, 0);
-
- const doc = this.container.contentDocument;
- doc.open();
- doc.write(content);
- doc.close();
+ isVisible() {
+ return this.container.style.visibility !== 'hidden';
}
- invokeApi(action, params) {
- if (this.container !== null) {
- this.container.contentWindow.postMessage({action, params}, '*');
- }
+ showTermDefs(definitions, options) {
+ this.invokeApi('showTermDefs', {definitions, options});
}
- inject() {
- if (this.container !== null) {
- return;
- }
-
- this.container = document.createElement('iframe');
- this.container.id = 'yomichan-popup';
- this.container.addEventListener('mousedown', e => e.stopPropagation());
- this.container.addEventListener('scroll', e => e.stopPropagation());
+ showKanjiDefs(definitions, options) {
+ this.invokeApi('showKanjiDefs', {definitions, options});
+ }
- document.body.appendChild(this.container);
+ invokeApi(action, params) {
+ this.container.contentWindow.postMessage({action, params}, '*');
}
}
diff --git a/ext/fg/js/util.js b/ext/fg/js/util.js
index 96007b74..cedfb887 100644
--- a/ext/fg/js/util.js
+++ b/ext/fg/js/util.js
@@ -17,7 +17,7 @@
*/
-function invokeApiBg(action, params) {
+function invokeBgApi(action, params) {
return new Promise((resolve, reject) => {
chrome.runtime.sendMessage({action, params}, ({result, error}) => {
if (error) {
@@ -29,32 +29,36 @@ function invokeApiBg(action, params) {
});
}
-function getEnabled() {
- return invokeApiBg('getEnabled', {});
+function isEnabled() {
+ return invokeBgApi('getEnabled', {});
}
function getOptions() {
- return invokeApiBg('getOptions', {});
+ return invokeBgApi('getOptions', {});
}
function findTerm(text) {
- return invokeApiBg('findTerm', {text});
+ return invokeBgApi('findTerm', {text});
+}
+
+function findTermGrouped(text) {
+ return invokeBgApi('findTermGrouped', {text});
}
function findKanji(text) {
- return invokeApiBg('findKanji', {text});
+ return invokeBgApi('findKanji', {text});
}
function renderText(data, template) {
- return invokeApiBg('renderText', {data, template});
+ return invokeBgApi('renderText', {data, template});
}
function canAddDefinitions(definitions, modes) {
- return invokeApiBg('canAddDefinitions', {definitions, modes}).catch(() => null);
+ return invokeBgApi('canAddDefinitions', {definitions, modes}).catch(() => null);
}
function addDefinition(definition, mode) {
- return invokeApiBg('addDefinition', {definition, mode});
+ return invokeBgApi('addDefinition', {definition, mode});
}
function textSourceFromPoint(point) {
diff --git a/ext/manifest.json b/ext/manifest.json
index 09905cd8..90663696 100644
--- a/ext/manifest.json
+++ b/ext/manifest.json
@@ -35,7 +35,9 @@
"fg/img/add_term_kana.png",
"fg/img/add_term_kanji.png",
"fg/img/play_audio.png",
+ "fg/img/spinner.gif",
"fg/js/frame.js",
+ "fg/frame.html",
"fg/ttf/kanji-stroke-orders.ttf",
"fg/ttf/vl-gothic-regular.ttf"
]