Merge branch 'dev'

This commit is contained in:
Alex Yatskov 2017-03-25 18:19:38 -07:00
commit 37321e8d52
20 changed files with 485 additions and 209 deletions

View File

@ -7,6 +7,7 @@
<script src="/mixed/lib/handlebars.min.js"></script> <script src="/mixed/lib/handlebars.min.js"></script>
<script src="/mixed/lib/dexie.min.js"></script> <script src="/mixed/lib/dexie.min.js"></script>
<script src="/mixed/lib/wanakana.min.js"></script> <script src="/mixed/lib/wanakana.min.js"></script>
<script src="/mixed/js/util.js"></script>
<script src="/bg/js/templates.js"></script> <script src="/bg/js/templates.js"></script>
<script src="/bg/js/util.js"></script> <script src="/bg/js/util.js"></script>
<script src="/bg/js/anki-connect.js"></script> <script src="/bg/js/anki-connect.js"></script>

View File

@ -7,7 +7,7 @@
<link rel="stylesheet" type="text/css" href="/mixed/lib/bootstrap-3.3.7-dist/css/bootstrap-theme.min.css"> <link rel="stylesheet" type="text/css" href="/mixed/lib/bootstrap-3.3.7-dist/css/bootstrap-theme.min.css">
</head> </head>
<body> <body>
<div class="container-fluid"> <div class="container">
<div class="page-header"> <div class="page-header">
<h1>Welcome to Yomichan!</h1> <h1>Welcome to Yomichan!</h1>
</div> </div>
@ -23,11 +23,12 @@
</p> </p>
<ol> <ol>
<li>Click on the <img src="/mixed/img/icon16.png" alt> icon in the browser toolbar to open the Yomichan options page.</li> <li>Click on the <img src="/mixed/img/icon16.png" alt> icon in the browser toolbar to open the Yomichan actions dialog.</li>
<li>Import the dictionaries (bundled or custom) you wish to use for term and Kanji searches.</li> <li>Click on the <em>monkey wrench</em> icon in the middle to open the options page.</li>
<li>Hold down <kbd>Shift</kbd> (or the middle mouse button) as you hover over text to see term definitions.</li> <li>Import the dictionaries you wish to use for term and Kanji searches.</li>
<li>Click on the <img src="/mixed/img/play-audio.png" alt> icon to hear the term pronounced by a native speaker (if audio is available).</li> <li>Hold down <kbd>Shift</kbd> key or the middle mouse button as you move your mouse over text to display definitions.</li>
<li>Click on Kanji in the definition window to view additional information about that character.</li> <li>Click on the <img src="/mixed/img/play-audio.png" alt> icon to hear the term pronounced by a native speaker.</li>
<li>Click on individual Kanji in the term definition results to view additional information about those characters.</li>
</ol> </ol>
</div> </div>
</div> </div>

View File

@ -90,7 +90,7 @@ class Deinflection {
source: this.term, source: this.term,
rules: this.rules, rules: this.rules,
definitions: this.definitions, definitions: this.definitions,
reasons: [this.reason] reasons: this.reason.length > 0 ? [this.reason] : []
}]; }];
} }

View File

@ -20,8 +20,13 @@
window.displayWindow = new class extends Display { window.displayWindow = new class extends Display {
constructor() { constructor() {
super($('#spinner'), $('#content')); super($('#spinner'), $('#content'));
$('#search').click(this.onSearch.bind(this));
window.wanakana.bind($('#query').get(0)); const search = $('#search');
search.click(this.onSearch.bind(this));
const query = $('#query');
query.on('input', () => search.prop('disabled', query.val().length === 0));
window.wanakana.bind(query.get(0));
} }
definitionAdd(definition, mode) { definitionAdd(definition, mode) {
@ -44,6 +49,10 @@ window.displayWindow = new class extends Display {
window.alert(`Error: ${error}`); window.alert(`Error: ${error}`);
} }
clearSearch() {
$('#query').focus().select();
}
onSearch(e) { onSearch(e) {
e.preventDefault(); e.preventDefault();
$('#intro').slideUp(); $('#intro').slideUp();

View File

@ -18,17 +18,14 @@
$(document).ready(() => { $(document).ready(() => {
$('#open-search').click(() => window.open(chrome.extension.getURL('/bg/search.html'))); $('#open-search').click(() => commandExec('search'));
$('#open-options').click(() => chrome.runtime.openOptionsPage()); $('#open-options').click(() => commandExec('options'));
$('#open-help').click(() => window.open('http://foosoft.net/projects/yomichan')); $('#open-help').click(() => commandExec('help'));
optionsLoad().then(options => { optionsLoad().then(options => {
const toggle = $('#enable-search'); const toggle = $('#enable-search');
toggle.prop('checked', options.general.enable).change(); toggle.prop('checked', options.general.enable).change();
toggle.bootstrapToggle(); toggle.bootstrapToggle();
toggle.change(() => { toggle.change(() => commandExec('toggle'));
options.general.enable = toggle.prop('checked');
optionsSave(options).then(() => instYomi().optionsSet(options));
});
}); });
}); });

View File

@ -284,7 +284,7 @@ templates['fields.html'] = template({"1":function(container,depth0,helpers,parti
templates['kanji.html'] = template({"1":function(container,depth0,helpers,partials,data) { templates['kanji.html'] = template({"1":function(container,depth0,helpers,partials,data) {
var stack1, helper, alias1=depth0 != null ? depth0 : {}; var stack1, helper, alias1=depth0 != null ? depth0 : {};
return "<div class=\"entry\">\n <div class=\"actions\">\n" return "<div class=\"entry\" data-type=\"kanji\">\n <div class=\"actions\">\n <img src=\"/mixed/img/entry-current.png\" class=\"current\" title=\"Current entry (Alt + Up/Down/Home/End/PgUp/PgDn)\" alt>\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 : "") + ((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 : "")
+ ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.source : depth0),{"name":"if","hash":{},"fn":container.program(4, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.source : depth0),{"name":"if","hash":{},"fn":container.program(4, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
+ " </div>\n\n <div class=\"glyph\">" + " </div>\n\n <div class=\"glyph\">"
@ -299,9 +299,9 @@ templates['kanji.html'] = template({"1":function(container,depth0,helpers,partia
+ ((stack1 = helpers["if"].call(alias1,((stack1 = (depth0 != null ? depth0.glossary : depth0)) != null ? stack1["1"] : stack1),{"name":"if","hash":{},"fn":container.program(11, data, 0),"inverse":container.program(15, data, 0),"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(11, data, 0),"inverse":container.program(15, data, 0),"data":data})) != null ? stack1 : "")
+ " </div>\n</div>\n"; + " </div>\n</div>\n";
},"2":function(container,depth0,helpers,partials,data) { },"2":function(container,depth0,helpers,partials,data) {
return " <a href=\"#\" title=\"Add Kanji\" class=\"action-add-note pending disabled\" data-mode=\"kanji\"><img src=\"/mixed/img/add-kanji.png\" alt></a>\n"; return " <a href=\"#\" class=\"action-add-note pending disabled\" data-mode=\"kanji\"><img src=\"/mixed/img/add-kanji.png\" title=\"Add Kanji (Alt + K)\" alt></a>\n";
},"4":function(container,depth0,helpers,partials,data) { },"4":function(container,depth0,helpers,partials,data) {
return " <a href=\"#\" title=\"Source term\" class=\"source-term\"><img src=\"/mixed/img/source-term.png\" alt></a>\n"; return " <a href=\"#\" class=\"source-term\"><img src=\"/mixed/img/source-term.png\" title=\"Source term (Alt + B)\" alt></a>\n";
},"6":function(container,depth0,helpers,partials,data) { },"6":function(container,depth0,helpers,partials,data) {
var stack1; var stack1;
@ -442,7 +442,7 @@ templates['terms.html'] = template({"1":function(container,depth0,helpers,partia
},"12":function(container,depth0,helpers,partials,data) { },"12":function(container,depth0,helpers,partials,data) {
var stack1, alias1=depth0 != null ? depth0 : {}; var stack1, alias1=depth0 != null ? depth0 : {};
return "<div class=\"entry\">\n <div class=\"actions\">\n" return "<div class=\"entry\" data-type=\"term\">\n <div class=\"actions\">\n <img src=\"/mixed/img/entry-current.png\" class=\"current\" title=\"Current entry (Alt + Up/Down/Home/End/PgUp/PgDn)\" alt>\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.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 : "") + ((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" + " </div>\n\n"
@ -453,9 +453,9 @@ templates['terms.html'] = template({"1":function(container,depth0,helpers,partia
+ ((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 : "") + ((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"; + " </div>\n</div>\n";
},"13":function(container,depth0,helpers,partials,data) { },"13":function(container,depth0,helpers,partials,data) {
return " <a href=\"#\" title=\"Add expression\" class=\"action-add-note pending disabled\" data-mode=\"term-kanji\"><img src=\"/mixed/img/add-term-kanji.png\" alt></a>\n <a href=\"#\" title=\"Add reading\" class=\"action-add-note pending disabled\" data-mode=\"term-kana\"><img src=\"/mixed/img/add-term-kana.png\" alt></a>\n"; return " <a href=\"#\" class=\"action-add-note pending disabled\" data-mode=\"term-kanji\"><img src=\"/mixed/img/add-term-kanji.png\" title=\"Add expression (Alt + E)\" alt></a>\n <a href=\"#\" class=\"action-add-note pending disabled\" data-mode=\"term-kana\"><img src=\"/mixed/img/add-term-kana.png\" title=\"Add reading (Alt + R)\" alt></a>\n";
},"15":function(container,depth0,helpers,partials,data) { },"15":function(container,depth0,helpers,partials,data) {
return " <a href=\"#\" title=\"Play audio\" class=\"action-play-audio\"><img src=\"/mixed/img/play-audio.png\" alt></a>\n"; return " <a href=\"#\" class=\"action-play-audio\"><img src=\"/mixed/img/play-audio.png\" title=\"Play audio (Alt + P)\" alt></a>\n";
},"17":function(container,depth0,helpers,partials,data) { },"17":function(container,depth0,helpers,partials,data) {
var stack1, helper, options, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function", buffer = var stack1, helper, options, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function", buffer =
" <div class=\"expression\"><ruby>"; " <div class=\"expression\"><ruby>";

View File

@ -33,6 +33,15 @@ function promiseCallback(promise, callback) {
} }
/*
* Commands
*/
function commandExec(command) {
instYomi().onCommand(command);
}
/* /*
* Instance * Instance
*/ */

View File

@ -25,12 +25,13 @@ window.yomichan = new class {
this.anki = new AnkiNull(); this.anki = new AnkiNull();
this.options = null; this.options = null;
chrome.runtime.onMessage.addListener(this.onMessage.bind(this)); this.translator.prepare().then(optionsLoad).then(this.optionsSet.bind(this)).then(() => {
if (chrome.runtime.onInstalled) { chrome.commands.onCommand.addListener(this.onCommand.bind(this));
chrome.runtime.onInstalled.addListener(this.onInstalled.bind(this)); chrome.runtime.onMessage.addListener(this.onMessage.bind(this));
} if (chrome.runtime.onInstalled) {
chrome.runtime.onInstalled.addListener(this.onInstalled.bind(this));
this.translator.prepare().then(optionsLoad).then(this.optionsSet.bind(this)); }
});
} }
optionsSet(options) { optionsSet(options) {
@ -55,7 +56,10 @@ window.yomichan = new class {
} }
noteFormat(definition, mode) { noteFormat(definition, mode) {
const note = {fields: {}, tags: this.options.anki.tags}; const note = {
fields: {},
tags: this.options.anki.tags
};
let fields = []; let fields = [];
if (mode === 'kanji') { if (mode === 'kanji') {
@ -116,8 +120,15 @@ window.yomichan = new class {
} }
definitionAdd(definition, mode) { definitionAdd(definition, mode) {
const note = this.noteFormat(definition, mode); let promise = Promise.resolve();
return this.anki.addNote(note); if (mode !== 'kanji') {
promise = audioInject(definition, this.options.anki.terms.fields);
}
return promise.then(() => {
const note = this.noteFormat(definition, mode);
return this.anki.addNote(note);
});
} }
definitionsAddable(definitions, modes) { definitionsAddable(definitions, modes) {
@ -153,34 +164,60 @@ window.yomichan = new class {
} }
} }
onCommand(command) {
const handlers = {
search: () => {
chrome.tabs.create({url: chrome.extension.getURL('/bg/search.html')});
},
help: () => {
chrome.tabs.create({url: 'https://foosoft.net/projects/yomichan/'});
},
options: () => {
chrome.runtime.openOptionsPage();
},
toggle: () => {
this.options.general.enable = !this.options.general.enable;
optionsSave(this.options).then(() => this.optionsSet(this.options));
}
};
const handler = handlers[command];
if (handler) {
handler();
}
}
onMessage(request, sender, callback) { onMessage(request, sender, callback) {
const handlers = new class { const handlers = new class {
api_optionsGet({callback}) { optionsGet({callback}) {
promiseCallback(optionsLoad(), callback); promiseCallback(optionsLoad(), callback);
} }
api_kanjiFind({text, callback}) { kanjiFind({text, callback}) {
promiseCallback(this.kanjiFind(text), callback); promiseCallback(this.kanjiFind(text), callback);
} }
api_termsFind({text, callback}) { termsFind({text, callback}) {
promiseCallback(this.termsFind(text), callback); promiseCallback(this.termsFind(text), callback);
} }
api_templateRender({template, data, callback}) { templateRender({template, data, callback}) {
promiseCallback(this.templateRender(template, data), callback); promiseCallback(this.templateRender(template, data), callback);
} }
api_definitionAdd({definition, mode, callback}) { definitionAdd({definition, mode, callback}) {
promiseCallback(this.definitionAdd(definition, mode), callback); promiseCallback(this.definitionAdd(definition, mode), callback);
} }
api_definitionsAddable({definitions, modes, callback}) { definitionsAddable({definitions, modes, callback}) {
promiseCallback(this.definitionsAddable(definitions, modes), callback); promiseCallback(this.definitionsAddable(definitions, modes), callback);
} }
}; };
const {action, params} = request, method = handlers[`api_${action}`]; const {action, params} = request, method = handlers[action];
if (typeof(method) === 'function') { if (typeof(method) === 'function') {
params.callback = callback; params.callback = callback;
method.call(this, params); method.call(this, params);

View File

@ -18,12 +18,12 @@
</head> </head>
<body> <body>
<p> <p>
<input type="checkbox" id="enable-search"> <input type="checkbox" id="enable-search" title="Toggle (Alt + Delete)">
</p> </p>
<p> <p>
<div class="btn-group" style="white-space: nowrap"> <div class="btn-group" style="white-space: nowrap">
<button type="button" id="open-search" title="Search" class="btn btn-default btn-xs glyphicon glyphicon-search"></button> <button type="button" id="open-search" title="Search (Alt + Insert)" class="btn btn-default btn-xs glyphicon glyphicon-search"></button>
<button type="button" id="open-options" title="Options" class="btn btn-default btn-xs glyphicon glyphicon-wrench"></button> <button type="button" id="open-options" title="Options (Alt + End)" class="btn btn-default btn-xs glyphicon glyphicon-wrench"></button>
<button type="button" id="open-help" title="Help" class="btn btn-default btn-xs glyphicon glyphicon-question-sign"></button> <button type="button" id="open-help" title="Help" class="btn btn-default btn-xs glyphicon glyphicon-question-sign"></button>
</div> </div>
</p> </p>

View File

@ -20,7 +20,7 @@
<form class="input-group"> <form class="input-group">
<input type="text" class="form-control" placeholder="Search for..." id="query" autofocus> <input type="text" class="form-control" placeholder="Search for..." id="query" autofocus>
<span class="input-group-btn"> <span class="input-group-btn">
<input type="submit" class="btn btn-default form-control" id="search" value="Search"> <input type="submit" class="btn btn-default form-control" id="search" value="Search" disabled>
</span> </span>
</form> </form>
</p> </p>
@ -34,6 +34,7 @@
<script src="/mixed/lib/jquery-3.1.1.min.js"></script> <script src="/mixed/lib/jquery-3.1.1.min.js"></script>
<script src="/bg/js/util.js"></script> <script src="/bg/js/util.js"></script>
<script src="/mixed/js/util.js"></script>
<script src="/mixed/js/display.js"></script> <script src="/mixed/js/display.js"></script>
<script src="/mixed/lib/wanakana.min.js"></script> <script src="/mixed/lib/wanakana.min.js"></script>
<script src="/bg/js/display-window.js"></script> <script src="/bg/js/display-window.js"></script>

View File

@ -6,28 +6,35 @@
<link rel="stylesheet" href="/mixed/lib/bootstrap-3.3.7-dist/css/bootstrap.min.css"> <link rel="stylesheet" href="/mixed/lib/bootstrap-3.3.7-dist/css/bootstrap.min.css">
<link rel="stylesheet" href="/mixed/lib/bootstrap-3.3.7-dist/css/bootstrap-theme.min.css"> <link rel="stylesheet" href="/mixed/lib/bootstrap-3.3.7-dist/css/bootstrap-theme.min.css">
<link rel="stylesheet" href="/mixed/css/frame.css"> <link rel="stylesheet" href="/mixed/css/frame.css">
<style type="text/css">
.entry {
padding-left: 10px;
padding-right: 10px;
}
</style>
</head> </head>
<body> <body>
<div class="container-fluid"> <div id="spinner">
<div id="spinner"> <img src="/mixed/img/spinner.gif">
<img src="/mixed/img/spinner.gif"> </div>
</div>
<div id="content"></div> <div id="content"></div>
<div id="orphan"> <div id="orphan">
<div class="container-fluid">
<h1>Yomichan Updated!</h1> <h1>Yomichan Updated!</h1>
<p> <p>
The Yomichan extension has been updated to a new version! In order to continue The Yomichan extension has been updated to a new version! In order to continue
viewing definitions on this page you must reload this tab or restart your browser. viewing definitions on this page you must reload this tab or restart your browser.
</p> </p>
</div> </div>
<script src="/mixed/lib/jquery-3.1.1.min.js"></script>
<script src="/mixed/lib/wanakana.min.js"></script>
<script src="/fg/js/util.js"></script>
<script src="/mixed/js/display.js"></script>
<script src="/fg/js/display-frame.js"></script>
</div> </div>
<script src="/mixed/lib/jquery-3.1.1.min.js"></script>
<script src="/mixed/lib/wanakana.min.js"></script>
<script src="/fg/js/util.js"></script>
<script src="/mixed/js/util.js"></script>
<script src="/mixed/js/display.js"></script>
<script src="/fg/js/display-frame.js"></script>
</body> </body>
</html> </html>

View File

@ -47,6 +47,10 @@ window.displayFrame = new class extends Display {
} }
} }
clearSearch() {
window.parent.postMessage('popupClose', '*');
}
showOrphaned() { showOrphaned() {
$('#content').hide(); $('#content').hide();
$('#orphan').show(); $('#orphan').show();
@ -54,20 +58,20 @@ window.displayFrame = new class extends Display {
onMessage(e) { onMessage(e) {
const handlers = new class { const handlers = new class {
api_showTermDefs({definitions, options, context}) { showTermDefs({definitions, options, context}) {
this.showTermDefs(definitions, options, context); this.showTermDefs(definitions, options, context);
} }
api_showKanjiDefs({definitions, options, context}) { showKanjiDefs({definitions, options, context}) {
this.showKanjiDefs(definitions, options, context); this.showKanjiDefs(definitions, options, context);
} }
api_showOrphaned() { showOrphaned() {
this.showOrphaned(); this.showOrphaned();
} }
}; };
const {action, params} = e.originalEvent.data, method = handlers[`api_${action}`]; const {action, params} = e.originalEvent.data, method = handlers[action];
if (typeof(method) === 'function') { if (typeof(method) === 'function') {
method.call(this, params); method.call(this, params);
} }

View File

@ -35,6 +35,7 @@ window.driver = new class {
window.addEventListener('mouseup', this.onMouseUp.bind(this)); window.addEventListener('mouseup', this.onMouseUp.bind(this));
window.addEventListener('mousemove', this.onMouseMove.bind(this)); window.addEventListener('mousemove', this.onMouseMove.bind(this));
window.addEventListener('resize', e => this.searchClear()); window.addEventListener('resize', e => this.searchClear());
window.addEventListener('message', this.onFrameMessage.bind(this));
chrome.runtime.onMessage.addListener(this.onBgMessage.bind(this)); chrome.runtime.onMessage.addListener(this.onBgMessage.bind(this));
}).catch(this.handleError.bind(this)); }).catch(this.handleError.bind(this));
} }
@ -45,14 +46,14 @@ window.driver = new class {
} }
popupTimerClear() { popupTimerClear() {
if (this.popupTimer !== null) { if (this.popupTimer) {
window.clearTimeout(this.popupTimer); window.clearTimeout(this.popupTimer);
this.popupTimer = null; this.popupTimer = null;
} }
} }
onMouseOver(e) { onMouseOver(e) {
if (e.target === this.popup.container && this.popuptimer !== null) { if (e.target === this.popup.container && this.popupTimer) {
this.popupTimerClear(); this.popupTimerClear();
} }
} }
@ -101,14 +102,30 @@ window.driver = new class {
} }
} }
onBgMessage({action, params}, sender, callback) { onFrameMessage(e) {
const handlers = new class { const handlers = {
api_optionsSet(options) { popupClose: () => {
this.options = options; this.searchClear();
} }
}; };
const method = handlers[`api_${action}`]; const handler = handlers[e.data];
if (handler) {
handler();
}
}
onBgMessage({action, params}, sender, callback) {
const handlers = new class {
optionsSet(options) {
this.options = options;
if (!this.options.enable) {
this.searchClear();
}
}
};
const method = handlers[action];
if (typeof(method) === 'function') { if (typeof(method) === 'function') {
method.call(this, params); method.call(this, params);
} }
@ -122,11 +139,11 @@ window.driver = new class {
} }
const textSource = docRangeFromPoint(point, this.options.scanning.imposter); const textSource = docRangeFromPoint(point, this.options.scanning.imposter);
if (textSource === null || !textSource.containsPoint(point)) { if (!textSource || !textSource.containsPoint(point)) {
return; return;
} }
if (this.lastTextSource !== null && this.lastTextSource.equals(textSource)) { if (this.lastTextSource && this.lastTextSource.equals(textSource)) {
return; return;
} }
@ -200,7 +217,7 @@ window.driver = new class {
docImposterDestroy(); docImposterDestroy();
this.popup.hide(); this.popup.hide();
if (this.options.scanning.selectText && this.lastTextSource !== null) { if (this.options.scanning.selectText && this.lastTextSource) {
this.lastTextSource.deselect(); this.lastTextSource.deselect();
} }

View File

@ -1,7 +1,7 @@
{ {
"manifest_version": 2, "manifest_version": 2,
"name": "Yomichan", "name": "Yomichan",
"version": "1.1.7", "version": "1.1.8",
"description": "Japanese dictionary with Anki integration", "description": "Japanese dictionary with Anki integration",
"icons": {"16": "mixed/img/icon16.png", "48": "mixed/img/icon48.png", "128": "mixed/img/icon128.png"}, "icons": {"16": "mixed/img/icon16.png", "48": "mixed/img/icon48.png", "128": "mixed/img/icon128.png"},
@ -31,6 +31,26 @@
"<all_urls>", "<all_urls>",
"storage" "storage"
], ],
"commands": {
"toggle": {
"suggested_key": {
"default": "Alt+Delete"
},
"description": "Toggle text scanning"
},
"search": {
"suggested_key": {
"default": "Alt+Insert"
},
"description": "Open search window"
},
"options": {
"suggested_key": {
"default": "Alt+End"
},
"description": "Open options page"
}
},
"web_accessible_resources": ["fg/frame.html"], "web_accessible_resources": ["fg/frame.html"],
"applications": { "applications": {
"gecko": { "gecko": {

View File

@ -52,7 +52,8 @@ hr {
*/ */
.entry { .entry {
padding: 15px 0px 15px 0px; padding-top: 10px;
padding-bottom: 10px;
} }
.tag-default { .tag-default {

Binary file not shown.

After

Width:  |  Height:  |  Size: 743 B

View File

@ -25,6 +25,9 @@ class Display {
this.audioCache = {}; this.audioCache = {};
this.responseCache = {}; this.responseCache = {};
this.sequence = 0; this.sequence = 0;
this.index = 0;
$(document).keydown(this.onKeyDown.bind(this));
} }
definitionAdd(definition, mode) { definitionAdd(definition, mode) {
@ -47,9 +50,17 @@ class Display {
throw 'override me'; throw 'override me';
} }
clearSearch() {
throw 'override me';
}
showTermDefs(definitions, options, context) { showTermDefs(definitions, options, context) {
window.focus();
this.spinner.hide(); this.spinner.hide();
this.definitions = definitions; this.definitions = definitions;
this.options = options;
this.context = context;
const sequence = ++this.sequence; const sequence = ++this.sequence;
const params = { const params = {
@ -68,43 +79,23 @@ class Display {
this.templateRender('terms.html', params).then(content => { this.templateRender('terms.html', params).then(content => {
this.container.html(content); this.container.html(content);
this.entryScroll(context && context.index || 0);
let offset = 0; $('.action-add-note').click(this.onAddNote.bind(this));
if (context && context.hasOwnProperty('index') && context.index < definitions.length) { $('.action-play-audio').click(this.onPlayAudio.bind(this));
const entry = $('.entry').eq(context.index); $('.kanji-link').click(this.onKanjiLookup.bind(this));
offset = entry.offset().top;
}
window.scrollTo(0, offset);
$('.action-add-note').click(this.onActionAddNote.bind(this));
$('.action-play-audio').click(e => {
e.preventDefault();
const index = Display.entryIndexFind($(e.currentTarget));
this.audioPlay(this.definitions[index]);
});
$('.kanji-link').click(e => {
e.preventDefault();
const link = $(e.target);
context = context || {};
context.source = {
definitions,
index: Display.entryIndexFind(link)
};
this.kanjiFind(link.text()).then(kanjiDefs => {
this.showKanjiDefs(kanjiDefs, options, context);
}).catch(this.handleError.bind(this));
});
return this.adderButtonsUpdate(['term-kanji', 'term-kana'], sequence); return this.adderButtonsUpdate(['term-kanji', 'term-kana'], sequence);
}).catch(this.handleError.bind(this)); }).catch(this.handleError.bind(this));
} }
showKanjiDefs(definitions, options, context) { showKanjiDefs(definitions, options, context) {
window.focus();
this.spinner.hide(); this.spinner.hide();
this.definitions = definitions; this.definitions = definitions;
this.options = options;
this.context = context;
const sequence = ++this.sequence; const sequence = ++this.sequence;
const params = { const params = {
@ -122,17 +113,10 @@ class Display {
this.templateRender('kanji.html', params).then(content => { this.templateRender('kanji.html', params).then(content => {
this.container.html(content); this.container.html(content);
window.scrollTo(0, 0); this.entryScroll(context && context.index || 0);
$('.action-add-note').click(this.onActionAddNote.bind(this)); $('.action-add-note').click(this.onAddNote.bind(this));
$('.source-term').click(e => { $('.source-term').click(this.onSourceTerm.bind(this));
e.preventDefault();
if (context && context.source) {
context.index = context.source.index;
this.showTermDefs(context.source.definitions, options, context);
}
});
return this.adderButtonsUpdate(['kanji'], sequence); return this.adderButtonsUpdate(['kanji'], sequence);
}).catch(this.handleError.bind(this)); }).catch(this.handleError.bind(this));
@ -159,31 +143,186 @@ class Display {
}); });
} }
onActionAddNote(e) { entryScroll(index, smooth) {
e.preventDefault(); index = Math.min(index, this.definitions.length - 1);
this.spinner.show(); index = Math.max(index, 0);
const link = $(e.currentTarget); $('.current').hide().eq(index).show();
const mode = link.data('mode');
const index = Display.entryIndexFind(link);
const definition = this.definitions[index];
let promise = Promise.resolve(); const container = $('html,body').stop();
if (mode !== 'kanji') { const entry = $('.entry').eq(index);
const filename = Display.audioBuildFilename(definition); const target = index === 0 ? 0 : entry.offset().top;
if (filename) {
promise = this.audioBuildUrl(definition).then(url => definition.audio = {url, filename}).catch(() => {}); if (smooth) {
} container.animate({scrollTop: target}, 200);
} else {
container.scrollTop(target);
} }
promise.then(() => { this.index = index;
return this.definitionAdd(definition, mode).then(success => { }
if (success) {
Display.adderButtonFind(index, mode).addClass('disabled'); onSourceTerm(e) {
} else { e.preventDefault();
this.handleError('note could not be added'); this.sourceBack();
}
onKanjiLookup(e) {
e.preventDefault();
const link = $(e.target);
const context = {
source: {
definitions: this.definitions,
index: Display.entryIndexFind(link)
}
};
if (this.context) {
context.sentence = this.context.sentence;
context.url = this.context.url;
}
this.kanjiFind(link.text()).then(kanjiDefs => {
this.showKanjiDefs(kanjiDefs, this.options, context);
}).catch(this.handleError.bind(this));
}
onPlayAudio(e) {
e.preventDefault();
const index = Display.entryIndexFind($(e.currentTarget));
this.audioPlay(this.definitions[index]);
}
onAddNote(e) {
e.preventDefault();
const link = $(e.currentTarget);
const index = Display.entryIndexFind(link);
this.noteAdd(this.definitions[index], link.data('mode'));
}
onKeyDown(e) {
const noteTryAdd = mode => {
const button = Display.adderButtonFind(this.index, mode);
if (button.length !== 0 && !button.hasClass('disabled')) {
this.noteAdd(this.definitions[this.index], mode);
}
};
const handlers = {
27: /* escape */ () => {
this.clearSearch();
return true;
},
33: /* page up */ () => {
if (e.altKey) {
this.entryScroll(this.index - 3, true);
return true;
} }
}); },
34: /* page down */ () => {
if (e.altKey) {
this.entryScroll(this.index + 3, true);
return true;
}
},
35: /* end */ () => {
if (e.altKey) {
this.entryScroll(this.definitions.length - 1, true);
return true;
}
},
36: /* home */ () => {
if (e.altKey) {
this.entryScroll(0, true);
return true;
}
},
38: /* up */ () => {
if (e.altKey) {
this.entryScroll(this.index - 1, true);
return true;
}
},
40: /* down */ () => {
if (e.altKey) {
this.entryScroll(this.index + 1, true);
return true;
}
},
66: /* b */ () => {
if (e.altKey) {
this.sourceBack();
return true;
}
},
69: /* e */ () => {
if (e.altKey) {
noteTryAdd('term-kanji');
return true;
}
},
75: /* k */ () => {
if (e.altKey) {
noteTryAdd('kanji');
return true;
}
},
82: /* r */ () => {
if (e.altKey) {
noteTryAdd('term-kana');
return true;
}
},
80: /* p */ () => {
if (e.altKey) {
if ($('.entry').eq(this.index).data('type') === 'term') {
this.audioPlay(this.definitions[this.index]);
}
return true;
}
}
};
const handler = handlers[e.keyCode];
if (handler && handler()) {
e.preventDefault();
}
}
sourceBack() {
if (this.context && this.context.source) {
const context = {
url: this.context.source.url,
sentence: this.context.source.sentence,
index: this.context.source.index
};
this.showTermDefs(this.context.source.definitions, this.options, context);
}
}
noteAdd(definition, mode) {
this.spinner.show();
return this.definitionAdd(definition, mode).then(success => {
if (success) {
const index = this.definitions.indexOf(definition);
Display.adderButtonFind(index, mode).addClass('disabled');
} else {
this.handleError('note could not be added');
}
}).catch(this.handleError.bind(this)).then(() => this.spinner.hide()); }).catch(this.handleError.bind(this)).then(() => this.spinner.hide());
} }
@ -194,7 +333,7 @@ class Display {
this.audioCache[key].pause(); this.audioCache[key].pause();
} }
this.audioBuildUrl(definition).then(url => { audioBuildUrl(definition, this.responseCache).then(url => {
if (!url) { if (!url) {
url = '/mixed/mp3/button.mp3'; url = '/mixed/mp3/button.mp3';
} }
@ -217,79 +356,6 @@ class Display {
}).catch(this.handleError.bind(this)).then(() => this.spinner.hide()); }).catch(this.handleError.bind(this)).then(() => this.spinner.hide());
} }
audioBuildUrl(definition) {
return new Promise((resolve, reject) => {
const response = this.responseCache[definition.expression];
if (response) {
resolve(response);
return;
}
const data = {
post: 'dictionary_reference',
match_type: 'exact',
search_query: definition.expression
};
const params = [];
for (const key in data) {
params.push(`${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`);
}
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://www.japanesepod101.com/learningcenter/reference/dictionary_post');
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.addEventListener('error', () => reject('failed to scrape audio data'));
xhr.addEventListener('load', () => {
this.responseCache[definition.expression] = xhr.responseText;
resolve(xhr.responseText);
});
xhr.send(params.join('&'));
}).then(response => {
const dom = new DOMParser().parseFromString(response, 'text/html');
const entries = [];
for (const row of dom.getElementsByClassName('dc-result-row')) {
try {
const url = row.getElementsByClassName('ill-onebuttonplayer').item(0).getAttribute('data-url');
const expression = dom.getElementsByClassName('dc-vocab').item(0).innerText;
const reading = dom.getElementsByClassName('dc-vocab_kana').item(0).innerText;
if (url && expression && reading) {
entries.push({url, expression, reading});
}
} catch (e) {
// NOP
}
}
return entries;
}).then(entries => {
for (const entry of entries) {
if (!definition.reading || definition.reading === entry.reading) {
return entry.url;
}
}
});
}
static audioBuildFilename(definition) {
if (!definition.reading && !definition.expression) {
return;
}
let filename = 'yomichan';
if (definition.reading) {
filename += `_${definition.reading}`;
}
if (definition.expression) {
filename += `_${definition.expression}`;
}
return filename += '.mp3';
}
static entryIndexFind(element) { static entryIndexFind(element) {
return $('.entry').index(element.closest('.entry')); return $('.entry').index(element.closest('.entry'));
} }

104
ext/mixed/js/util.js Normal file
View File

@ -0,0 +1,104 @@
/*
* Copyright (C) 2017 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/>.
*/
/*
* Audio
*/
function audioBuildUrl(definition, cache={}) {
return new Promise((resolve, reject) => {
const response = cache[definition.expression];
if (response) {
resolve(response);
} else {
const data = {
post: 'dictionary_reference',
match_type: 'exact',
search_query: definition.expression
};
const params = [];
for (const key in data) {
params.push(`${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`);
}
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://www.japanesepod101.com/learningcenter/reference/dictionary_post');
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.addEventListener('error', () => reject('failed to scrape audio data'));
xhr.addEventListener('load', () => {
cache[definition.expression] = xhr.responseText;
resolve(xhr.responseText);
});
xhr.send(params.join('&'));
}
}).then(response => {
const dom = new DOMParser().parseFromString(response, 'text/html');
for (const row of dom.getElementsByClassName('dc-result-row')) {
try {
const url = row.getElementsByClassName('ill-onebuttonplayer').item(0).getAttribute('data-url');
const reading = row.getElementsByClassName('dc-vocab_kana').item(0).innerText;
if (url && reading && (!definition.reading || definition.reading === reading)) {
return url;
}
} catch (e) {
// NOP
}
}
});
}
function audioBuildFilename(definition) {
if (definition.reading && definition.expression) {
let filename = 'yomichan';
if (definition.reading) {
filename += `_${definition.reading}`;
}
if (definition.expression) {
filename += `_${definition.expression}`;
}
return filename += '.mp3';
}
}
function audioInject(definition, fields) {
const filename = audioBuildFilename(definition);
if (!filename) {
return Promise.resolve(true);
}
let usesAudio = false;
for (const name in fields) {
if (fields[name].includes('{audio}')) {
usesAudio = true;
break;
}
}
if (!usesAudio) {
return Promise.resolve(true);
}
return audioBuildUrl(definition).then(url => {
definition.audio = {url, filename};
return true;
}).catch(() => false);
}

View File

@ -1,11 +1,12 @@
{{#*inline "kanji"}} {{#*inline "kanji"}}
<div class="entry"> <div class="entry" data-type="kanji">
<div class="actions"> <div class="actions">
<img src="/mixed/img/entry-current.png" class="current" title="Current entry (Alt + Up/Down/Home/End/PgUp/PgDn)" alt>
{{#if addable}} {{#if addable}}
<a href="#" title="Add Kanji" class="action-add-note pending disabled" data-mode="kanji"><img src="/mixed/img/add-kanji.png" alt></a> <a href="#" class="action-add-note pending disabled" data-mode="kanji"><img src="/mixed/img/add-kanji.png" title="Add Kanji (Alt + K)" alt></a>
{{/if}} {{/if}}
{{#if source}} {{#if source}}
<a href="#" title="Source term" class="source-term"><img src="/mixed/img/source-term.png" alt></a> <a href="#" class="source-term"><img src="/mixed/img/source-term.png" title="Source term (Alt + B)" alt></a>
{{/if}} {{/if}}
</div> </div>

View File

@ -18,14 +18,15 @@
{{/inline}} {{/inline}}
{{#*inline "term"}} {{#*inline "term"}}
<div class="entry"> <div class="entry" data-type="term">
<div class="actions"> <div class="actions">
<img src="/mixed/img/entry-current.png" class="current" title="Current entry (Alt + Up/Down/Home/End/PgUp/PgDn)" alt>
{{#if addable}} {{#if addable}}
<a href="#" title="Add expression" class="action-add-note pending disabled" data-mode="term-kanji"><img src="/mixed/img/add-term-kanji.png" alt></a> <a href="#" class="action-add-note pending disabled" data-mode="term-kanji"><img src="/mixed/img/add-term-kanji.png" title="Add expression (Alt + E)" alt></a>
<a href="#" title="Add reading" class="action-add-note pending disabled" data-mode="term-kana"><img src="/mixed/img/add-term-kana.png" alt></a> <a href="#" class="action-add-note pending disabled" data-mode="term-kana"><img src="/mixed/img/add-term-kana.png" title="Add reading (Alt + R)" alt></a>
{{/if}} {{/if}}
{{#if playback}} {{#if playback}}
<a href="#" title="Play audio" class="action-play-audio"><img src="/mixed/img/play-audio.png" alt></a> <a href="#" class="action-play-audio"><img src="/mixed/img/play-audio.png" title="Play audio (Alt + P)" alt></a>
{{/if}} {{/if}}
</div> </div>