diff options
| author | Alex Yatskov <alex@foosoft.net> | 2016-04-04 22:02:26 -0700 | 
|---|---|---|
| committer | Alex Yatskov <alex@foosoft.net> | 2016-04-04 22:02:26 -0700 | 
| commit | 37ffcbf3ee2d9722751e899bdf4bac91ca7c92c1 (patch) | |
| tree | ee2c320602cbf5bb846d2b2832375e8e4650f11e /ext/js | |
| parent | e5a3904cf7a90519aaf7d7529179156eb4b28fdf (diff) | |
File reorg
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}; +} |