aboutsummaryrefslogtreecommitdiff
path: root/ext/fg/js
diff options
context:
space:
mode:
authorAlex Yatskov <alex@foosoft.net>2017-02-26 11:05:41 -0800
committerAlex Yatskov <alex@foosoft.net>2017-02-26 11:05:41 -0800
commit32f95e59a9c5612d2b5658ea8a70b55ec17cca18 (patch)
treea75e426d9350f266402ff0e8a9d040a985a37629 /ext/fg/js
parentb7faaf0b51363b8366adae3ba7511d5232d6cd30 (diff)
parent8966dc1213d4af15c956dbd8976b80a1287c9fe0 (diff)
Merge branch 'dev' into firefox
Diffstat (limited to 'ext/fg/js')
-rw-r--r--ext/fg/js/driver.js75
-rw-r--r--ext/fg/js/frame.js96
-rw-r--r--ext/fg/js/popup.js14
-rw-r--r--ext/fg/js/util.js94
4 files changed, 197 insertions, 82 deletions
diff --git a/ext/fg/js/driver.js b/ext/fg/js/driver.js
index 2e818acf..9d972abf 100644
--- a/ext/fg/js/driver.js
+++ b/ext/fg/js/driver.js
@@ -24,21 +24,16 @@ class Driver {
this.lastMousePos = null;
this.lastTextSource = null;
this.pendingLookup = false;
- this.enabled = false;
this.options = null;
- chrome.runtime.onMessage.addListener(this.onBgMessage.bind(this));
- window.addEventListener('mouseover', this.onMouseOver.bind(this));
- window.addEventListener('mousedown', this.onMouseDown.bind(this));
- window.addEventListener('mousemove', this.onMouseMove.bind(this));
- window.addEventListener('resize', e => this.searchClear());
-
getOptions().then(options => {
this.options = options;
- return isEnabled();
- }).then(enabled => {
- this.enabled = enabled;
- });
+ window.addEventListener('mouseover', this.onMouseOver.bind(this));
+ window.addEventListener('mousedown', this.onMouseDown.bind(this));
+ window.addEventListener('mousemove', this.onMouseMove.bind(this));
+ window.addEventListener('resize', e => this.searchClear());
+ chrome.runtime.onMessage.addListener(this.onBgMessage.bind(this));
+ }).catch(this.handleError.bind(this));
}
popupTimerSet(callback) {
@@ -63,7 +58,7 @@ class Driver {
this.lastMousePos = {x: e.clientX, y: e.clientY};
this.popupTimerClear();
- if (!this.enabled) {
+ if (!this.options.general.enable) {
return;
}
@@ -75,7 +70,7 @@ class Driver {
return;
}
- const searcher = () => this.searchAt(this.lastMousePos, false);
+ const searcher = () => this.searchAt(this.lastMousePos);
if (!this.popup.isVisible() || e.shiftKey || e.which === 2 /* mmb */) {
searcher();
} else {
@@ -98,17 +93,13 @@ class Driver {
callback();
}
- searchAt(point, hideNotFound) {
+ searchAt(point) {
if (this.pendingLookup) {
return;
}
- const textSource = textSourceFromPoint(point);
+ const textSource = textSourceFromPoint(point, this.options.scanning.imposter);
if (textSource === null || !textSource.containsPoint(point)) {
- if (hideNotFound) {
- this.searchClear();
- }
-
return;
}
@@ -119,14 +110,10 @@ class Driver {
this.pendingLookup = true;
this.searchTerms(textSource).then(found => {
if (!found) {
- this.searchKanji(textSource).then(found => {
- if (!found && hideNotFound) {
- this.searchClear();
- }
- });
+ return this.searchKanji(textSource);
}
}).catch(error => {
- window.alert('Error: ' + error);
+ this.handleError(error, textSource);
}).then(() => {
this.pendingLookup = false;
});
@@ -143,13 +130,11 @@ class Driver {
textSource.setEndOffset(length);
const sentence = extractSentence(textSource, this.options.anki.sentenceExt);
- definitions.forEach(definition => {
- definition.url = window.location.href;
- definition.sentence = sentence;
- });
+ const url = window.location.href;
this.popup.showNextTo(textSource.getRect());
- this.popup.showTermDefs(definitions, this.options);
+ this.popup.showTermDefs(definitions, this.options, {sentence, url});
+
this.lastTextSource = textSource;
if (this.options.scanning.selectText) {
textSource.select();
@@ -157,9 +142,6 @@ class Driver {
return true;
}
- }).catch(error => {
- window.alert('Error: ' + error);
- return false;
});
}
@@ -170,10 +152,12 @@ class Driver {
if (definitions.length === 0) {
return false;
} else {
- definitions.forEach(definition => definition.url = window.location.href);
+ const sentence = extractSentence(textSource, this.options.anki.sentenceExt);
+ const url = window.location.href;
this.popup.showNextTo(textSource.getRect());
- this.popup.showKanjiDefs(definitions, this.options);
+ this.popup.showKanjiDefs(definitions, this.options, {sentence, url});
+
this.lastTextSource = textSource;
if (this.options.scanning.selectText) {
textSource.select();
@@ -181,13 +165,11 @@ class Driver {
return true;
}
- }).catch(error => {
- window.alert('Error: ' + error);
- return false;
});
}
searchClear() {
+ destroyImposters();
this.popup.hide();
if (this.options.scanning.selectText && this.lastTextSource !== null) {
@@ -197,14 +179,19 @@ class Driver {
this.lastTextSource = null;
}
- api_setOptions(options) {
- this.options = options;
+ handleError(error, textSource) {
+ if (window.orphaned) {
+ if (textSource) {
+ this.popup.showNextTo(textSource.getRect());
+ this.popup.showOrphaned();
+ }
+ } else {
+ showError(error);
+ }
}
- api_setEnabled(enabled) {
- if (!(this.enabled = enabled)) {
- this.searchClear();
- }
+ api_setOptions(options) {
+ this.options = options;
}
}
diff --git a/ext/fg/js/frame.js b/ext/fg/js/frame.js
index 36356f02..1028f0f6 100644
--- a/ext/fg/js/frame.js
+++ b/ext/fg/js/frame.js
@@ -30,26 +30,32 @@ class Frame {
});
}
- api_showTermDefs({definitions, options}) {
+ api_showTermDefs({definitions, options, context}) {
const sequence = ++this.sequence;
- const context = {
+ const params = {
definitions,
grouped: options.general.groupResults,
addable: options.ankiMethod !== 'disabled',
playback: options.general.audioPlayback
};
+ definitions.forEach(definition => {
+ definition.sentence = context.sentence;
+ definition.url = context.url;
+ });
+
this.definitions = definitions;
this.showSpinner(false);
window.scrollTo(0, 0);
- renderText(context, 'terms.html').then(content => {
- $('.content').html(content);
+ renderText(params, 'terms.html').then(content => {
+ $('#content').html(content);
$('.action-add-note').click(this.onAddNote.bind(this));
$('.kanji-link').click(e => {
e.preventDefault();
- findKanji($(e.target).text()).then(kdefs => this.api_showKanjiDefs({options, definitions: kdefs}));
+ const character = $(e.target).text();
+ findKanji(character).then(definitions => this.api_showKanjiDefs({definitions, options, context}));
});
$('.action-play-audio').click(e => {
@@ -59,28 +65,42 @@ class Frame {
});
this.updateAddNoteButtons(['term_kanji', 'term_kana'], sequence);
+ }).catch(error => {
+ this.handleError(error);
});
}
- api_showKanjiDefs({definitions, options}) {
+ api_showKanjiDefs({definitions, options, context}) {
const sequence = ++this.sequence;
- const context = {
+ const params = {
definitions,
addable: options.ankiMethod !== 'disabled'
};
+ definitions.forEach(definition => {
+ definition.sentence = context.sentence;
+ definition.url = context.url;
+ });
+
this.definitions = definitions;
this.showSpinner(false);
window.scrollTo(0, 0);
- renderText(context, 'kanji.html').then(content => {
- $('.content').html(content);
+ renderText(params, 'kanji.html').then(content => {
+ $('#content').html(content);
$('.action-add-note').click(this.onAddNote.bind(this));
this.updateAddNoteButtons(['kanji'], sequence);
+ }).catch(error => {
+ this.handleError(error);
});
}
+ api_showOrphaned() {
+ $('#content').hide();
+ $('#orphan').show();
+ }
+
findAddNoteButton(index, mode) {
return $(`.action-add-note[data-index="${index}"][data-mode="${mode}"]`);
}
@@ -93,15 +113,24 @@ class Frame {
const index = link.data('index');
const mode = link.data('mode');
- addDefinition(this.definitions[index], mode).then(success => {
+ const definition = this.definitions[index];
+ if (mode !== 'kanji') {
+ const url = buildAudioUrl(definition);
+ const filename = buildAudioFilename(definition);
+ if (url && filename) {
+ definition.audio = {url, filename};
+ }
+ }
+
+ addDefinition(definition, mode).then(success => {
if (success) {
const button = this.findAddNoteButton(index, mode);
button.addClass('disabled');
} else {
- window.alert('Note could not be added');
+ showError('note could not be added');
}
}).catch(error => {
- window.alert('Error: ' + error);
+ this.handleError(error);
}).then(() => {
this.showSpinner(false);
});
@@ -118,7 +147,7 @@ class Frame {
}
states.forEach((state, index) => {
- for (let mode in state) {
+ for (const mode in state) {
const button = this.findAddNoteButton(index, mode);
if (state[mode]) {
button.removeClass('disabled');
@@ -129,6 +158,8 @@ class Frame {
button.removeClass('pending');
}
});
+ }).catch(error => {
+ this.handleError(error);
});
}
@@ -142,18 +173,41 @@ class Frame {
}
playAudio(definition) {
- let url = `https://assets.languagepod101.com/dictionary/japanese/audiomp3.php?kanji=${encodeURIComponent(definition.expression)}`;
- if (definition.reading) {
- url += `&kana=${encodeURIComponent(definition.reading)}`;
+ for (const key in this.audioCache) {
+ const audio = this.audioCache[key];
+ if (audio !== null) {
+ audio.pause();
+ }
}
- for (let key in this.audioCache) {
- this.audioCache[key].pause();
+ const url = buildAudioUrl(definition);
+ if (!url) {
+ return;
+ }
+
+ let audio = this.audioCache[url];
+ if (audio) {
+ audio.currentTime = 0;
+ audio.play();
+ } else {
+ audio = new Audio(url);
+ audio.onloadeddata = () => {
+ if (audio.duration === 5.694694) {
+ audio = new Audio('mp3/button.mp3');
+ }
+
+ this.audioCache[url] = audio;
+ audio.play();
+ };
}
+ }
- const audio = this.audioCache[url] || new Audio(url);
- audio.currentTime = 0;
- audio.play();
+ handleError(error) {
+ if (window.orphaned) {
+ this.api_showOrphaned();
+ } else {
+ showError(error);
+ }
}
}
diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js
index d47ab4ae..751c6acc 100644
--- a/ext/fg/js/popup.js
+++ b/ext/fg/js/popup.js
@@ -68,15 +68,19 @@ class Popup {
return this.container.style.visibility !== 'hidden';
}
- showTermDefs(definitions, options) {
- this.invokeApi('showTermDefs', {definitions, options});
+ showTermDefs(definitions, options, context) {
+ this.invokeApi('showTermDefs', {definitions, options, context});
}
- showKanjiDefs(definitions, options) {
- this.invokeApi('showKanjiDefs', {definitions, options});
+ showKanjiDefs(definitions, options, context) {
+ this.invokeApi('showKanjiDefs', {definitions, options, context});
}
- invokeApi(action, params) {
+ showOrphaned() {
+ this.invokeApi('showOrphaned');
+ }
+
+ invokeApi(action, params={}) {
this.container.contentWindow.postMessage({action, params}, '*');
}
}
diff --git a/ext/fg/js/util.js b/ext/fg/js/util.js
index ef45d08c..99da6381 100644
--- a/ext/fg/js/util.js
+++ b/ext/fg/js/util.js
@@ -19,18 +19,23 @@
function invokeBgApi(action, params) {
return new Promise((resolve, reject) => {
- chrome.runtime.sendMessage({action, params}, ({result, error}) => {
- if (error) {
- reject(error);
- } else {
- resolve(result);
- }
- });
+ try {
+ chrome.runtime.sendMessage({action, params}, ({result, error}) => {
+ if (error) {
+ reject(error);
+ } else {
+ resolve(result);
+ }
+ });
+ } catch (e) {
+ window.orphaned = true;
+ reject(e.message);
+ }
});
}
-function isEnabled() {
- return invokeBgApi('getEnabled', {});
+function showError(error) {
+ window.alert(`Error: ${error}`);
}
function getOptions() {
@@ -61,12 +66,36 @@ function addDefinition(definition, mode) {
return invokeBgApi('addDefinition', {definition, mode});
}
-function textSourceFromPoint(point) {
+function createImposter(element) {
+ const imposter = document.createElement('div');
+ const elementRect = element.getBoundingClientRect();
+
+ imposter.className = 'yomichan-imposter';
+ imposter.innerText = element.value;
+ imposter.style.cssText = window.getComputedStyle(element).cssText;
+ imposter.style.position = 'absolute';
+ imposter.style.top = elementRect.top + 'px';
+ imposter.style.left = elementRect.left + 'px';
+ imposter.style.zIndex = 2147483646;
+ document.body.appendChild(imposter);
+
+ imposter.scrollTop = element.scrollTop;
+ imposter.scrollLeft = element.scrollLeft;
+}
+
+function destroyImposters() {
+ for (const element of document.getElementsByClassName('yomichan-imposter')) {
+ element.parentNode.removeChild(element);
+ }
+}
+
+function textSourceFromPoint(point, imposter) {
const element = document.elementFromPoint(point.x, point.y);
if (element !== null) {
- const names = ['IMG', 'INPUT', 'BUTTON', 'TEXTAREA'];
- if (names.includes(element.nodeName)) {
+ if (element.nodeName === 'IMG' || element.nodeName === 'BUTTON') {
return new TextSourceElement(element);
+ } else if (imposter && (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA')) {
+ createImposter(element);
}
}
@@ -75,6 +104,7 @@ function textSourceFromPoint(point) {
return new TextSourceRange(range);
}
+ destroyImposters();
return null;
}
@@ -132,3 +162,43 @@ function extractSentence(source, extent) {
return content.substring(startPos, endPos).trim();
}
+
+function buildAudioUrl(definition) {
+ let kana = definition.reading;
+ let kanji = definition.expression;
+
+ if (!kana && !kanji) {
+ return null;
+ }
+
+ if (!kana && wanakana.isHiragana(kanji)) {
+ kana = kanji;
+ kanji = null;
+ }
+
+ const params = [];
+ if (kanji) {
+ params.push(`kanji=${encodeURIComponent(kanji)}`);
+ }
+ if (kana) {
+ params.push(`kana=${encodeURIComponent(kana)}`);
+ }
+
+ return `https://assets.languagepod101.com/dictionary/japanese/audiomp3.php?${params.join('&')}`;
+}
+
+function buildAudioFilename(definition) {
+ if (!definition.reading && !definition.expression) {
+ return null;
+ }
+
+ let filename = 'yomichan';
+ if (definition.reading) {
+ filename += `_${definition.reading}`;
+ }
+ if (definition.expression) {
+ filename += `_${definition.expression}`;
+ }
+
+ return filename += '.mp3';
+}