diff options
Diffstat (limited to 'ext/fg')
-rw-r--r-- | ext/fg/float.html | 5 | ||||
-rw-r--r-- | ext/fg/js/document.module.js | 177 | ||||
-rw-r--r-- | ext/fg/js/float.js | 36 | ||||
-rw-r--r-- | ext/fg/js/source.module.js | 244 |
4 files changed, 39 insertions, 423 deletions
diff --git a/ext/fg/float.html b/ext/fg/float.html index 4860753a..07f2d58b 100644 --- a/ext/fg/float.html +++ b/ext/fg/float.html @@ -36,7 +36,10 @@ <script src="/fg/js/api.js"></script> <script src="/fg/js/util.js"></script> + <script src="/fg/js/document.js"></script> + <script src="/fg/js/source.js"></script> + <script src="/mixed/js/display.js"></script> - <script type="module" src="/fg/js/float.js"></script> + <script src="/fg/js/float.js"></script> </body> </html> diff --git a/ext/fg/js/document.module.js b/ext/fg/js/document.module.js deleted file mode 100644 index d86aff33..00000000 --- a/ext/fg/js/document.module.js +++ /dev/null @@ -1,177 +0,0 @@ -/* - * 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/>. - */ - -import {TextSourceRange, TextSourceElement} from './source.module.js'; - -export 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}; -} - -export 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; - - return imposter; -} - -export function docImposterDestroy() { - for (const element of document.getElementsByClassName('yomichan-imposter')) { - element.parentNode.removeChild(element); - } -} - -export function docRangeFromPoint(point) { - const element = document.elementFromPoint(point.x, point.y); - let imposter = null; - if (element) { - if (element.nodeName === 'IMG' || element.nodeName === 'BUTTON') { - return new TextSourceElement(element); - } else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') { - imposter = docImposterCreate(element); - } - } - - if (!document.caretRangeFromPoint) { - document.caretRangeFromPoint = (x, y) => { - const position = document.caretPositionFromPoint(x,y); - if (position && position.offsetNode && position.offsetNode.nodeType === Node.TEXT_NODE) { - const range = document.createRange(); - range.setStart(position.offsetNode, position.offset); - 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 rects = range.getClientRects(); - for (const rect of rects) { - if (point.y <= rect.bottom + 2) { - return new TextSourceRange(range); - } - } -} - -export 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/float.js b/ext/fg/js/float.js index 9301135b..65ed89a1 100644 --- a/ext/fg/js/float.js +++ b/ext/fg/js/float.js @@ -16,7 +16,6 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -import {Display} from '../../mixed/js/display.js'; class DisplayFloat extends Display { constructor() { @@ -124,6 +123,41 @@ class DisplayFloat extends Display { parent.appendChild(this.styleNode); } } + + async onTermLookup(e) { + try { + e.preventDefault(); + + const clickedElement = $(e.target); + const textSource = docRangeFromPoint({x: e.clientX, y: e.clientY}); + textSource.setEndOffset(this.options.scanning.length); + + const {definitions, length} = await apiTermsFind(textSource.text()); + if (definitions.length === 0) { + return false; + } + + textSource.setEndOffset(length); + + const sentence = docSentenceExtract(textSource, this.options.anki.sentenceExt); + + const context = { + source: { + definitions: this.definitions, + index: Display.entryIndexFind(clickedElement) + } + }; + + if (this.context) { + context.sentence = sentence; + context.url = this.context.url; + } + + this.termsShow(definitions, this.options, context); + } catch (e) { + this.onError(e); + } + } } window.yomichan_display = new DisplayFloat(); diff --git a/ext/fg/js/source.module.js b/ext/fg/js/source.module.js deleted file mode 100644 index ed9263c5..00000000 --- a/ext/fg/js/source.module.js +++ /dev/null @@ -1,244 +0,0 @@ -/* - * 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/>. - */ - - -/* - * TextSourceRange - */ - -export class TextSourceRange { - constructor(range, content='') { - this.range = range; - this.content = content; - } - - clone() { - return new TextSourceRange(this.range.cloneRange(), this.content); - } - - text() { - return this.content; - } - - setEndOffset(length) { - const state = TextSourceRange.seekForward(this.range.startContainer, this.range.startOffset, length); - this.range.setEnd(state.node, state.offset); - this.content = state.content; - return length - state.remainder; - } - - setStartOffset(length) { - const state = TextSourceRange.seekBackward(this.range.startContainer, this.range.startOffset, length); - this.range.setStart(state.node, state.offset); - this.content = state.content; - return length - state.remainder; - } - - containsPoint(point) { - const rect = this.getPaddedRect(); - return point.x >= rect.left && point.x <= rect.right; - } - - getRect() { - return this.range.getBoundingClientRect(); - } - - getPaddedRect() { - const range = this.range.cloneRange(); - const startOffset = range.startOffset; - const endOffset = range.endOffset; - const node = range.startContainer; - - range.setStart(node, Math.max(0, startOffset - 1)); - range.setEnd(node, Math.min(node.length, endOffset + 1)); - - return range.getBoundingClientRect(); - } - - select() { - const selection = window.getSelection(); - selection.removeAllRanges(); - selection.addRange(this.range); - } - - deselect() { - const selection = window.getSelection(); - selection.removeAllRanges(); - } - - equals(other) { - return other && other.range && other.range.compareBoundaryPoints(Range.START_TO_START, this.range) === 0; - } - - static shouldEnter(node) { - if (node.nodeType !== 1) { - return false; - } - - const skip = ['RT', 'SCRIPT', 'STYLE']; - if (skip.includes(node.nodeName)) { - return false; - } - - const style = window.getComputedStyle(node); - const hidden = - style.visibility === 'hidden' || - style.display === 'none' || - parseFloat(style.fontSize) === 0; - - return !hidden; - } - - static seekForward(node, offset, length) { - const state = {node, offset, remainder: length, content: ''}; - if (!TextSourceRange.seekForwardHelper(node, state)) { - return state; - } - - for (let current = node; current !== null; current = current.parentElement) { - for (let sibling = current.nextSibling; sibling !== null; sibling = sibling.nextSibling) { - if (!TextSourceRange.seekForwardHelper(sibling, state)) { - return state; - } - } - } - - return state; - } - - static seekForwardHelper(node, state) { - if (node.nodeType === 3 && node.parentElement && TextSourceRange.shouldEnter(node.parentElement)) { - const offset = state.node === node ? state.offset : 0; - const remaining = node.length - offset; - const consumed = Math.min(remaining, state.remainder); - state.content = state.content + node.nodeValue.substring(offset, offset + consumed); - state.node = node; - state.offset = offset + consumed; - state.remainder -= consumed; - } else if (TextSourceRange.shouldEnter(node)) { - for (let i = 0; i < node.childNodes.length; ++i) { - if (!TextSourceRange.seekForwardHelper(node.childNodes[i], state)) { - break; - } - } - } - - return state.remainder > 0; - } - - static seekBackward(node, offset, length) { - const state = {node, offset, remainder: length, content: ''}; - if (!TextSourceRange.seekBackwardHelper(node, state)) { - return state; - } - - for (let current = node; current !== null; current = current.parentElement) { - for (let sibling = current.previousSibling; sibling !== null; sibling = sibling.previousSibling) { - if (!TextSourceRange.seekBackwardHelper(sibling, state)) { - return state; - } - } - } - - return state; - } - - static seekBackwardHelper(node, state) { - if (node.nodeType === 3 && node.parentElement && TextSourceRange.shouldEnter(node.parentElement)) { - const offset = state.node === node ? state.offset : node.length; - const remaining = offset; - const consumed = Math.min(remaining, state.remainder); - state.content = node.nodeValue.substring(offset - consumed, offset) + state.content; - state.node = node; - state.offset = offset - consumed; - state.remainder -= consumed; - } else if (TextSourceRange.shouldEnter(node)) { - for (let i = node.childNodes.length - 1; i >= 0; --i) { - if (!TextSourceRange.seekBackwardHelper(node.childNodes[i], state)) { - break; - } - } - } - - return state.remainder > 0; - } -} - - -/* - * TextSourceElement - */ - -export 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 && other.element === this.element && other.content === this.content; - } -} |