Merge branch 'dev'
This commit is contained in:
commit
37321e8d52
@ -7,6 +7,7 @@
|
||||
<script src="/mixed/lib/handlebars.min.js"></script>
|
||||
<script src="/mixed/lib/dexie.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/util.js"></script>
|
||||
<script src="/bg/js/anki-connect.js"></script>
|
||||
|
@ -7,7 +7,7 @@
|
||||
<link rel="stylesheet" type="text/css" href="/mixed/lib/bootstrap-3.3.7-dist/css/bootstrap-theme.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container-fluid">
|
||||
<div class="container">
|
||||
<div class="page-header">
|
||||
<h1>Welcome to Yomichan!</h1>
|
||||
</div>
|
||||
@ -23,11 +23,12 @@
|
||||
</p>
|
||||
|
||||
<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>Import the dictionaries (bundled or custom) you wish to use for term and Kanji searches.</li>
|
||||
<li>Hold down <kbd>Shift</kbd> (or the middle mouse button) as you hover over text to see term definitions.</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>Click on Kanji in the definition window to view additional information about that character.</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>Click on the <em>monkey wrench</em> icon in the middle to open the options page.</li>
|
||||
<li>Import the dictionaries you wish to use for term and Kanji searches.</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 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>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -90,7 +90,7 @@ class Deinflection {
|
||||
source: this.term,
|
||||
rules: this.rules,
|
||||
definitions: this.definitions,
|
||||
reasons: [this.reason]
|
||||
reasons: this.reason.length > 0 ? [this.reason] : []
|
||||
}];
|
||||
}
|
||||
|
||||
|
@ -20,8 +20,13 @@
|
||||
window.displayWindow = new class extends Display {
|
||||
constructor() {
|
||||
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) {
|
||||
@ -44,6 +49,10 @@ window.displayWindow = new class extends Display {
|
||||
window.alert(`Error: ${error}`);
|
||||
}
|
||||
|
||||
clearSearch() {
|
||||
$('#query').focus().select();
|
||||
}
|
||||
|
||||
onSearch(e) {
|
||||
e.preventDefault();
|
||||
$('#intro').slideUp();
|
||||
|
@ -18,17 +18,14 @@
|
||||
|
||||
|
||||
$(document).ready(() => {
|
||||
$('#open-search').click(() => window.open(chrome.extension.getURL('/bg/search.html')));
|
||||
$('#open-options').click(() => chrome.runtime.openOptionsPage());
|
||||
$('#open-help').click(() => window.open('http://foosoft.net/projects/yomichan'));
|
||||
$('#open-search').click(() => commandExec('search'));
|
||||
$('#open-options').click(() => commandExec('options'));
|
||||
$('#open-help').click(() => commandExec('help'));
|
||||
|
||||
optionsLoad().then(options => {
|
||||
const toggle = $('#enable-search');
|
||||
toggle.prop('checked', options.general.enable).change();
|
||||
toggle.bootstrapToggle();
|
||||
toggle.change(() => {
|
||||
options.general.enable = toggle.prop('checked');
|
||||
optionsSave(options).then(() => instYomi().optionsSet(options));
|
||||
});
|
||||
toggle.change(() => commandExec('toggle'));
|
||||
});
|
||||
});
|
||||
|
@ -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) {
|
||||
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.source : depth0),{"name":"if","hash":{},"fn":container.program(4, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
|
||||
+ " </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 : "")
|
||||
+ " </div>\n</div>\n";
|
||||
},"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) {
|
||||
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) {
|
||||
var stack1;
|
||||
|
||||
@ -442,7 +442,7 @@ templates['terms.html'] = template({"1":function(container,depth0,helpers,partia
|
||||
},"12":function(container,depth0,helpers,partials,data) {
|
||||
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.playback : depth0),{"name":"if","hash":{},"fn":container.program(15, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "")
|
||||
+ " </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 : "")
|
||||
+ " </div>\n</div>\n";
|
||||
},"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) {
|
||||
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) {
|
||||
var stack1, helper, options, alias1=depth0 != null ? depth0 : {}, alias2=helpers.helperMissing, alias3="function", buffer =
|
||||
" <div class=\"expression\"><ruby>";
|
||||
|
@ -33,6 +33,15 @@ function promiseCallback(promise, callback) {
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Commands
|
||||
*/
|
||||
|
||||
function commandExec(command) {
|
||||
instYomi().onCommand(command);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Instance
|
||||
*/
|
||||
|
@ -25,12 +25,13 @@ window.yomichan = new class {
|
||||
this.anki = new AnkiNull();
|
||||
this.options = null;
|
||||
|
||||
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));
|
||||
this.translator.prepare().then(optionsLoad).then(this.optionsSet.bind(this)).then(() => {
|
||||
chrome.commands.onCommand.addListener(this.onCommand.bind(this));
|
||||
chrome.runtime.onMessage.addListener(this.onMessage.bind(this));
|
||||
if (chrome.runtime.onInstalled) {
|
||||
chrome.runtime.onInstalled.addListener(this.onInstalled.bind(this));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
optionsSet(options) {
|
||||
@ -55,7 +56,10 @@ window.yomichan = new class {
|
||||
}
|
||||
|
||||
noteFormat(definition, mode) {
|
||||
const note = {fields: {}, tags: this.options.anki.tags};
|
||||
const note = {
|
||||
fields: {},
|
||||
tags: this.options.anki.tags
|
||||
};
|
||||
|
||||
let fields = [];
|
||||
if (mode === 'kanji') {
|
||||
@ -116,8 +120,15 @@ window.yomichan = new class {
|
||||
}
|
||||
|
||||
definitionAdd(definition, mode) {
|
||||
const note = this.noteFormat(definition, mode);
|
||||
return this.anki.addNote(note);
|
||||
let promise = Promise.resolve();
|
||||
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) {
|
||||
@ -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) {
|
||||
const handlers = new class {
|
||||
api_optionsGet({callback}) {
|
||||
optionsGet({callback}) {
|
||||
promiseCallback(optionsLoad(), callback);
|
||||
}
|
||||
|
||||
api_kanjiFind({text, callback}) {
|
||||
kanjiFind({text, callback}) {
|
||||
promiseCallback(this.kanjiFind(text), callback);
|
||||
}
|
||||
|
||||
api_termsFind({text, callback}) {
|
||||
termsFind({text, callback}) {
|
||||
promiseCallback(this.termsFind(text), callback);
|
||||
}
|
||||
|
||||
api_templateRender({template, data, callback}) {
|
||||
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);
|
||||
}
|
||||
|
||||
api_definitionsAddable({definitions, modes, callback}) {
|
||||
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') {
|
||||
params.callback = callback;
|
||||
method.call(this, params);
|
||||
|
@ -18,12 +18,12 @@
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
<input type="checkbox" id="enable-search">
|
||||
<input type="checkbox" id="enable-search" title="Toggle (Alt + Delete)">
|
||||
</p>
|
||||
<p>
|
||||
<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-options" title="Options" class="btn btn-default btn-xs glyphicon glyphicon-wrench"></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 (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>
|
||||
</div>
|
||||
</p>
|
||||
|
@ -20,7 +20,7 @@
|
||||
<form class="input-group">
|
||||
<input type="text" class="form-control" placeholder="Search for..." id="query" autofocus>
|
||||
<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>
|
||||
</form>
|
||||
</p>
|
||||
@ -34,6 +34,7 @@
|
||||
|
||||
<script src="/mixed/lib/jquery-3.1.1.min.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/lib/wanakana.min.js"></script>
|
||||
<script src="/bg/js/display-window.js"></script>
|
||||
|
@ -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-theme.min.css">
|
||||
<link rel="stylesheet" href="/mixed/css/frame.css">
|
||||
<style type="text/css">
|
||||
.entry {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container-fluid">
|
||||
<div id="spinner">
|
||||
<img src="/mixed/img/spinner.gif">
|
||||
</div>
|
||||
<div id="spinner">
|
||||
<img src="/mixed/img/spinner.gif">
|
||||
</div>
|
||||
|
||||
<div id="content"></div>
|
||||
<div id="content"></div>
|
||||
|
||||
<div id="orphan">
|
||||
<div id="orphan">
|
||||
<div class="container-fluid">
|
||||
<h1>Yomichan Updated!</h1>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</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>
|
||||
|
||||
<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>
|
||||
</html>
|
||||
|
@ -47,6 +47,10 @@ window.displayFrame = new class extends Display {
|
||||
}
|
||||
}
|
||||
|
||||
clearSearch() {
|
||||
window.parent.postMessage('popupClose', '*');
|
||||
}
|
||||
|
||||
showOrphaned() {
|
||||
$('#content').hide();
|
||||
$('#orphan').show();
|
||||
@ -54,20 +58,20 @@ window.displayFrame = new class extends Display {
|
||||
|
||||
onMessage(e) {
|
||||
const handlers = new class {
|
||||
api_showTermDefs({definitions, options, context}) {
|
||||
showTermDefs({definitions, options, context}) {
|
||||
this.showTermDefs(definitions, options, context);
|
||||
}
|
||||
|
||||
api_showKanjiDefs({definitions, options, context}) {
|
||||
showKanjiDefs({definitions, options, context}) {
|
||||
this.showKanjiDefs(definitions, options, context);
|
||||
}
|
||||
|
||||
api_showOrphaned() {
|
||||
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') {
|
||||
method.call(this, params);
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ window.driver = new class {
|
||||
window.addEventListener('mouseup', this.onMouseUp.bind(this));
|
||||
window.addEventListener('mousemove', this.onMouseMove.bind(this));
|
||||
window.addEventListener('resize', e => this.searchClear());
|
||||
window.addEventListener('message', this.onFrameMessage.bind(this));
|
||||
chrome.runtime.onMessage.addListener(this.onBgMessage.bind(this));
|
||||
}).catch(this.handleError.bind(this));
|
||||
}
|
||||
@ -45,14 +46,14 @@ window.driver = new class {
|
||||
}
|
||||
|
||||
popupTimerClear() {
|
||||
if (this.popupTimer !== null) {
|
||||
if (this.popupTimer) {
|
||||
window.clearTimeout(this.popupTimer);
|
||||
this.popupTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
onMouseOver(e) {
|
||||
if (e.target === this.popup.container && this.popuptimer !== null) {
|
||||
if (e.target === this.popup.container && this.popupTimer) {
|
||||
this.popupTimerClear();
|
||||
}
|
||||
}
|
||||
@ -101,14 +102,30 @@ window.driver = new class {
|
||||
}
|
||||
}
|
||||
|
||||
onBgMessage({action, params}, sender, callback) {
|
||||
const handlers = new class {
|
||||
api_optionsSet(options) {
|
||||
this.options = options;
|
||||
onFrameMessage(e) {
|
||||
const handlers = {
|
||||
popupClose: () => {
|
||||
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') {
|
||||
method.call(this, params);
|
||||
}
|
||||
@ -122,11 +139,11 @@ window.driver = new class {
|
||||
}
|
||||
|
||||
const textSource = docRangeFromPoint(point, this.options.scanning.imposter);
|
||||
if (textSource === null || !textSource.containsPoint(point)) {
|
||||
if (!textSource || !textSource.containsPoint(point)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.lastTextSource !== null && this.lastTextSource.equals(textSource)) {
|
||||
if (this.lastTextSource && this.lastTextSource.equals(textSource)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -200,7 +217,7 @@ window.driver = new class {
|
||||
docImposterDestroy();
|
||||
this.popup.hide();
|
||||
|
||||
if (this.options.scanning.selectText && this.lastTextSource !== null) {
|
||||
if (this.options.scanning.selectText && this.lastTextSource) {
|
||||
this.lastTextSource.deselect();
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"name": "Yomichan",
|
||||
"version": "1.1.7",
|
||||
"version": "1.1.8",
|
||||
|
||||
"description": "Japanese dictionary with Anki integration",
|
||||
"icons": {"16": "mixed/img/icon16.png", "48": "mixed/img/icon48.png", "128": "mixed/img/icon128.png"},
|
||||
@ -31,6 +31,26 @@
|
||||
"<all_urls>",
|
||||
"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"],
|
||||
"applications": {
|
||||
"gecko": {
|
||||
|
@ -52,7 +52,8 @@ hr {
|
||||
*/
|
||||
|
||||
.entry {
|
||||
padding: 15px 0px 15px 0px;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.tag-default {
|
||||
|
BIN
ext/mixed/img/entry-current.png
Normal file
BIN
ext/mixed/img/entry-current.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 743 B |
@ -25,6 +25,9 @@ class Display {
|
||||
this.audioCache = {};
|
||||
this.responseCache = {};
|
||||
this.sequence = 0;
|
||||
this.index = 0;
|
||||
|
||||
$(document).keydown(this.onKeyDown.bind(this));
|
||||
}
|
||||
|
||||
definitionAdd(definition, mode) {
|
||||
@ -47,9 +50,17 @@ class Display {
|
||||
throw 'override me';
|
||||
}
|
||||
|
||||
clearSearch() {
|
||||
throw 'override me';
|
||||
}
|
||||
|
||||
showTermDefs(definitions, options, context) {
|
||||
window.focus();
|
||||
|
||||
this.spinner.hide();
|
||||
this.definitions = definitions;
|
||||
this.options = options;
|
||||
this.context = context;
|
||||
|
||||
const sequence = ++this.sequence;
|
||||
const params = {
|
||||
@ -68,43 +79,23 @@ class Display {
|
||||
|
||||
this.templateRender('terms.html', params).then(content => {
|
||||
this.container.html(content);
|
||||
this.entryScroll(context && context.index || 0);
|
||||
|
||||
let offset = 0;
|
||||
if (context && context.hasOwnProperty('index') && context.index < definitions.length) {
|
||||
const entry = $('.entry').eq(context.index);
|
||||
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));
|
||||
});
|
||||
$('.action-add-note').click(this.onAddNote.bind(this));
|
||||
$('.action-play-audio').click(this.onPlayAudio.bind(this));
|
||||
$('.kanji-link').click(this.onKanjiLookup.bind(this));
|
||||
|
||||
return this.adderButtonsUpdate(['term-kanji', 'term-kana'], sequence);
|
||||
}).catch(this.handleError.bind(this));
|
||||
}
|
||||
|
||||
showKanjiDefs(definitions, options, context) {
|
||||
window.focus();
|
||||
|
||||
this.spinner.hide();
|
||||
this.definitions = definitions;
|
||||
this.options = options;
|
||||
this.context = context;
|
||||
|
||||
const sequence = ++this.sequence;
|
||||
const params = {
|
||||
@ -122,17 +113,10 @@ class Display {
|
||||
|
||||
this.templateRender('kanji.html', params).then(content => {
|
||||
this.container.html(content);
|
||||
window.scrollTo(0, 0);
|
||||
this.entryScroll(context && context.index || 0);
|
||||
|
||||
$('.action-add-note').click(this.onActionAddNote.bind(this));
|
||||
$('.source-term').click(e => {
|
||||
e.preventDefault();
|
||||
|
||||
if (context && context.source) {
|
||||
context.index = context.source.index;
|
||||
this.showTermDefs(context.source.definitions, options, context);
|
||||
}
|
||||
});
|
||||
$('.action-add-note').click(this.onAddNote.bind(this));
|
||||
$('.source-term').click(this.onSourceTerm.bind(this));
|
||||
|
||||
return this.adderButtonsUpdate(['kanji'], sequence);
|
||||
}).catch(this.handleError.bind(this));
|
||||
@ -159,31 +143,186 @@ class Display {
|
||||
});
|
||||
}
|
||||
|
||||
onActionAddNote(e) {
|
||||
e.preventDefault();
|
||||
this.spinner.show();
|
||||
entryScroll(index, smooth) {
|
||||
index = Math.min(index, this.definitions.length - 1);
|
||||
index = Math.max(index, 0);
|
||||
|
||||
const link = $(e.currentTarget);
|
||||
const mode = link.data('mode');
|
||||
const index = Display.entryIndexFind(link);
|
||||
const definition = this.definitions[index];
|
||||
$('.current').hide().eq(index).show();
|
||||
|
||||
let promise = Promise.resolve();
|
||||
if (mode !== 'kanji') {
|
||||
const filename = Display.audioBuildFilename(definition);
|
||||
if (filename) {
|
||||
promise = this.audioBuildUrl(definition).then(url => definition.audio = {url, filename}).catch(() => {});
|
||||
}
|
||||
const container = $('html,body').stop();
|
||||
const entry = $('.entry').eq(index);
|
||||
const target = index === 0 ? 0 : entry.offset().top;
|
||||
|
||||
if (smooth) {
|
||||
container.animate({scrollTop: target}, 200);
|
||||
} else {
|
||||
container.scrollTop(target);
|
||||
}
|
||||
|
||||
promise.then(() => {
|
||||
return this.definitionAdd(definition, mode).then(success => {
|
||||
if (success) {
|
||||
Display.adderButtonFind(index, mode).addClass('disabled');
|
||||
} else {
|
||||
this.handleError('note could not be added');
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
onSourceTerm(e) {
|
||||
e.preventDefault();
|
||||
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());
|
||||
}
|
||||
|
||||
@ -194,7 +333,7 @@ class Display {
|
||||
this.audioCache[key].pause();
|
||||
}
|
||||
|
||||
this.audioBuildUrl(definition).then(url => {
|
||||
audioBuildUrl(definition, this.responseCache).then(url => {
|
||||
if (!url) {
|
||||
url = '/mixed/mp3/button.mp3';
|
||||
}
|
||||
@ -217,79 +356,6 @@ class Display {
|
||||
}).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) {
|
||||
return $('.entry').index(element.closest('.entry'));
|
||||
}
|
||||
|
104
ext/mixed/js/util.js
Normal file
104
ext/mixed/js/util.js
Normal 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);
|
||||
}
|
@ -1,11 +1,12 @@
|
||||
{{#*inline "kanji"}}
|
||||
<div class="entry">
|
||||
<div class="entry" data-type="kanji">
|
||||
<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}}
|
||||
<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 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}}
|
||||
</div>
|
||||
|
||||
|
@ -18,14 +18,15 @@
|
||||
{{/inline}}
|
||||
|
||||
{{#*inline "term"}}
|
||||
<div class="entry">
|
||||
<div class="entry" data-type="term">
|
||||
<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}}
|
||||
<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="#" 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-kanji"><img src="/mixed/img/add-term-kanji.png" title="Add expression (Alt + E)" 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 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}}
|
||||
</div>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user