summaryrefslogtreecommitdiff
path: root/ext/fg
diff options
context:
space:
mode:
Diffstat (limited to 'ext/fg')
-rw-r--r--ext/fg/css/client.css2
-rw-r--r--ext/fg/float.html (renamed from ext/fg/frame.html)12
-rw-r--r--ext/fg/js/api.js58
-rw-r--r--ext/fg/js/document.js163
-rw-r--r--ext/fg/js/float.js (renamed from ext/fg/js/display-frame.js)64
-rw-r--r--ext/fg/js/frontend.js (renamed from ext/fg/js/driver.js)230
-rw-r--r--ext/fg/js/popup.js111
-rw-r--r--ext/fg/js/source-element.js77
-rw-r--r--ext/fg/js/source.js (renamed from ext/fg/js/source-range.js)70
-rw-r--r--ext/fg/js/util.js191
10 files changed, 512 insertions, 466 deletions
diff --git a/ext/fg/css/client.css b/ext/fg/css/client.css
index 9f566480..b5b1f6bd 100644
--- a/ext/fg/css/client.css
+++ b/ext/fg/css/client.css
@@ -17,7 +17,7 @@
*/
-iframe#yomichan-popup {
+iframe#yomichan-float {
all: initial;
background-color: #fff;
border: 1px solid #999;
diff --git a/ext/fg/frame.html b/ext/fg/float.html
index c20745af..89872cce 100644
--- a/ext/fg/frame.html
+++ b/ext/fg/float.html
@@ -5,7 +5,7 @@
<title></title>
<link rel="stylesheet" href="/mixed/lib/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="/mixed/lib/bootstrap/css/bootstrap-theme.min.css">
- <link rel="stylesheet" href="/mixed/css/frame.css">
+ <link rel="stylesheet" href="/mixed/css/display.css">
<style type="text/css">
.entry, .note {
padding-left: 10px;
@@ -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>
@@ -32,9 +32,11 @@
<script src="/mixed/lib/jquery.min.js"></script>
<script src="/mixed/lib/wanakana.min.js"></script>
+
+ <script src="/fg/js/api.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>
+
+ <script src="/fg/js/float.js"></script>
</body>
</html>
diff --git a/ext/fg/js/api.js b/ext/fg/js/api.js
new file mode 100644
index 00000000..151882eb
--- /dev/null
+++ b/ext/fg/js/api.js
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2016-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/>.
+ */
+
+
+function apiOptionsSet(options) {
+ return utilInvoke('optionsSet', {options});
+}
+
+function apiOptionsGet() {
+ return utilInvoke('optionsGet');
+}
+
+function apiTermsFind(text) {
+ return utilInvoke('termsFind', {text});
+}
+
+function apiKanjiFind(text) {
+ return utilInvoke('kanjiFind', {text});
+}
+
+function apiDefinitionAdd(definition, mode) {
+ return utilInvoke('definitionAdd', {definition, mode});
+}
+
+function apiDefinitionsAddable(definitions, modes) {
+ return utilInvoke('definitionsAddable', {definitions, modes}).catch(() => null);
+}
+
+function apiNoteView(noteId) {
+ return utilInvoke('noteView', {noteId});
+}
+
+function apiTemplateRender(template, data) {
+ return utilInvoke('templateRender', {data, template});
+}
+
+function apiCommandExec(command) {
+ return utilInvoke('commandExec', {command});
+}
+
+function apiAudioGetUrl(definition, source) {
+ return utilInvoke('audioGetUrl', {definition, source});
+}
diff --git a/ext/fg/js/document.js b/ext/fg/js/document.js
new file mode 100644
index 00000000..26c85b40
--- /dev/null
+++ b/ext/fg/js/document.js
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2016-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/>.
+ */
+
+
+function docOffsetCalc(element) {
+ const scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
+ const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft;
+
+ const clientTop = document.documentElement.clientTop || document.body.clientTop || 0;
+ const clientLeft = document.documentElement.clientLeft || document.body.clientLeft || 0;
+
+ const rect = element.getBoundingClientRect();
+ const top = Math.round(rect.top + scrollTop - clientTop);
+ const left = Math.round(rect.left + scrollLeft - clientLeft);
+
+ return {top, left};
+}
+
+function docImposterCreate(element) {
+ const styleProps = window.getComputedStyle(element);
+ const stylePairs = [];
+ for (const key of styleProps) {
+ stylePairs.push(`${key}: ${styleProps[key]};`);
+ }
+
+ const offset = docOffsetCalc(element);
+ const imposter = document.createElement('div');
+ imposter.className = 'yomichan-imposter';
+ imposter.innerText = element.value;
+ imposter.style.cssText = stylePairs.join('\n');
+ imposter.style.position = 'absolute';
+ imposter.style.top = `${offset.top}px`;
+ imposter.style.left = `${offset.left}px`;
+ imposter.style.opacity = 0;
+ imposter.style.zIndex = 2147483646;
+ if (element.nodeName === 'TEXTAREA' && styleProps.overflow === 'visible') {
+ imposter.style.overflow = 'auto';
+ }
+
+ document.body.appendChild(imposter);
+ imposter.scrollTop = element.scrollTop;
+ imposter.scrollLeft = element.scrollLeft;
+}
+
+function docImposterDestroy() {
+ for (const element of document.getElementsByClassName('yomichan-imposter')) {
+ element.parentNode.removeChild(element);
+ }
+}
+
+function docRangeFromPoint(point) {
+ const element = document.elementFromPoint(point.x, point.y);
+ if (element) {
+ if (element.nodeName === 'IMG' || element.nodeName === 'BUTTON') {
+ return new TextSourceElement(element);
+ } else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {
+ docImposterCreate(element);
+ }
+ }
+
+ if (!document.caretRangeFromPoint) {
+ document.caretRangeFromPoint = (x, y) => {
+ const position = document.caretPositionFromPoint(x,y);
+ if (position) {
+ const range = document.createRange();
+ range.setStart(position.offsetNode, position.offset);
+ range.setEnd(position.offsetNode, position.offset);
+ return range;
+ }
+ };
+ }
+
+ const range = document.caretRangeFromPoint(point.x, point.y);
+ if (range) {
+ return new TextSourceRange(range);
+ }
+}
+
+function docSentenceExtract(source, extent) {
+ const quotesFwd = {'「': '」', '『': '』', "'": "'", '"': '"'};
+ const quotesBwd = {'」': '「', '』': '『', "'": "'", '"': '"'};
+ const terminators = '…。..??!!';
+
+ const sourceLocal = source.clone();
+ const position = sourceLocal.setStartOffset(extent);
+ sourceLocal.setEndOffset(position + extent);
+ const content = sourceLocal.text();
+
+ let quoteStack = [];
+
+ let startPos = 0;
+ for (let i = position; i >= startPos; --i) {
+ const c = content[i];
+
+ if (c === '\n') {
+ startPos = i + 1;
+ break;
+ }
+
+ if (quoteStack.length === 0 && (terminators.includes(c) || c in quotesFwd)) {
+ startPos = i + 1;
+ break;
+ }
+
+ if (quoteStack.length > 0 && c === quoteStack[0]) {
+ quoteStack.pop();
+ } else if (c in quotesBwd) {
+ quoteStack = [quotesBwd[c]].concat(quoteStack);
+ }
+ }
+
+ quoteStack = [];
+
+ let endPos = content.length;
+ for (let i = position; i <= endPos; ++i) {
+ const c = content[i];
+
+ if (c === '\n') {
+ endPos = i + 1;
+ break;
+ }
+
+ if (quoteStack.length === 0) {
+ if (terminators.includes(c)) {
+ endPos = i + 1;
+ break;
+ }
+ else if (c in quotesBwd) {
+ endPos = i;
+ break;
+ }
+ }
+
+ if (quoteStack.length > 0 && c === quoteStack[0]) {
+ quoteStack.pop();
+ } else if (c in quotesFwd) {
+ quoteStack = [quotesFwd[c]].concat(quoteStack);
+ }
+ }
+
+ const text = content.substring(startPos, endPos);
+ const padding = text.length - text.replace(/^\s+/, '').length;
+
+ return {
+ text: text.trim(),
+ offset: position - startPos - padding
+ };
+}
diff --git a/ext/fg/js/display-frame.js b/ext/fg/js/float.js
index 9fd09e74..22374f8b 100644
--- a/ext/fg/js/display-frame.js
+++ b/ext/fg/js/float.js
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 Alex Yatskov <alex@foosoft.net>
+ * Copyright (C) 2016-2017 Alex Yatskov <alex@foosoft.net>
* Author: Alex Yatskov <alex@foosoft.net>
*
* This program is free software: you can redistribute it and/or modify
@@ -17,61 +17,45 @@
*/
-window.displayFrame = new class extends Display {
+class DisplayFloat extends Display {
constructor() {
- super($('#spinner'), $('#content'));
- $(window).on('message', this.onMessage.bind(this));
+ super($('#spinner'), $('#definitions'));
+ $(window).on('message', utilAsync(this.onMessage.bind(this)));
}
- definitionAdd(definition, mode) {
- return bgDefinitionAdd(definition, mode);
- }
-
- definitionsAddable(definitions, modes) {
- return bgDefinitionsAddable(definitions, modes);
- }
-
- templateRender(template, data) {
- return bgTemplateRender(template, data);
- }
-
- kanjiFind(character) {
- return bgKanjiFind(character);
- }
-
- handleError(error) {
- if (window.orphaned) {
- this.showOrphaned();
+ onError(error) {
+ if (window.yomichan_orphaned) {
+ this.onOrphaned();
} else {
window.alert(`Error: ${error}`);
}
}
- clearSearch() {
- window.parent.postMessage('popupClose', '*');
+ onOrphaned() {
+ $('#definitions').hide();
+ $('#error-orphaned').show();
}
- selectionCopy() {
- window.parent.postMessage('selectionCopy', '*');
+ onSearchClear() {
+ window.parent.postMessage('popupClose', '*');
}
- showOrphaned() {
- $('#content').hide();
- $('#orphan').show();
+ onSelectionCopy() {
+ window.parent.postMessage('selectionCopy', '*');
}
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();
}
};
@@ -85,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;
}
}
@@ -99,4 +83,6 @@ window.displayFrame = new class extends Display {
super.onKeyDown(e);
}
}
-};
+}
+
+window.yomichan_display = new DisplayFloat();
diff --git a/ext/fg/js/driver.js b/ext/fg/js/frontend.js
index b0cc4613..41c93f00 100644
--- a/ext/fg/js/driver.js
+++ b/ext/fg/js/frontend.js
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 Alex Yatskov <alex@foosoft.net>
+ * Copyright (C) 2016-2017 Alex Yatskov <alex@foosoft.net>
* Author: Alex Yatskov <alex@foosoft.net>
*
* This program is free software: you can redistribute it and/or modify
@@ -17,38 +17,31 @@
*/
-window.driver = new class {
+class Frontend {
constructor() {
this.popup = new Popup();
this.popupTimer = null;
- this.lastMousePos = null;
this.mouseDownLeft = false;
this.mouseDownMiddle = false;
- this.lastTextSource = null;
+ this.textSourceLast = null;
this.pendingLookup = false;
this.options = null;
+ }
- bgOptionsGet().then(options => {
- this.options = options;
- window.addEventListener('mouseover', this.onMouseOver.bind(this));
+ async prepare() {
+ try {
+ this.options = await apiOptionsGet();
+
+ window.addEventListener('message', this.onFrameMessage.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));
- }
-
- popupTimerSet(callback) {
- this.popupTimerClear();
- this.popupTimer = window.setTimeout(callback, this.options.scanning.delay);
- }
+ window.addEventListener('mouseover', this.onMouseOver.bind(this));
+ window.addEventListener('mouseup', this.onMouseUp.bind(this));
+ window.addEventListener('resize', this.onResize.bind(this));
- popupTimerClear() {
- if (this.popupTimer) {
- window.clearTimeout(this.popupTimer);
- this.popupTimer = null;
+ chrome.runtime.onMessage.addListener(this.onBgMessage.bind(this));
+ } catch (e) {
+ this.onError(e);
}
}
@@ -59,7 +52,6 @@ window.driver = new class {
}
onMouseMove(e) {
- this.lastMousePos = {x: e.clientX, y: e.clientY};
this.popupTimerClear();
if (!this.options.general.enable) {
@@ -70,6 +62,10 @@ window.driver = new class {
return;
}
+ if (this.pendingLookup) {
+ return;
+ }
+
const mouseScan = this.mouseDownMiddle && this.options.scanning.middleMouse;
const keyScan =
this.options.scanning.modifier === 'alt' && e.altKey ||
@@ -81,16 +77,24 @@ window.driver = new class {
return;
}
- const searchFunc = () => this.searchAt(this.lastMousePos);
+ const search = async () => {
+ try {
+ await this.searchAt({x: e.clientX, y: e.clientY});
+ this.pendingLookup = false;
+ } catch (e) {
+ this.onError(e);
+ }
+ };
+
if (this.options.scanning.modifier === 'none') {
- this.popupTimerSet(searchFunc);
+ this.popupTimerSet(search);
} else {
- searchFunc();
+ search();
}
}
onMouseDown(e) {
- this.lastMousePos = {x: e.clientX, y: e.clientY};
+ this.mousePosLast = {x: e.clientX, y: e.clientY};
this.popupTimerClear();
this.searchClear();
@@ -126,6 +130,10 @@ window.driver = new class {
}
}
+ onResize() {
+ this.searchClear();
+ }
+
onBgMessage({action, params}, sender, callback) {
const handlers = {
optionsSet: options => {
@@ -144,106 +152,122 @@ window.driver = new class {
callback();
}
- searchAt(point) {
- if (this.pendingLookup) {
- return;
- }
+ onError(error) {
+ window.alert(`Error: ${error}`);
+ }
- const textSource = docRangeFromPoint(point);
- if (!textSource || !textSource.containsPoint(point)) {
- docImposterDestroy();
- return;
- }
+ popupTimerSet(callback) {
+ this.popupTimerClear();
+ this.popupTimer = window.setTimeout(callback, this.options.scanning.delay);
+ }
- if (this.lastTextSource && this.lastTextSource.equals(textSource)) {
- return;
+ popupTimerClear() {
+ if (this.popupTimer) {
+ window.clearTimeout(this.popupTimer);
+ this.popupTimer = null;
}
+ }
- this.pendingLookup = true;
- this.searchTerms(textSource).then(found => {
- if (!found) {
- return this.searchKanji(textSource);
+ async searchAt(point) {
+ let textSource = null;
+
+ try {
+ if (this.pendingLookup) {
+ return;
+ }
+
+ textSource = docRangeFromPoint(point);
+ if (!textSource || !textSource.containsPoint(point)) {
+ docImposterDestroy();
+ return;
+ }
+
+ if (this.textSourceLast && this.textSourceLast.equals(textSource)) {
+ return;
+ }
+
+ this.pendingLookup = true;
+
+ if (!await this.searchTerms(textSource)) {
+ await this.searchKanji(textSource);
+ }
+ } catch (e) {
+ if (window.yomichan_orphaned) {
+ if (textSource && this.options.scanning.modifier !== 'none') {
+ this.popup.showOrphaned(textSource.getRect(), this.options);
+ }
+ } else {
+ this.onError(e);
}
- }).catch(error => {
- this.handleError(error, textSource);
- }).then(() => {
+ } finally {
docImposterDestroy();
this.pendingLookup = false;
- });
+ }
}
- searchTerms(textSource) {
+ async searchTerms(textSource) {
textSource.setEndOffset(this.options.scanning.length);
- return bgTermsFind(textSource.text()).then(({definitions, length}) => {
- if (definitions.length === 0) {
- return false;
- } else {
- textSource.setEndOffset(length);
-
- const sentence = docSentenceExtract(textSource, this.options.anki.sentenceExt);
- const url = window.location.href;
- this.popup.showTermDefs(
- textSource.getRect(),
- definitions,
- this.options,
- {sentence, url}
- );
-
- this.lastTextSource = textSource;
- if (this.options.scanning.selectText) {
- textSource.select();
- }
+ const {definitions, length} = await apiTermsFind(textSource.text());
+ if (definitions.length === 0) {
+ return false;
+ }
- return true;
- }
- });
+ textSource.setEndOffset(length);
+
+ const sentence = docSentenceExtract(textSource, this.options.anki.sentenceExt);
+ const url = window.location.href;
+ this.popup.termsShow(
+ textSource.getRect(),
+ definitions,
+ this.options,
+ {sentence, url}
+ );
+
+ this.textSourceLast = textSource;
+ if (this.options.scanning.selectText) {
+ textSource.select();
+ }
+
+ return true;
}
- searchKanji(textSource) {
+ async searchKanji(textSource) {
textSource.setEndOffset(1);
- return bgKanjiFind(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}
- );
-
- this.lastTextSource = textSource;
- if (this.options.scanning.selectText) {
- textSource.select();
- }
+ const definitions = await apiKanjiFind(textSource.text());
+ if (definitions.length === 0) {
+ return false;
+ }
- return true;
- }
- });
+ const sentence = docSentenceExtract(textSource, this.options.anki.sentenceExt);
+ const url = window.location.href;
+ this.popup.kanjiShow(
+ textSource.getRect(),
+ definitions,
+ this.options,
+ {sentence, url}
+ );
+
+ this.textSourceLast = textSource;
+ if (this.options.scanning.selectText) {
+ textSource.select();
+ }
+
+ return true;
}
searchClear() {
docImposterDestroy();
this.popup.hide();
- if (this.options.scanning.selectText && this.lastTextSource) {
- this.lastTextSource.deselect();
+ if (this.options.scanning.selectText && this.textSourceLast) {
+ this.textSourceLast.deselect();
}
- this.lastTextSource = null;
+ this.textSourceLast = null;
}
+}
- handleError(error, textSource) {
- if (window.orphaned) {
- if (textSource && this.options.scanning.modifier !== 'none') {
- this.popup.showOrphaned(textSource.getRect(), this.options);
- }
- } else {
- window.alert(`Error: ${error}`);
- }
- }
-};
+window.yomichan_frontend = new Frontend();
+window.yomichan_frontend.prepare();
diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js
index cd7e846a..03958832 100644
--- a/ext/fg/js/popup.js
+++ b/ext/fg/js/popup.js
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 Alex Yatskov <alex@foosoft.net>
+ * Copyright (C) 2016-2017 Alex Yatskov <alex@foosoft.net>
* Author: Alex Yatskov <alex@foosoft.net>
*
* This program is free software: you can redistribute it and/or modify
@@ -20,10 +20,10 @@
class Popup {
constructor() {
this.container = document.createElement('iframe');
- this.container.id = 'yomichan-popup';
+ this.container.id = 'yomichan-float';
this.container.addEventListener('mousedown', e => e.stopPropagation());
this.container.addEventListener('scroll', e => e.stopPropagation());
- this.container.setAttribute('src', chrome.extension.getURL('/fg/frame.html'));
+ this.container.setAttribute('src', chrome.extension.getURL('/fg/float.html'));
this.container.style.width = '0px';
this.container.style.height = '0px';
this.injected = null;
@@ -40,51 +40,56 @@ class Popup {
return this.injected;
}
- show(elementRect, options) {
- return this.inject().then(() => {
- const containerStyle = window.getComputedStyle(this.container);
- const containerHeight = parseInt(containerStyle.height);
- const containerWidth = parseInt(containerStyle.width);
-
- const limitX = document.body.clientWidth;
- const limitY = window.innerHeight;
-
- let x = elementRect.left;
- let width = Math.max(containerWidth, options.general.popupWidth);
- const overflowX = Math.max(x + width - limitX, 0);
- if (overflowX > 0) {
- if (x >= overflowX) {
- x -= overflowX;
- } else {
- width = limitX;
- x = 0;
- }
- }
+ async show(elementRect, options) {
+ await this.inject();
+
+ const containerStyle = window.getComputedStyle(this.container);
+ const containerHeight = parseInt(containerStyle.height);
+ const containerWidth = parseInt(containerStyle.width);
+
+ const limitX = document.body.clientWidth;
+ const limitY = window.innerHeight;
- let y = 0;
- let height = Math.max(containerHeight, options.general.popupHeight);
- const yBelow = elementRect.bottom + options.general.popupOffset;
- const yAbove = elementRect.top - options.general.popupOffset;
- const overflowBelow = Math.max(yBelow + height - limitY, 0);
- const overflowAbove = Math.max(height - yAbove, 0);
- if (overflowBelow > 0 || overflowAbove > 0) {
- if (overflowBelow < overflowAbove) {
- height = Math.max(height - overflowBelow, 0);
- y = yBelow;
- } else {
- height = Math.max(height - overflowAbove, 0);
- y = Math.max(yAbove - height, 0);
- }
+ let x = elementRect.left;
+ let width = Math.max(containerWidth, options.general.popupWidth);
+ const overflowX = Math.max(x + width - limitX, 0);
+ if (overflowX > 0) {
+ if (x >= overflowX) {
+ x -= overflowX;
} else {
+ width = limitX;
+ x = 0;
+ }
+ }
+
+ let y = 0;
+ let height = Math.max(containerHeight, options.general.popupHeight);
+ const yBelow = elementRect.bottom + options.general.popupOffset;
+ const yAbove = elementRect.top - options.general.popupOffset;
+ const overflowBelow = Math.max(yBelow + height - limitY, 0);
+ const overflowAbove = Math.max(height - yAbove, 0);
+ if (overflowBelow > 0 || overflowAbove > 0) {
+ if (overflowBelow < overflowAbove) {
+ height = Math.max(height - overflowBelow, 0);
y = yBelow;
+ } else {
+ height = Math.max(height - overflowAbove, 0);
+ y = Math.max(yAbove - height, 0);
}
+ } else {
+ y = yBelow;
+ }
+
+ this.container.style.left = `${x}px`;
+ this.container.style.top = `${y}px`;
+ this.container.style.width = `${width}px`;
+ this.container.style.height = `${height}px`;
+ this.container.style.visibility = 'visible';
+ }
- this.container.style.left = `${x}px`;
- this.container.style.top = `${y}px`;
- this.container.style.width = `${width}px`;
- this.container.style.height = `${height}px`;
- this.container.style.visibility = 'visible';
- });
+ async showOrphaned(elementRect, options) {
+ await this.show(elementRect, options);
+ this.invokeApi('orphaned');
}
hide() {
@@ -95,22 +100,14 @@ class Popup {
return this.injected && this.container.style.visibility !== 'hidden';
}
- showTermDefs(elementRect, definitions, options, context) {
- this.show(elementRect, options).then(() => {
- this.invokeApi('showTermDefs', {definitions, options, context});
- });
- }
-
- showKanjiDefs(elementRect, definitions, options, context) {
- this.show(elementRect, options).then(() => {
- this.invokeApi('showKanjiDefs', {definitions, options, context});
- });
+ async termsShow(elementRect, definitions, options, context) {
+ await this.show(elementRect, options);
+ this.invokeApi('termsShow', {definitions, options, context});
}
- showOrphaned(elementRect, options) {
- this.show(elementRect, options).then(() => {
- this.invokeApi('showOrphaned');
- });
+ async kanjiShow(elementRect, definitions, options, context) {
+ await this.show(elementRect, options);
+ this.invokeApi('kanjiShow', {definitions, options, context});
}
invokeApi(action, params={}) {
diff --git a/ext/fg/js/source-element.js b/ext/fg/js/source-element.js
deleted file mode 100644
index a8101382..00000000
--- a/ext/fg/js/source-element.js
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2016 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/>.
- */
-
-
-class TextSourceElement {
- constructor(element, content='') {
- this.element = element;
- this.content = content;
- }
-
- clone() {
- return new TextSourceElement(this.element, this.content);
- }
-
- text() {
- return this.content;
- }
-
- setEndOffset(length) {
- switch (this.element.nodeName) {
- case 'BUTTON':
- this.content = this.element.innerHTML;
- break;
- case 'IMG':
- this.content = this.element.getAttribute('alt');
- break;
- default:
- this.content = this.element.value;
- break;
- }
-
- this.content = this.content || '';
- this.content = this.content.substring(0, length);
-
- return this.content.length;
- }
-
- setStartOffset(length) {
- return 0;
- }
-
- containsPoint(point) {
- const rect = this.getRect();
- return point.x >= rect.left && point.x <= rect.right;
- }
-
- getRect() {
- return this.element.getBoundingClientRect();
- }
-
- select() {
- // NOP
- }
-
- deselect() {
- // NOP
- }
-
- equals(other) {
- return other.element === this.element && other.content === this.content;
- }
-}
diff --git a/ext/fg/js/source-range.js b/ext/fg/js/source.js
index fa73b0a4..3b6ecb2a 100644
--- a/ext/fg/js/source-range.js
+++ b/ext/fg/js/source.js
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 Alex Yatskov <alex@foosoft.net>
+ * Copyright (C) 2016-2017 Alex Yatskov <alex@foosoft.net>
* Author: Alex Yatskov <alex@foosoft.net>
*
* This program is free software: you can redistribute it and/or modify
@@ -17,6 +17,10 @@
*/
+/*
+ * TextSourceRange
+ */
+
class TextSourceRange {
constructor(range, content='') {
this.range = range;
@@ -174,3 +178,67 @@ class TextSourceRange {
return state.remainder > 0;
}
}
+
+
+/*
+ * TextSourceElement
+ */
+
+class TextSourceElement {
+ constructor(element, content='') {
+ this.element = element;
+ this.content = content;
+ }
+
+ clone() {
+ return new TextSourceElement(this.element, this.content);
+ }
+
+ text() {
+ return this.content;
+ }
+
+ setEndOffset(length) {
+ switch (this.element.nodeName) {
+ case 'BUTTON':
+ this.content = this.element.innerHTML;
+ break;
+ case 'IMG':
+ this.content = this.element.getAttribute('alt');
+ break;
+ default:
+ this.content = this.element.value;
+ break;
+ }
+
+ this.content = this.content || '';
+ this.content = this.content.substring(0, length);
+
+ return this.content.length;
+ }
+
+ setStartOffset(length) {
+ return 0;
+ }
+
+ containsPoint(point) {
+ const rect = this.getRect();
+ return point.x >= rect.left && point.x <= rect.right;
+ }
+
+ getRect() {
+ return this.element.getBoundingClientRect();
+ }
+
+ select() {
+ // NOP
+ }
+
+ deselect() {
+ // NOP
+ }
+
+ equals(other) {
+ return other.element === this.element && other.content === this.content;
+ }
+}
diff --git a/ext/fg/js/util.js b/ext/fg/js/util.js
index c6270ce6..5eff4018 100644
--- a/ext/fg/js/util.js
+++ b/ext/fg/js/util.js
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 Alex Yatskov <alex@foosoft.net>
+ * Copyright (C) 2016-2017 Alex Yatskov <alex@foosoft.net>
* Author: Alex Yatskov <alex@foosoft.net>
*
* This program is free software: you can redistribute it and/or modify
@@ -17,11 +17,13 @@
*/
-/*
- * Background
- */
+function utilAsync(func) {
+ return function(...args) {
+ func.apply(this, args);
+ };
+}
-function bgInvoke(action, params) {
+function utilInvoke(action, params={}) {
return new Promise((resolve, reject) => {
try {
chrome.runtime.sendMessage({action, params}, ({result, error}) => {
@@ -32,185 +34,8 @@ function bgInvoke(action, params) {
}
});
} catch (e) {
- window.orphaned = true;
+ window.yomichan_orphaned = true;
reject(e.message);
}
});
}
-
-function bgOptionsGet() {
- return bgInvoke('optionsGet', {});
-}
-
-function bgTermsFind(text) {
- return bgInvoke('termsFind', {text});
-}
-
-function bgKanjiFind(text) {
- return bgInvoke('kanjiFind', {text});
-}
-
-function bgTemplateRender(template, data) {
- return bgInvoke('templateRender', {data, template});
-}
-
-function bgDefinitionsAddable(definitions, modes) {
- return bgInvoke('definitionsAddable', {definitions, modes}).catch(() => null);
-}
-
-function bgDefinitionAdd(definition, mode) {
- return bgInvoke('definitionAdd', {definition, mode});
-}
-
-
-/*
- * Document
- */
-
-function docOffsetCalc(element) {
- const scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
- const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft;
-
- const clientTop = document.documentElement.clientTop || document.body.clientTop || 0;
- const clientLeft = document.documentElement.clientLeft || document.body.clientLeft || 0;
-
- const rect = element.getBoundingClientRect();
- const top = Math.round(rect.top + scrollTop - clientTop);
- const left = Math.round(rect.left + scrollLeft - clientLeft);
-
- return {top, left};
-}
-
-function docImposterCreate(element) {
- const styleProps = window.getComputedStyle(element);
- const stylePairs = [];
- for (const key of styleProps) {
- stylePairs.push(`${key}: ${styleProps[key]};`);
- }
-
- const offset = docOffsetCalc(element);
- const imposter = document.createElement('div');
- imposter.className = 'yomichan-imposter';
- imposter.innerText = element.value;
- imposter.style.cssText = stylePairs.join('\n');
- imposter.style.position = 'absolute';
- imposter.style.top = `${offset.top}px`;
- imposter.style.left = `${offset.left}px`;
- imposter.style.zIndex = 2147483646;
- if (element.nodeName === 'TEXTAREA' && styleProps.overflow === 'visible') {
- imposter.style.overflow = 'auto';
- }
-
- document.body.appendChild(imposter);
- imposter.scrollTop = element.scrollTop;
- imposter.scrollLeft = element.scrollLeft;
-}
-
-function docImposterDestroy() {
- for (const element of document.getElementsByClassName('yomichan-imposter')) {
- element.parentNode.removeChild(element);
- }
-}
-
-function docRangeFromPoint(point) {
- const element = document.elementFromPoint(point.x, point.y);
- if (element !== null) {
- if (element.nodeName === 'IMG' || element.nodeName === 'BUTTON') {
- return new TextSourceElement(element);
- } else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {
- docImposterCreate(element);
- }
- }
-
- if (!document.caretRangeFromPoint) {
- document.caretRangeFromPoint = (x, y) => {
- const position = document.caretPositionFromPoint(x,y);
- if (position === null) {
- return null;
- }
-
- const range = document.createRange();
- range.setStart(position.offsetNode, position.offset);
- range.setEnd(position.offsetNode, position.offset);
- return range;
- };
- }
-
- const range = document.caretRangeFromPoint(point.x, point.y);
- if (range !== null) {
- return new TextSourceRange(range);
- }
-
- return null;
-}
-
-function docSentenceExtract(source, extent) {
- const quotesFwd = {'「': '」', '『': '』', "'": "'", '"': '"'};
- const quotesBwd = {'」': '「', '』': '『', "'": "'", '"': '"'};
- const terminators = '…。..??!!';
-
- const sourceLocal = source.clone();
- const position = sourceLocal.setStartOffset(extent);
- sourceLocal.setEndOffset(position + extent);
- const content = sourceLocal.text();
-
- let quoteStack = [];
-
- let startPos = 0;
- for (let i = position; i >= startPos; --i) {
- const c = content[i];
-
- if (c === '\n') {
- startPos = i + 1;
- break;
- }
-
- if (quoteStack.length === 0 && (terminators.includes(c) || c in quotesFwd)) {
- startPos = i + 1;
- break;
- }
-
- if (quoteStack.length > 0 && c === quoteStack[0]) {
- quoteStack.pop();
- } else if (c in quotesBwd) {
- quoteStack = [quotesBwd[c]].concat(quoteStack);
- }
- }
-
- quoteStack = [];
-
- let endPos = content.length;
- for (let i = position; i <= endPos; ++i) {
- const c = content[i];
-
- if (c === '\n') {
- endPos = i + 1;
- break;
- }
-
- if (quoteStack.length === 0) {
- if (terminators.includes(c)) {
- endPos = i + 1;
- break;
- }
- else if (c in quotesBwd) {
- endPos = i;
- break;
- }
- }
-
- if (quoteStack.length > 0 && c === quoteStack[0]) {
- quoteStack.pop();
- } else if (c in quotesFwd) {
- quoteStack = [quotesFwd[c]].concat(quoteStack);
- }
- }
-
- const text = content.substring(startPos, endPos);
- const padding = text.length - text.replace(/^\s+/, '').length;
-
- return {
- text: text.trim(),
- offset: position - startPos - padding
- };
-}