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/translator.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/japanese.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() {
super($('#spinner'), $('#content'));
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');
window.wanakana.bind(this.query.get(0));
}
handleError(error) {
onError(error) {
window.alert(`Error: ${error}`);
}
clearSearch() {
onSearchClear() {
this.query.focus().select();
}
inputSearch() {
onSearchInput() {
this.search.prop('disabled', this.query.val().length === 0);
}
@ -46,9 +46,9 @@ window.displayWindow = new class extends Display {
try {
this.intro.slideUp();
const {length, definitions} = await apiTermsFind(this.query.val());
super.showTermDefs(definitions, await apiOptionsGet());
super.termsShow(definitions, await apiOptionsGet());
} 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/handlebars.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/display.js"></script>
<script src="/mixed/js/japanese.js"></script>
<script src="/mixed/js/request.js"></script>
<script src="/bg/js/display-window.js"></script>
</body>

View File

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

View File

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

View File

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

View File

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

View File

@ -32,171 +32,57 @@ class Display {
$(document).keydown(this.onKeyDown.bind(this));
}
handleError(error) {
onError(error) {
throw 'override me';
}
clearSearch() {
onSearchClear() {
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 = {
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) {
onSourceTermView(e) {
e.preventDefault();
this.sourceBack();
}
onKanjiLookup(e) {
e.preventDefault();
async onKanjiLookup(e) {
try {
e.preventDefault();
const link = $(e.target);
const context = {
source: {
definitions: this.definitions,
index: Display.entryIndexFind(link)
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;
}
};
if (this.context) {
context.sentence = this.context.sentence;
context.url = this.context.url;
const kanjiDefs = await apiKanjiFind(link.text());
this.kanjiShow(kanjiDefs, this.options, context);
} 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();
const index = Display.entryIndexFind($(e.currentTarget));
this.audioPlay(this.definitions[index]);
}
onAddNote(e) {
onNoteAdd(e) {
e.preventDefault();
const link = $(e.currentTarget);
const index = Display.entryIndexFind(link);
this.noteAdd(this.definitions[index], link.data('mode'));
}
onViewNote(e) {
onNoteView(e) {
e.preventDefault();
const link = $(e.currentTarget);
const index = Display.entryIndexFind(link);
@ -220,48 +106,48 @@ class Display {
const handlers = {
27: /* escape */ () => {
this.clearSearch();
this.onSearchClear();
return true;
},
33: /* page up */ () => {
if (e.altKey) {
this.entryScroll(this.index - 3, true);
this.entryScrollIntoView(this.index - 3, true);
return true;
}
},
34: /* page down */ () => {
if (e.altKey) {
this.entryScroll(this.index + 3, true);
this.entryScrollIntoView(this.index + 3, true);
return true;
}
},
35: /* end */ () => {
if (e.altKey) {
this.entryScroll(this.definitions.length - 1, true);
this.entryScrollIntoView(this.definitions.length - 1, true);
return true;
}
},
36: /* home */ () => {
if (e.altKey) {
this.entryScroll(0, true);
this.entryScrollIntoView(0, true);
return true;
}
},
38: /* up */ () => {
if (e.altKey) {
this.entryScroll(this.index - 1, true);
this.entryScrollIntoView(this.index - 1, true);
return true;
}
},
40: /* down */ () => {
if (e.altKey) {
this.entryScroll(this.index + 1, true);
this.entryScrollIntoView(this.index + 1, 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() {
if (this.context && this.context.source) {
const context = {
@ -325,35 +340,42 @@ class Display {
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) {
this.spinner.show();
return apiDefinitionAdd(definition, mode).then(noteId => {
async noteAdd(definition, mode) {
try {
this.spinner.show();
const noteId = await apiDefinitionAdd(definition, mode);
if (noteId) {
const index = this.definitions.indexOf(definition);
Display.adderButtonFind(index, mode).addClass('disabled');
Display.viewerButtonFind(index).removeClass('pending disabled').data('noteId', noteId);
} 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) {
this.spinner.show();
async audioPlay(definition) {
try {
this.spinner.show();
for (const key in this.audioCache) {
this.audioCache[key].pause();
}
audioBuildUrl(definition, this.options.general.audioSource, this.responseCache).then(url => {
let url = await audioBuildUrl(definition, this.options.general.audioSource, this.responseCache);
if (!url) {
url = '/mixed/mp3/button.mp3';
}
for (const key in this.audioCache) {
this.audioCache[key].pause();
}
let audio = this.audioCache[url];
if (audio) {
audio.currentTime = 0;
@ -371,7 +393,11 @@ class Display {
audio.play();
};
}
}).catch(this.handleError.bind(this)).then(() => this.spinner.hide());
} catch (e) {
this.onError(e);
} finally {
this.spinner.hide();
}
}
static clozeBuild(sentence, source) {