This commit is contained in:
Alex Yatskov 2017-08-13 16:11:51 -07:00
parent 7fbe2ddaf3
commit aac2a58b5f
8 changed files with 288 additions and 272 deletions

View File

@ -20,6 +20,7 @@
<script src="/bg/js/templates.js"></script> <script src="/bg/js/templates.js"></script>
<script src="/bg/js/translator.js"></script> <script src="/bg/js/translator.js"></script>
<script src="/bg/js/util.js"></script> <script src="/bg/js/util.js"></script>
<script src="/bg/js/util.js"></script>
<script src="/mixed/js/audio.js"></script> <script src="/mixed/js/audio.js"></script>
<script src="/mixed/js/japanese.js"></script> <script src="/mixed/js/japanese.js"></script>
<script src="/mixed/js/request.js"></script> <script src="/mixed/js/request.js"></script>

View File

@ -17,26 +17,26 @@
*/ */
window.displayWindow = new class extends Display { window.yomichan_window = new class extends Display {
constructor() { constructor() {
super($('#spinner'), $('#content')); super($('#spinner'), $('#content'));
this.search = $('#search').click(this.onSearch.bind(this)); this.search = $('#search').click(this.onSearch.bind(this));
this.query = $('#query').on('input', this.inputSearch.bind(this)); this.query = $('#query').on('input', this.onSearchInput.bind(this));
this.intro = $('#intro'); this.intro = $('#intro');
window.wanakana.bind(this.query.get(0)); window.wanakana.bind(this.query.get(0));
} }
handleError(error) { onError(error) {
window.alert(`Error: ${error}`); window.alert(`Error: ${error}`);
} }
clearSearch() { onSearchClear() {
this.query.focus().select(); this.query.focus().select();
} }
inputSearch() { onSearchInput() {
this.search.prop('disabled', this.query.val().length === 0); this.search.prop('disabled', this.query.val().length === 0);
} }
@ -46,9 +46,9 @@ window.displayWindow = new class extends Display {
try { try {
this.intro.slideUp(); this.intro.slideUp();
const {length, definitions} = await apiTermsFind(this.query.val()); const {length, definitions} = await apiTermsFind(this.query.val());
super.showTermDefs(definitions, await apiOptionsGet()); super.termsShow(definitions, await apiOptionsGet());
} catch (e) { } catch (e) {
this.handleError(e); this.onError(e);
} }
} }
}; };

View File

@ -40,10 +40,10 @@
<script src="/bg/js/dictionary.js"></script> <script src="/bg/js/dictionary.js"></script>
<script src="/bg/js/handlebars.js"></script> <script src="/bg/js/handlebars.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="/mixed/js/audio.js"></script> <script src="/mixed/js/audio.js"></script>
<script src="/mixed/js/display.js"></script> <script src="/mixed/js/display.js"></script>
<script src="/mixed/js/japanese.js"></script> <script src="/mixed/js/japanese.js"></script>
<script src="/mixed/js/request.js"></script>
<script src="/bg/js/display-window.js"></script> <script src="/bg/js/display-window.js"></script>
</body> </body>

View File

@ -18,9 +18,9 @@
<img src="/mixed/img/spinner.gif"> <img src="/mixed/img/spinner.gif">
</div> </div>
<div id="content"></div> <div id="definitions"></div>
<div id="orphan"> <div id="error-orphaned">
<div class="container-fluid"> <div class="container-fluid">
<h1>Yomichan Updated!</h1> <h1>Yomichan Updated!</h1>
<p> <p>

View File

@ -17,65 +17,45 @@
*/ */
window.displayFrame = new class extends Display { window.yomichan_frame = new class extends Display {
constructor() { constructor() {
super($('#spinner'), $('#content')); super($('#spinner'), $('#content'));
$(window).on('message', this.onMessage.bind(this)); $(window).on('message', this.onMessage.bind(this));
} }
definitionAdd(definition, mode) { onError(error) {
return apiDefinitionAdd(definition, mode); if (window.yomichan_orphaned) {
} this.onOrphaned();
definitionsAddable(definitions, modes) {
return apiDefinitionsAddable(definitions, modes);
}
noteView(noteId) {
return apiNoteView(noteId);
}
templateRender(template, data) {
return apiTemplateRender(template, data);
}
kanjiFind(character) {
return apiKanjiFind(character);
}
handleError(error) {
if (window.yomichanOrphaned) {
this.showOrphaned();
} else { } else {
window.alert(`Error: ${error}`); window.alert(`Error: ${error}`);
} }
} }
clearSearch() { onOrphaned() {
$('#definitions').hide();
$('#error-orphaned').show();
}
onSearchClear() {
window.parent.postMessage('popupClose', '*'); window.parent.postMessage('popupClose', '*');
} }
selectionCopy() { onSelectionCopy() {
window.parent.postMessage('selectionCopy', '*'); window.parent.postMessage('selectionCopy', '*');
} }
showOrphaned() {
$('#content').hide();
$('#orphan').show();
}
onMessage(e) { onMessage(e) {
const handlers = { const handlers = {
showTermDefs: ({definitions, options, context}) => { termsShow: ({definitions, options, context}) => {
this.showTermDefs(definitions, options, context); this.termsShow(definitions, options, context);
}, },
showKanjiDefs: ({definitions, options, context}) => { kanjiShow: ({definitions, options, context}) => {
this.showKanjiDefs(definitions, options, context); this.kanjiShow(definitions, options, context);
}, },
showOrphaned: () => { orphaned: () => {
this.showOrphaned(); this.onOrphaned();
} }
}; };
@ -89,8 +69,8 @@ window.displayFrame = new class extends Display {
onKeyDown(e) { onKeyDown(e) {
const handlers = { const handlers = {
67: /* c */ () => { 67: /* c */ () => {
if (e.ctrlKey && window.getSelection().toString() === '') { if (e.ctrlKey && !window.getSelection().toString()) {
this.selectionCopy(); this.onSelectionCopy();
return true; return true;
} }
} }

View File

@ -17,7 +17,7 @@
*/ */
window.yomichanFrontend = new class { window.yomichan_frontend = new class {
constructor() { constructor() {
this.popup = new Popup(); this.popup = new Popup();
this.popupTimer = null; this.popupTimer = null;
@ -27,17 +27,23 @@ window.yomichanFrontend = new class {
this.lastTextSource = null; this.lastTextSource = null;
this.pendingLookup = false; this.pendingLookup = false;
this.options = null; this.options = null;
}
apiOptionsGet().then(options => { async prepare() {
this.options = options; try {
window.addEventListener('mouseover', this.onMouseOver.bind(this)); this.options = await apiOptionsGet();
window.addEventListener('mousedown', this.onMouseDown.bind(this)); } catch (e) {
window.addEventListener('mouseup', this.onMouseUp.bind(this)); this.onError(e);
window.addEventListener('mousemove', this.onMouseMove.bind(this)); }
window.addEventListener('resize', e => this.searchClear());
window.addEventListener('message', this.onFrameMessage.bind(this)); window.addEventListener('message', this.onFrameMessage.bind(this));
chrome.runtime.onMessage.addListener(this.onBgMessage.bind(this)); window.addEventListener('mousedown', this.onMouseDown.bind(this));
}).catch(this.handleError.bind(this)); window.addEventListener('mousemove', this.onMouseMove.bind(this));
window.addEventListener('mouseover', this.onMouseOver.bind(this));
window.addEventListener('mouseup', this.onMouseUp.bind(this));
window.addEventListener('resize', this.onResize.bind(this));
chrome.runtime.onMessage.addListener(this.onBgMessage.bind(this));
} }
popupTimerSet(callback) { popupTimerSet(callback) {
@ -144,7 +150,11 @@ window.yomichanFrontend = new class {
callback(); callback();
} }
searchAt(point) { onResize() {
this.onSearchClear();
}
async searchAt(point) {
if (this.pendingLookup) { if (this.pendingLookup) {
return; return;
} }
@ -160,70 +170,69 @@ window.yomichanFrontend = new class {
} }
this.pendingLookup = true; this.pendingLookup = true;
this.searchTerms(textSource).then(found => {
if (!found) { try {
return this.searchKanji(textSource); if (!await this.searchTerms(textSource)) {
await this.searchKanji(textSource);
} }
}).catch(error => { } catch (e) {
this.handleError(error, textSource); this.onError(e);
}).then(() => { }
docImposterDestroy();
this.pendingLookup = false; docImposterDestroy();
}); this.pendingLookup = false;
} }
searchTerms(textSource) { async searchTerms(textSource) {
textSource.setEndOffset(this.options.scanning.length); textSource.setEndOffset(this.options.scanning.length);
return apiTermsFind(textSource.text()).then(({definitions, length}) => { const {definitions, length} = await apiTermsFind(textSource.text());
if (definitions.length === 0) { if (definitions.length === 0) {
return false; return false;
} else { }
textSource.setEndOffset(length);
const sentence = docSentenceExtract(textSource, this.options.anki.sentenceExt); textSource.setEndOffset(length);
const url = window.location.href;
this.popup.showTermDefs(
textSource.getRect(),
definitions,
this.options,
{sentence, url}
);
this.lastTextSource = textSource; const sentence = docSentenceExtract(textSource, this.options.anki.sentenceExt);
if (this.options.scanning.selectText) { const url = window.location.href;
textSource.select(); this.popup.termsShow(
} textSource.getRect(),
definitions,
this.options,
{sentence, url}
);
return true; this.lastTextSource = textSource;
} if (this.options.scanning.selectText) {
}); textSource.select();
}
return true;
} }
searchKanji(textSource) { async searchKanji(textSource) {
textSource.setEndOffset(1); textSource.setEndOffset(1);
return apiKanjiFind(textSource.text()).then(definitions => { const definitions = await apiKanjiFind(textSource.text());
if (definitions.length === 0) { if (definitions.length === 0) {
return false; return false;
} else { }
const sentence = docSentenceExtract(textSource, this.options.anki.sentenceExt);
const url = window.location.href;
this.popup.showKanjiDefs(
textSource.getRect(),
definitions,
this.options,
{sentence, url}
);
this.lastTextSource = textSource; const sentence = docSentenceExtract(textSource, this.options.anki.sentenceExt);
if (this.options.scanning.selectText) { const url = window.location.href;
textSource.select(); this.popup.showKanji(
} textSource.getRect(),
definitions,
this.options,
{sentence, url}
);
return true; this.lastTextSource = textSource;
} if (this.options.scanning.selectText) {
}); textSource.select();
}
return true;
} }
searchClear() { searchClear() {
@ -238,7 +247,7 @@ window.yomichanFrontend = new class {
} }
handleError(error, textSource) { handleError(error, textSource) {
if (window.yomichanOrphaned) { if (window.yomichan_orphaned) {
if (textSource && this.options.scanning.modifier !== 'none') { if (textSource && this.options.scanning.modifier !== 'none') {
this.popup.showOrphaned(textSource.getRect(), this.options); this.popup.showOrphaned(textSource.getRect(), this.options);
} }

View File

@ -28,7 +28,7 @@ function utilInvoke(action, params={}) {
} }
}); });
} catch (e) { } catch (e) {
window.yomichanOrphaned = true; window.yomichan_orphaned = true;
reject(e.message); reject(e.message);
} }
}); });

View File

@ -32,171 +32,57 @@ class Display {
$(document).keydown(this.onKeyDown.bind(this)); $(document).keydown(this.onKeyDown.bind(this));
} }
handleError(error) { onError(error) {
throw 'override me'; throw 'override me';
} }
clearSearch() { onSearchClear() {
throw 'override me'; throw 'override me';
} }
showTermDefs(definitions, options, context) { onSourceTermView(e) {
window.focus();
this.spinner.hide();
this.definitions = definitions;
this.options = options;
this.context = context;
const sequence = ++this.sequence;
const params = {
definitions,
addable: options.anki.enable,
grouped: options.general.groupResults,
playback: options.general.audioSource !== 'disabled',
debug: options.general.debugInfo
};
if (context) {
for (const definition of definitions) {
if (context.sentence) {
definition.cloze = Display.clozeBuild(context.sentence, definition.source);
}
definition.url = context.url;
}
}
apiTemplateRender('terms.html', params).then(content => {
this.container.html(content);
this.entryScroll(context && context.index || 0);
$('.action-add-note').click(this.onAddNote.bind(this));
$('.action-view-note').click(this.onViewNote.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 = {
definitions,
source: context && context.source,
addable: options.anki.enable,
debug: options.general.debugInfo
};
if (context) {
for (const definition of definitions) {
if (context.sentence) {
definition.cloze = Display.clozeBuild(context.sentence);
}
definition.url = context.url;
}
}
apiTemplateRender('kanji.html', params).then(content => {
this.container.html(content);
this.entryScroll(context && context.index || 0);
$('.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));
}
adderButtonsUpdate(modes, sequence) {
return apiDefinitionsAddable(this.definitions, modes).then(states => {
if (!states || sequence !== this.sequence) {
return;
}
states.forEach((state, index) => {
for (const mode in state) {
const button = Display.adderButtonFind(index, mode);
if (state[mode]) {
button.removeClass('disabled');
} else {
button.addClass('disabled');
}
button.removeClass('pending');
}
});
});
}
entryScroll(index, smooth) {
index = Math.min(index, this.definitions.length - 1);
index = Math.max(index, 0);
$('.current').hide().eq(index).show();
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);
}
this.index = index;
}
onSourceTerm(e) {
e.preventDefault(); e.preventDefault();
this.sourceBack(); this.sourceBack();
} }
onKanjiLookup(e) { async onKanjiLookup(e) {
e.preventDefault(); try {
e.preventDefault();
const link = $(e.target); const link = $(e.target);
const context = { const context = {
source: { source: {
definitions: this.definitions, definitions: this.definitions,
index: Display.entryIndexFind(link) index: Display.entryIndexFind(link)
}
};
if (this.context) {
context.sentence = this.context.sentence;
context.url = this.context.url;
} }
};
if (this.context) { const kanjiDefs = await apiKanjiFind(link.text());
context.sentence = this.context.sentence; this.kanjiShow(kanjiDefs, this.options, context);
context.url = this.context.url; } catch (e) {
this.onError(e);
} }
apiKanjiFind(link.text()).then(kanjiDefs => {
this.showKanjiDefs(kanjiDefs, this.options, context);
}).catch(this.handleError.bind(this));
} }
onPlayAudio(e) { onAudioPlay(e) {
e.preventDefault(); e.preventDefault();
const index = Display.entryIndexFind($(e.currentTarget)); const index = Display.entryIndexFind($(e.currentTarget));
this.audioPlay(this.definitions[index]); this.audioPlay(this.definitions[index]);
} }
onAddNote(e) { onNoteAdd(e) {
e.preventDefault(); e.preventDefault();
const link = $(e.currentTarget); const link = $(e.currentTarget);
const index = Display.entryIndexFind(link); const index = Display.entryIndexFind(link);
this.noteAdd(this.definitions[index], link.data('mode')); this.noteAdd(this.definitions[index], link.data('mode'));
} }
onViewNote(e) { onNoteView(e) {
e.preventDefault(); e.preventDefault();
const link = $(e.currentTarget); const link = $(e.currentTarget);
const index = Display.entryIndexFind(link); const index = Display.entryIndexFind(link);
@ -220,48 +106,48 @@ class Display {
const handlers = { const handlers = {
27: /* escape */ () => { 27: /* escape */ () => {
this.clearSearch(); this.onSearchClear();
return true; return true;
}, },
33: /* page up */ () => { 33: /* page up */ () => {
if (e.altKey) { if (e.altKey) {
this.entryScroll(this.index - 3, true); this.entryScrollIntoView(this.index - 3, true);
return true; return true;
} }
}, },
34: /* page down */ () => { 34: /* page down */ () => {
if (e.altKey) { if (e.altKey) {
this.entryScroll(this.index + 3, true); this.entryScrollIntoView(this.index + 3, true);
return true; return true;
} }
}, },
35: /* end */ () => { 35: /* end */ () => {
if (e.altKey) { if (e.altKey) {
this.entryScroll(this.definitions.length - 1, true); this.entryScrollIntoView(this.definitions.length - 1, true);
return true; return true;
} }
}, },
36: /* home */ () => { 36: /* home */ () => {
if (e.altKey) { if (e.altKey) {
this.entryScroll(0, true); this.entryScrollIntoView(0, true);
return true; return true;
} }
}, },
38: /* up */ () => { 38: /* up */ () => {
if (e.altKey) { if (e.altKey) {
this.entryScroll(this.index - 1, true); this.entryScrollIntoView(this.index - 1, true);
return true; return true;
} }
}, },
40: /* down */ () => { 40: /* down */ () => {
if (e.altKey) { if (e.altKey) {
this.entryScroll(this.index + 1, true); this.entryScrollIntoView(this.index + 1, true);
return true; return true;
} }
}, },
@ -317,6 +203,135 @@ class Display {
} }
} }
async termsShow(definitions, options, context) {
try {
window.focus();
this.definitions = definitions;
this.options = options;
this.context = context;
const sequence = ++this.sequence;
const params = {
definitions,
addable: options.anki.enable,
grouped: options.general.groupResults,
playback: options.general.audioSource !== 'disabled',
debug: options.general.debugInfo
};
if (context) {
for (const definition of definitions) {
if (context.sentence) {
definition.cloze = Display.clozeBuild(context.sentence, definition.source);
}
definition.url = context.url;
}
}
const content = await apiTemplateRender('terms.html', params);
this.container.html(content);
this.entryScrollIntoView(context && context.index || 0);
$('.action-add-note').click(this.onNoteAdd.bind(this));
$('.action-view-note').click(this.onNoteView.bind(this));
$('.action-play-audio').click(this.onAudioPlay.bind(this));
$('.kanji-link').click(this.onKanjiLookup.bind(this));
await this.adderButtonUpdate(['term-kanji', 'term-kana'], sequence);
} catch (e) {
this.onError(e);
}
}
async kanjiShow(definitions, options, context) {
try {
window.focus();
this.definitions = definitions;
this.options = options;
this.context = context;
const sequence = ++this.sequence;
const params = {
definitions,
source: context && context.source,
addable: options.anki.enable,
debug: options.general.debugInfo
};
if (context) {
for (const definition of definitions) {
if (context.sentence) {
definition.cloze = Display.clozeBuild(context.sentence);
}
definition.url = context.url;
}
}
const content = await apiTemplateRender('kanji.html', params);
this.container.html(content);
this.entryScrollIntoView(context && context.index || 0);
$('.action-add-note').click(this.onNoteAdd.bind(this));
$('.source-term').click(this.onSourceTermView.bind(this));
await this.adderButtonUpdate(['kanji'], sequence);
} catch (e) {
this.onError(e);
}
}
async adderButtonUpdate(modes, sequence) {
try {
this.spinner.show();
const states = apiDefinitionsAddable(this.definitions, modes);
if (!states || sequence !== this.sequence) {
return;
}
for (let i = 0; i < states.length; ++i) {
const state = states[i];
for (const mode in state) {
const button = Display.adderButtonFind(i, mode);
if (state[mode]) {
button.removeClass('disabled');
} else {
button.addClass('disabled');
}
button.removeClass('pending');
}
}
} catch (e) {
this.onError(e);
} finally {
this.spinner.hide();
}
}
entryScrollIntoView(index, smooth) {
index = Math.min(index, this.definitions.length - 1);
index = Math.max(index, 0);
$('.current').hide().eq(index).show();
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);
}
this.index = index;
}
sourceBack() { sourceBack() {
if (this.context && this.context.source) { if (this.context && this.context.source) {
const context = { const context = {
@ -325,35 +340,42 @@ class Display {
index: this.context.source.index index: this.context.source.index
}; };
this.showTermDefs(this.context.source.definitions, this.options, context); this.termsShow(this.context.source.definitions, this.options, context);
} }
} }
noteAdd(definition, mode) { async noteAdd(definition, mode) {
this.spinner.show(); try {
return apiDefinitionAdd(definition, mode).then(noteId => { this.spinner.show();
const noteId = await apiDefinitionAdd(definition, mode);
if (noteId) { if (noteId) {
const index = this.definitions.indexOf(definition); const index = this.definitions.indexOf(definition);
Display.adderButtonFind(index, mode).addClass('disabled'); Display.adderButtonFind(index, mode).addClass('disabled');
Display.viewerButtonFind(index).removeClass('pending disabled').data('noteId', noteId); Display.viewerButtonFind(index).removeClass('pending disabled').data('noteId', noteId);
} else { } else {
this.handleError('note could not be added'); throw 'note could note be added';
} }
}).catch(this.handleError.bind(this)).then(() => this.spinner.hide()); } catch (e) {
this.onError(e);
} finally {
this.spinner.hide();
}
} }
audioPlay(definition) { async audioPlay(definition) {
this.spinner.show(); try {
this.spinner.show();
for (const key in this.audioCache) { let url = await audioBuildUrl(definition, this.options.general.audioSource, this.responseCache);
this.audioCache[key].pause();
}
audioBuildUrl(definition, this.options.general.audioSource, this.responseCache).then(url => {
if (!url) { if (!url) {
url = '/mixed/mp3/button.mp3'; url = '/mixed/mp3/button.mp3';
} }
for (const key in this.audioCache) {
this.audioCache[key].pause();
}
let audio = this.audioCache[url]; let audio = this.audioCache[url];
if (audio) { if (audio) {
audio.currentTime = 0; audio.currentTime = 0;
@ -371,7 +393,11 @@ class Display {
audio.play(); audio.play();
}; };
} }
}).catch(this.handleError.bind(this)).then(() => this.spinner.hide()); } catch (e) {
this.onError(e);
} finally {
this.spinner.hide();
}
} }
static clozeBuild(sentence, source) { static clozeBuild(sentence, source) {