aboutsummaryrefslogtreecommitdiff
path: root/ext/fg
diff options
context:
space:
mode:
authorAlex Yatskov <alex@foosoft.net>2019-04-28 11:47:04 -0700
committerAlex Yatskov <alex@foosoft.net>2019-04-28 11:47:04 -0700
commit9b00daed07db8d70485f2fee22bb1a9b118aa186 (patch)
treec8c7e9f90d45be017e881abacdd2c328674bb506 /ext/fg
parent162f3c3836f83660cbb7f01cc724367216eab3e2 (diff)
parentc7f8d0874a758924802179a44757153bbf18bf7f (diff)
Merge branch 'master' of https://github.com/FooSoft/yomichan
Diffstat (limited to 'ext/fg')
-rw-r--r--ext/fg/css/client.css22
-rw-r--r--ext/fg/js/document.js12
-rw-r--r--ext/fg/js/frontend.js194
-rw-r--r--ext/fg/js/popup.js4
4 files changed, 229 insertions, 3 deletions
diff --git a/ext/fg/css/client.css b/ext/fg/css/client.css
index b5b1f6bd..a9b8e025 100644
--- a/ext/fg/css/client.css
+++ b/ext/fg/css/client.css
@@ -27,3 +27,25 @@ iframe#yomichan-float {
visibility: hidden;
z-index: 2147483647;
}
+
+iframe#yomichan-float.yomichan-float-full-width {
+ border-left: none;
+ border-right: none;
+ left: 0 !important;
+ right: 0 !important;
+ width: 100% !important;
+ box-sizing: border-box;
+ resize: none;
+}
+
+iframe#yomichan-float.yomichan-float-full-width:not(.yomichan-float-above) {
+ border-bottom: none;
+ top: auto !important;
+ bottom: 0 !important;
+}
+
+iframe#yomichan-float.yomichan-float-full-width.yomichan-float-above {
+ border-top: none;
+ top: 0 !important;
+ bottom: auto !important;
+}
diff --git a/ext/fg/js/document.js b/ext/fg/js/document.js
index 9366832e..13acb036 100644
--- a/ext/fg/js/document.js
+++ b/ext/fg/js/document.js
@@ -85,14 +85,24 @@ function docRangeFromPoint(point) {
range.setEnd(position.offsetNode, position.offset);
return range;
}
+ return null;
};
}
const range = document.caretRangeFromPoint(point.x, point.y);
+ if (range === null) {
+ return;
+ }
if(imposter !== null) imposter.style.zIndex = -2147483646;
- const rect = range.getClientRects()[0];
+ const rects = range.getClientRects();
+
+ if (rects.length === 0) {
+ return;
+ }
+
+ const rect = rects[0];
if (point.y > rect.bottom + 2) {
return;
}
diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js
index 266f9640..a81cbef8 100644
--- a/ext/fg/js/frontend.js
+++ b/ext/fg/js/frontend.js
@@ -26,6 +26,14 @@ class Frontend {
this.textSourceLast = null;
this.pendingLookup = false;
this.options = null;
+
+ this.primaryTouchIdentifier = null;
+ this.contextMenuChecking = false;
+ this.contextMenuPrevent = false;
+ this.contextMenuPreviousRange = null;
+ this.mouseDownPrevent = false;
+ this.clickPrevent = false;
+ this.scrollPrevent = false;
}
async prepare() {
@@ -39,6 +47,15 @@ class Frontend {
window.addEventListener('mouseup', this.onMouseUp.bind(this));
window.addEventListener('resize', this.onResize.bind(this));
+ if (this.options.scanning.touchInputEnabled) {
+ window.addEventListener('click', this.onClick.bind(this));
+ window.addEventListener('touchstart', this.onTouchStart.bind(this));
+ window.addEventListener('touchend', this.onTouchEnd.bind(this));
+ window.addEventListener('touchcancel', this.onTouchCancel.bind(this));
+ window.addEventListener('touchmove', this.onTouchMove.bind(this), {passive: false});
+ window.addEventListener('contextmenu', this.onContextMenu.bind(this));
+ }
+
chrome.runtime.onMessage.addListener(this.onBgMessage.bind(this));
} catch (e) {
this.onError(e);
@@ -79,7 +96,7 @@ class Frontend {
const search = async () => {
try {
- await this.searchAt({x: e.clientX, y: e.clientY});
+ await this.searchAt({x: e.clientX, y: e.clientY}, 'mouse');
} catch (e) {
this.onError(e);
}
@@ -93,6 +110,14 @@ class Frontend {
}
onMouseDown(e) {
+ if (this.mouseDownPrevent) {
+ this.setMouseDownPrevent(false, false);
+ this.setClickPrevent(true);
+ e.preventDefault();
+ e.stopPropagation();
+ return false;
+ }
+
this.mousePosLast = {x: e.clientX, y: e.clientY};
this.popupTimerClear();
this.searchClear();
@@ -133,6 +158,85 @@ class Frontend {
this.searchClear();
}
+ onClick(e) {
+ if (this.clickPrevent) {
+ this.setClickPrevent(false);
+ e.preventDefault();
+ e.stopPropagation();
+ return false;
+ }
+ }
+
+ onTouchStart(e) {
+ if (this.primaryTouchIdentifier !== null && this.getIndexOfTouch(e.touches, this.primaryTouchIdentifier) >= 0) {
+ return;
+ }
+
+ this.setPrimaryTouch(this.getPrimaryTouch(e.changedTouches));
+ }
+
+ onTouchEnd(e) {
+ if (this.primaryTouchIdentifier === null) {
+ return;
+ }
+
+ if (this.getIndexOfTouch(e.changedTouches, this.primaryTouchIdentifier) < 0) {
+ return;
+ }
+
+ this.setPrimaryTouch(this.getPrimaryTouch(this.excludeTouches(e.touches, e.changedTouches)));
+ }
+
+ onTouchCancel(e) {
+ this.onTouchEnd(e);
+ }
+
+ onTouchMove(e) {
+ if (!this.scrollPrevent || this.primaryTouchIdentifier === null) {
+ return;
+ }
+
+ const touches = e.changedTouches;
+ const index = this.getIndexOfTouch(touches, this.primaryTouchIdentifier);
+ if (index < 0) {
+ return;
+ }
+
+ const touch = touches[index];
+ this.searchFromTouch(touch.clientX, touch.clientY, 'touchMove');
+
+ e.preventDefault(); // Disable scroll
+ }
+
+ onContextMenu(e) {
+ if (this.contextMenuPrevent) {
+ this.setContextMenuPrevent(false, false);
+ e.preventDefault();
+ e.stopPropagation();
+ return false;
+ }
+ }
+
+ onAfterSearch(newRange, type, searched, success) {
+ if (type === 'mouse') {
+ return;
+ }
+
+ if (
+ !this.contextMenuChecking ||
+ (this.contextMenuPreviousRange === null ? newRange === null : this.contextMenuPreviousRange.equals(newRange))) {
+ return;
+ }
+
+ if (type === 'touchStart' && newRange !== null) {
+ this.scrollPrevent = true;
+ }
+
+ this.setContextMenuPrevent(true, false);
+ this.setMouseDownPrevent(true, false);
+ this.contextMenuChecking = false;
+ }
+
onBgMessage({action, params}, sender, callback) {
const handlers = {
optionsSet: options => {
@@ -167,18 +271,22 @@ class Frontend {
}
}
- async searchAt(point) {
+ async searchAt(point, type) {
if (this.pendingLookup || this.popup.containsPoint(point)) {
return;
}
const textSource = docRangeFromPoint(point);
let hideResults = !textSource || !textSource.containsPoint(point);
+ let searched = false;
+ let success = false;
try {
if (!hideResults && (!this.textSourceLast || !this.textSourceLast.equals(textSource))) {
+ searched = true;
this.pendingLookup = true;
hideResults = !await this.searchTerms(textSource) && !await this.searchKanji(textSource);
+ success = true;
}
} catch (e) {
if (window.yomichan_orphaned) {
@@ -196,6 +304,7 @@ class Frontend {
}
this.pendingLookup = false;
+ this.onAfterSearch(this.textSourceLast, type, searched, success);
}
}
@@ -262,6 +371,87 @@ class Frontend {
this.textSourceLast = null;
}
+
+ getPrimaryTouch(touchList) {
+ return touchList.length > 0 ? touchList[0] : null;
+ }
+
+ getIndexOfTouch(touchList, identifier) {
+ for (let i in touchList) {
+ let t = touchList[i];
+ if (t.identifier === identifier) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ excludeTouches(touchList, excludeTouchList) {
+ const result = [];
+ for (let r of touchList) {
+ if (this.getIndexOfTouch(excludeTouchList, r.identifier) < 0) {
+ result.push(r);
+ }
+ }
+ return result;
+ }
+
+ setPrimaryTouch(touch) {
+ if (touch === null) {
+ this.primaryTouchIdentifier = null;
+ this.contextMenuPreviousRange = null;
+ this.contextMenuChecking = false;
+ this.scrollPrevent = false;
+ this.setContextMenuPrevent(false, true);
+ this.setMouseDownPrevent(false, true);
+ this.setClickPrevent(false);
+ }
+ else {
+ this.primaryTouchIdentifier = touch.identifier;
+ this.contextMenuPreviousRange = this.textSourceLast ? this.textSourceLast.clone() : null;
+ this.contextMenuChecking = true;
+ this.scrollPrevent = false;
+ this.setContextMenuPrevent(false, false);
+ this.setMouseDownPrevent(false, false);
+ this.setClickPrevent(false);
+
+ this.searchFromTouch(touch.clientX, touch.clientY, 'touchStart');
+ }
+ }
+
+ setContextMenuPrevent(value, delay) {
+ if (!delay) {
+ this.contextMenuPrevent = value;
+ }
+ }
+
+ setMouseDownPrevent(value, delay) {
+ if (!delay) {
+ this.mouseDownPrevent = value;
+ }
+ }
+
+ setClickPrevent(value) {
+ this.clickPrevent = value;
+ }
+
+ searchFromTouch(x, y, type) {
+ this.popupTimerClear();
+
+ if (!this.options.general.enable || this.pendingLookup) {
+ return;
+ }
+
+ const search = async () => {
+ try {
+ await this.searchAt({x, y}, type);
+ } catch (e) {
+ this.onError(e);
+ }
+ };
+
+ search();
+ }
}
window.yomichan_frontend = new Frontend();
diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js
index a17b184a..14276efe 100644
--- a/ext/fg/js/popup.js
+++ b/ext/fg/js/popup.js
@@ -62,6 +62,7 @@ class Popup {
}
}
+ let above = false;
let y = 0;
let height = Math.max(containerHeight, options.general.popupHeight);
const yBelow = elementRect.bottom + options.general.popupOffset;
@@ -75,11 +76,14 @@ class Popup {
} else {
height = Math.max(height - overflowAbove, 0);
y = Math.max(yAbove - height, 0);
+ above = true;
}
} else {
y = yBelow;
}
+ this.container.classList.toggle('yomichan-float-full-width', options.general.popupDisplayMode === 'full-width');
+ this.container.classList.toggle('yomichan-float-above', above);
this.container.style.left = `${x}px`;
this.container.style.top = `${y}px`;
this.container.style.width = `${width}px`;