diff options
Diffstat (limited to 'ext/js')
-rw-r--r-- | ext/js/api.js | 38 | ||||
-rw-r--r-- | ext/js/client.js | 122 | ||||
-rw-r--r-- | ext/js/util.js | 70 |
3 files changed, 230 insertions, 0 deletions
diff --git a/ext/js/api.js b/ext/js/api.js new file mode 100644 index 00000000..7f552a1e --- /dev/null +++ b/ext/js/api.js @@ -0,0 +1,38 @@ +/* + * 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/>. + */ + + +function sendMessage(action, data, callback) { + chrome.runtime.sendMessage({action: action, data: data}, callback); +} + +function findTerm(text, callback) { + sendMessage('findTerm', {text: text}, callback); +} + +function findKanji(text, callback) { + sendMessage('findKanji', {text: text}, callback); +} + +function renderTemplate(data, template, callback) { + sendMessage('renderTemplate', {data: data, template: template}, callback); +} + +function getState(callback) { + sendMessage('getState', null, callback); +} diff --git a/ext/js/client.js b/ext/js/client.js new file mode 100644 index 00000000..2d9a470f --- /dev/null +++ b/ext/js/client.js @@ -0,0 +1,122 @@ +/* + * 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 Client { + constructor() { + this.popupOffset = 10; + this.lastMosePos = null; + this.enabled = false; + + this.popup = document.createElement('div'); + this.popup.classList.add('yomichan-popup'); + this.popup.addEventListener('mousedown', (e) => e.stopPropagation()); + this.popup.addEventListener('scroll', (e) => e.stopPropagation()); + document.body.appendChild(this.popup); + + chrome.runtime.onMessage.addListener(this.onMessage.bind(this)); + window.addEventListener('mousedown', this.onMouseDown.bind(this)); + window.addEventListener('mousemove', this.onMouseMove.bind(this)); + window.addEventListener('keydown', this.onKeyDown.bind(this)); + window.addEventListener('scroll', (e) => this.hidePopup()); + window.addEventListener('resize', (e) => this.hidePopup()); + + getState((state) => this.setEnabled(state === 'enabled')); + } + + onKeyDown(e) { + if (this.enabled && this.lastMousePos !== null && (e.keyCode === 16 || e.charCode === 16)) { + this.searchAtPoint(this.lastMousePos); + } + } + + onMouseMove(e) { + this.lastMousePos = {x: e.clientX, y: e.clientY}; + if (this.enabled && (e.shiftKey || e.which === 2)) { + this.searchAtPoint(this.lastMousePos); + } + } + + onMouseDown(e) { + this.lastMousePos = {x: e.clientX, y: e.clientY}; + if (this.enabled && (e.shiftKey || e.which === 2)) { + this.searchAtPoint(this.lastMousePos); + } else { + this.hidePopup(); + } + } + + onMessage(request, sender, callback) { + this.setEnabled(request === 'enabled'); + callback(); + } + + searchAtPoint(point) { + const range = getRangeAtPoint(point, 10); + if (range === null) { + this.hidePopup(); + return; + } + + const rect = getRangePaddedRect(range); + if (point.x < rect.left || point.x > rect.right) { + this.hidePopup(); + return; + } + + findTerm(range.toString(), ({results, length}) => { + if (length === 0) { + this.hidePopup(); + } else { + range.setEnd(range.endContainer, range.startOffset + length); + renderTemplate({defs: results}, 'defs.html', (html) => { + this.popup.innerHTML = html; + this.showPopup(range); + }); + } + }); + } + + showPopup(range) { + const selection = window.getSelection(); + selection.removeAllRanges(); + selection.addRange(range); + + const pos = getPopupPositionForRange(this.popup, range, this.popupOffset); + + this.popup.style.left = pos.x + 'px'; + this.popup.style.top = pos.y + 'px'; + this.popup.style.visibility = 'visible'; + } + + hidePopup() { + if (this.popup.style.visibility !== 'hidden') { + const selection = window.getSelection(); + selection.removeAllRanges(); + this.popup.style.visibility = 'hidden'; + } + } + + setEnabled(enabled) { + if (!(this.enabled = enabled)) { + this.hidePopup(); + } + } +} + +window.yomiClient = new Client(); diff --git a/ext/js/util.js b/ext/js/util.js new file mode 100644 index 00000000..eb153e9b --- /dev/null +++ b/ext/js/util.js @@ -0,0 +1,70 @@ +/* + * 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/>. + */ + + +function getRangeAtPoint(point, lookAhead) { + const range = document.caretRangeFromPoint(point.x, point.y); + if (range === null) { + return null; + } + + const node = range.startContainer; + if (node.nodeType !== 3 /* TEXT_NODE */) { + return null; + } + + const offset = range.startOffset; + const length = Math.min(node.length - offset, lookAhead); + if (length === 0) { + return null; + } + + range.setEnd(node, offset + length); + return range; +} + +function getRangePaddedRect(range) { + const node = range.startContainer; + const startOffset = range.startOffset; + const endOffset = range.endOffset; + + range.setStart(node, Math.max(0, startOffset - 1)); + range.setEnd(node, Math.min(node.length, endOffset + 1)); + const rect = range.getBoundingClientRect(); + range.setStart(node, startOffset); + range.setEnd(node, endOffset); + + return rect; +} + +function getPopupPositionForRange(popup, range, offset) { + const rangeRect = range.getBoundingClientRect(); + const popupRect = popup.getBoundingClientRect(); + + let posX = rangeRect.left; + if (posX + popupRect.width >= window.innerWidth) { + posX = window.innerWidth - popupRect.width; + } + + let posY = rangeRect.bottom + offset; + if (posY + popupRect.height >= window.innerHeight) { + posY = rangeRect.top - popupRect.height - offset; + } + + return {x: posX, y: posY}; +} |