diff options
Diffstat (limited to 'ext/fg')
-rw-r--r-- | ext/fg/css/client.css | 22 | ||||
-rw-r--r-- | ext/fg/float.html | 1 | ||||
-rw-r--r-- | ext/fg/js/document.js | 12 | ||||
-rw-r--r-- | ext/fg/js/frontend.js | 196 | ||||
-rw-r--r-- | ext/fg/js/popup.js | 4 | ||||
-rw-r--r-- | ext/fg/js/util.js | 12 |
6 files changed, 239 insertions, 8 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/float.html b/ext/fg/float.html index 89872cce..fed7eeab 100644 --- a/ext/fg/float.html +++ b/ext/fg/float.html @@ -2,6 +2,7 @@ <html lang="en"> <head> <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width,initial-scale=1" /> <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"> 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..bd652f3b 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 => { @@ -152,7 +256,7 @@ class Frontend { } onError(error) { - window.alert(`Error: ${error.toString ? error.toString() : error}`); + console.log(error); } popupTimerSet(callback) { @@ -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 58e0672e..d2acf4d0 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.popupVerticalOffset; @@ -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`; diff --git a/ext/fg/js/util.js b/ext/fg/js/util.js index 5eff4018..954b3988 100644 --- a/ext/fg/js/util.js +++ b/ext/fg/js/util.js @@ -26,11 +26,15 @@ function utilAsync(func) { function utilInvoke(action, params={}) { return new Promise((resolve, reject) => { try { - chrome.runtime.sendMessage({action, params}, ({result, error}) => { - if (error) { - reject(error); + chrome.runtime.sendMessage({action, params}, (response) => { + if (response !== null && typeof response === 'object') { + if (response.error) { + reject(response.error); + } else { + resolve(response.result); + } } else { - resolve(result); + reject(`Unexpected response of type ${typeof response}`); } }); } catch (e) { |