diff options
Diffstat (limited to 'ext/fg/js')
| -rw-r--r-- | ext/fg/js/api.js | 58 | ||||
| -rw-r--r-- | ext/fg/js/document.js | 163 | ||||
| -rw-r--r-- | ext/fg/js/float.js (renamed from ext/fg/js/display-frame.js) | 64 | ||||
| -rw-r--r-- | ext/fg/js/frontend.js (renamed from ext/fg/js/driver.js) | 230 | ||||
| -rw-r--r-- | ext/fg/js/popup.js | 111 | ||||
| -rw-r--r-- | ext/fg/js/source-element.js | 77 | ||||
| -rw-r--r-- | ext/fg/js/source.js (renamed from ext/fg/js/source-range.js) | 70 | ||||
| -rw-r--r-- | ext/fg/js/util.js | 191 | 
8 files changed, 504 insertions, 460 deletions
| diff --git a/ext/fg/js/api.js b/ext/fg/js/api.js new file mode 100644 index 00000000..151882eb --- /dev/null +++ b/ext/fg/js/api.js @@ -0,0 +1,58 @@ +/* + * 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/>. + */ + + +function apiOptionsSet(options) { +    return utilInvoke('optionsSet', {options}); +} + +function apiOptionsGet() { +    return utilInvoke('optionsGet'); +} + +function apiTermsFind(text) { +    return utilInvoke('termsFind', {text}); +} + +function apiKanjiFind(text) { +    return utilInvoke('kanjiFind', {text}); +} + +function apiDefinitionAdd(definition, mode) { +    return utilInvoke('definitionAdd', {definition, mode}); +} + +function apiDefinitionsAddable(definitions, modes) { +    return utilInvoke('definitionsAddable', {definitions, modes}).catch(() => null); +} + +function apiNoteView(noteId) { +    return utilInvoke('noteView', {noteId}); +} + +function apiTemplateRender(template, data) { +    return utilInvoke('templateRender', {data, template}); +} + +function apiCommandExec(command) { +    return utilInvoke('commandExec', {command}); +} + +function apiAudioGetUrl(definition, source) { +    return utilInvoke('audioGetUrl', {definition, source}); +} diff --git a/ext/fg/js/document.js b/ext/fg/js/document.js new file mode 100644 index 00000000..26c85b40 --- /dev/null +++ b/ext/fg/js/document.js @@ -0,0 +1,163 @@ +/* + * 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/>. + */ + + +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}; +} + +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; +} + +function docImposterDestroy() { +    for (const element of document.getElementsByClassName('yomichan-imposter')) { +        element.parentNode.removeChild(element); +    } +} + +function docRangeFromPoint(point) { +    const element = document.elementFromPoint(point.x, point.y); +    if (element) { +        if (element.nodeName === 'IMG' || element.nodeName === 'BUTTON') { +            return new TextSourceElement(element); +        } else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') { +            docImposterCreate(element); +        } +    } + +    if (!document.caretRangeFromPoint) { +        document.caretRangeFromPoint = (x, y) => { +            const position = document.caretPositionFromPoint(x,y); +            if (position) { +                const range = document.createRange(); +                range.setStart(position.offsetNode, position.offset); +                range.setEnd(position.offsetNode, position.offset); +                return range; +            } +        }; +    } + +    const range = document.caretRangeFromPoint(point.x, point.y); +    if (range) { +        return new TextSourceRange(range); +    } +} + +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/display-frame.js b/ext/fg/js/float.js index 9fd09e74..22374f8b 100644 --- a/ext/fg/js/display-frame.js +++ b/ext/fg/js/float.js @@ -1,5 +1,5 @@  /* - * Copyright (C) 2016  Alex Yatskov <alex@foosoft.net> + * 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 @@ -17,61 +17,45 @@   */ -window.displayFrame = new class extends Display { +class DisplayFloat extends Display {      constructor() { -        super($('#spinner'), $('#content')); -        $(window).on('message', this.onMessage.bind(this)); +        super($('#spinner'), $('#definitions')); +        $(window).on('message', utilAsync(this.onMessage.bind(this)));      } -    definitionAdd(definition, mode) { -        return bgDefinitionAdd(definition, mode); -    } - -    definitionsAddable(definitions, modes) { -        return bgDefinitionsAddable(definitions, modes); -    } - -    templateRender(template, data) { -        return bgTemplateRender(template, data); -    } - -    kanjiFind(character) { -        return bgKanjiFind(character); -    } - -    handleError(error) { -        if (window.orphaned) { -            this.showOrphaned(); +    onError(error) { +        if (window.yomichan_orphaned) { +            this.onOrphaned();          } else {              window.alert(`Error: ${error}`);          }      } -    clearSearch() { -        window.parent.postMessage('popupClose', '*'); +    onOrphaned() { +        $('#definitions').hide(); +        $('#error-orphaned').show();      } -    selectionCopy() { -        window.parent.postMessage('selectionCopy', '*'); +    onSearchClear() { +        window.parent.postMessage('popupClose', '*');      } -    showOrphaned() { -        $('#content').hide(); -        $('#orphan').show(); +    onSelectionCopy() { +        window.parent.postMessage('selectionCopy', '*');      }      onMessage(e) {          const handlers = { -            showTermDefs: ({definitions, options, context}) => { -                this.showTermDefs(definitions, options, context); +            termsShow: ({definitions, options, context}) => { +                this.termsShow(definitions, options, context);              }, -            showKanjiDefs: ({definitions, options, context}) => { -                this.showKanjiDefs(definitions, options, context); +            kanjiShow: ({definitions, options, context}) => { +                this.kanjiShow(definitions, options, context);              }, -            showOrphaned: () => { -                this.showOrphaned(); +            orphaned: () => { +                this.onOrphaned();              }          }; @@ -85,8 +69,8 @@ window.displayFrame = new class extends Display {      onKeyDown(e) {          const handlers = {              67: /* c */ () => { -                if (e.ctrlKey && window.getSelection().toString() === '') { -                    this.selectionCopy(); +                if (e.ctrlKey && !window.getSelection().toString()) { +                    this.onSelectionCopy();                      return true;                  }              } @@ -99,4 +83,6 @@ window.displayFrame = new class extends Display {              super.onKeyDown(e);          }      } -}; +} + +window.yomichan_display = new DisplayFloat(); diff --git a/ext/fg/js/driver.js b/ext/fg/js/frontend.js index b0cc4613..41c93f00 100644 --- a/ext/fg/js/driver.js +++ b/ext/fg/js/frontend.js @@ -1,5 +1,5 @@  /* - * Copyright (C) 2016  Alex Yatskov <alex@foosoft.net> + * 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 @@ -17,38 +17,31 @@   */ -window.driver = new class { +class Frontend {      constructor() {          this.popup = new Popup();          this.popupTimer = null; -        this.lastMousePos = null;          this.mouseDownLeft = false;          this.mouseDownMiddle = false; -        this.lastTextSource = null; +        this.textSourceLast = null;          this.pendingLookup = false;          this.options = null; +    } -        bgOptionsGet().then(options => { -            this.options = options; -            window.addEventListener('mouseover', this.onMouseOver.bind(this)); +    async prepare() { +        try { +            this.options = await apiOptionsGet(); + +            window.addEventListener('message', this.onFrameMessage.bind(this));              window.addEventListener('mousedown', this.onMouseDown.bind(this)); -            window.addEventListener('mouseup', this.onMouseUp.bind(this));              window.addEventListener('mousemove', this.onMouseMove.bind(this)); -            window.addEventListener('resize', e => this.searchClear()); -            window.addEventListener('message', this.onFrameMessage.bind(this)); -            chrome.runtime.onMessage.addListener(this.onBgMessage.bind(this)); -        }).catch(this.handleError.bind(this)); -    } - -    popupTimerSet(callback) { -        this.popupTimerClear(); -        this.popupTimer = window.setTimeout(callback, this.options.scanning.delay); -    } +            window.addEventListener('mouseover', this.onMouseOver.bind(this)); +            window.addEventListener('mouseup', this.onMouseUp.bind(this)); +            window.addEventListener('resize', this.onResize.bind(this)); -    popupTimerClear() { -        if (this.popupTimer) { -            window.clearTimeout(this.popupTimer); -            this.popupTimer = null; +            chrome.runtime.onMessage.addListener(this.onBgMessage.bind(this)); +        } catch (e) { +            this.onError(e);          }      } @@ -59,7 +52,6 @@ window.driver = new class {      }      onMouseMove(e) { -        this.lastMousePos = {x: e.clientX, y: e.clientY};          this.popupTimerClear();          if (!this.options.general.enable) { @@ -70,6 +62,10 @@ window.driver = new class {              return;          } +        if (this.pendingLookup) { +            return; +        } +          const mouseScan = this.mouseDownMiddle && this.options.scanning.middleMouse;          const keyScan =              this.options.scanning.modifier === 'alt' && e.altKey || @@ -81,16 +77,24 @@ window.driver = new class {              return;          } -        const searchFunc = () => this.searchAt(this.lastMousePos); +        const search = async () => { +            try { +                await this.searchAt({x: e.clientX, y: e.clientY}); +                this.pendingLookup = false; +            } catch (e) { +                this.onError(e); +            } +        }; +          if (this.options.scanning.modifier === 'none') { -            this.popupTimerSet(searchFunc); +            this.popupTimerSet(search);          } else { -            searchFunc(); +            search();          }      }      onMouseDown(e) { -        this.lastMousePos = {x: e.clientX, y: e.clientY}; +        this.mousePosLast = {x: e.clientX, y: e.clientY};          this.popupTimerClear();          this.searchClear(); @@ -126,6 +130,10 @@ window.driver = new class {          }      } +    onResize() { +        this.searchClear(); +    } +      onBgMessage({action, params}, sender, callback) {          const handlers = {              optionsSet: options => { @@ -144,106 +152,122 @@ window.driver = new class {          callback();      } -    searchAt(point) { -        if (this.pendingLookup) { -            return; -        } +    onError(error) { +        window.alert(`Error: ${error}`); +    } -        const textSource = docRangeFromPoint(point); -        if (!textSource || !textSource.containsPoint(point)) { -            docImposterDestroy(); -            return; -        } +    popupTimerSet(callback) { +        this.popupTimerClear(); +        this.popupTimer = window.setTimeout(callback, this.options.scanning.delay); +    } -        if (this.lastTextSource && this.lastTextSource.equals(textSource)) { -            return; +    popupTimerClear() { +        if (this.popupTimer) { +            window.clearTimeout(this.popupTimer); +            this.popupTimer = null;          } +    } -        this.pendingLookup = true; -        this.searchTerms(textSource).then(found => { -            if (!found) { -                return this.searchKanji(textSource); +    async searchAt(point) { +        let textSource = null; + +        try { +            if (this.pendingLookup) { +                return; +            } + +            textSource = docRangeFromPoint(point); +            if (!textSource || !textSource.containsPoint(point)) { +                docImposterDestroy(); +                return; +            } + +            if (this.textSourceLast && this.textSourceLast.equals(textSource)) { +                return; +            } + +            this.pendingLookup = true; + +            if (!await this.searchTerms(textSource)) { +                await this.searchKanji(textSource); +            } +        } catch (e) { +            if (window.yomichan_orphaned) { +                if (textSource && this.options.scanning.modifier !== 'none') { +                    this.popup.showOrphaned(textSource.getRect(), this.options); +                } +            } else { +                this.onError(e);              } -        }).catch(error => { -            this.handleError(error, textSource); -        }).then(() => { +        } finally {              docImposterDestroy();              this.pendingLookup = false; -        }); +        }      } -    searchTerms(textSource) { +    async searchTerms(textSource) {          textSource.setEndOffset(this.options.scanning.length); -        return bgTermsFind(textSource.text()).then(({definitions, length}) => { -            if (definitions.length === 0) { -                return false; -            } else { -                textSource.setEndOffset(length); - -                const sentence = docSentenceExtract(textSource, this.options.anki.sentenceExt); -                const url = window.location.href; -                this.popup.showTermDefs( -                    textSource.getRect(), -                    definitions, -                    this.options, -                    {sentence, url} -                ); - -                this.lastTextSource = textSource; -                if (this.options.scanning.selectText) { -                    textSource.select(); -                } +        const {definitions, length} = await apiTermsFind(textSource.text()); +        if (definitions.length === 0) { +            return false; +        } -                return true; -            } -        }); +        textSource.setEndOffset(length); + +        const sentence = docSentenceExtract(textSource, this.options.anki.sentenceExt); +        const url = window.location.href; +        this.popup.termsShow( +            textSource.getRect(), +            definitions, +            this.options, +            {sentence, url} +        ); + +        this.textSourceLast = textSource; +        if (this.options.scanning.selectText) { +            textSource.select(); +        } + +        return true;      } -    searchKanji(textSource) { +    async searchKanji(textSource) {          textSource.setEndOffset(1); -        return bgKanjiFind(textSource.text()).then(definitions => { -            if (definitions.length === 0) { -                return false; -            } else { -                const sentence = docSentenceExtract(textSource, this.options.anki.sentenceExt); -                const url = window.location.href; -                this.popup.showKanjiDefs( -                    textSource.getRect(), -                    definitions, -                    this.options, -                    {sentence, url} -                ); - -                this.lastTextSource = textSource; -                if (this.options.scanning.selectText) { -                    textSource.select(); -                } +        const definitions = await apiKanjiFind(textSource.text()); +        if (definitions.length === 0) { +            return false; +        } -                return true; -            } -        }); +        const sentence = docSentenceExtract(textSource, this.options.anki.sentenceExt); +        const url = window.location.href; +        this.popup.kanjiShow( +            textSource.getRect(), +            definitions, +            this.options, +            {sentence, url} +        ); + +        this.textSourceLast = textSource; +        if (this.options.scanning.selectText) { +            textSource.select(); +        } + +        return true;      }      searchClear() {          docImposterDestroy();          this.popup.hide(); -        if (this.options.scanning.selectText && this.lastTextSource) { -            this.lastTextSource.deselect(); +        if (this.options.scanning.selectText && this.textSourceLast) { +            this.textSourceLast.deselect();          } -        this.lastTextSource = null; +        this.textSourceLast = null;      } +} -    handleError(error, textSource) { -        if (window.orphaned) { -            if (textSource && this.options.scanning.modifier !== 'none') { -                this.popup.showOrphaned(textSource.getRect(), this.options); -            } -        } else { -            window.alert(`Error: ${error}`); -        } -    } -}; +window.yomichan_frontend = new Frontend(); +window.yomichan_frontend.prepare(); diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js index cd7e846a..03958832 100644 --- a/ext/fg/js/popup.js +++ b/ext/fg/js/popup.js @@ -1,5 +1,5 @@  /* - * Copyright (C) 2016  Alex Yatskov <alex@foosoft.net> + * 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 @@ -20,10 +20,10 @@  class Popup {      constructor() {          this.container = document.createElement('iframe'); -        this.container.id = 'yomichan-popup'; +        this.container.id = 'yomichan-float';          this.container.addEventListener('mousedown', e => e.stopPropagation());          this.container.addEventListener('scroll', e => e.stopPropagation()); -        this.container.setAttribute('src', chrome.extension.getURL('/fg/frame.html')); +        this.container.setAttribute('src', chrome.extension.getURL('/fg/float.html'));          this.container.style.width = '0px';          this.container.style.height = '0px';          this.injected = null; @@ -40,51 +40,56 @@ class Popup {          return this.injected;      } -    show(elementRect, options) { -        return this.inject().then(() => { -            const containerStyle = window.getComputedStyle(this.container); -            const containerHeight = parseInt(containerStyle.height); -            const containerWidth = parseInt(containerStyle.width); - -            const limitX = document.body.clientWidth; -            const limitY = window.innerHeight; - -            let x = elementRect.left; -            let width = Math.max(containerWidth, options.general.popupWidth); -            const overflowX = Math.max(x + width - limitX, 0); -            if (overflowX > 0) { -                if (x >= overflowX) { -                    x -= overflowX; -                } else { -                    width = limitX; -                    x = 0; -                } -            } +    async show(elementRect, options) { +        await this.inject(); + +        const containerStyle = window.getComputedStyle(this.container); +        const containerHeight = parseInt(containerStyle.height); +        const containerWidth = parseInt(containerStyle.width); + +        const limitX = document.body.clientWidth; +        const limitY = window.innerHeight; -            let y = 0; -            let height = Math.max(containerHeight, options.general.popupHeight); -            const yBelow = elementRect.bottom + options.general.popupOffset; -            const yAbove = elementRect.top - options.general.popupOffset; -            const overflowBelow = Math.max(yBelow + height - limitY, 0); -            const overflowAbove = Math.max(height - yAbove, 0); -            if (overflowBelow > 0 || overflowAbove > 0) { -                if (overflowBelow < overflowAbove) { -                    height = Math.max(height - overflowBelow, 0); -                    y = yBelow; -                } else { -                    height = Math.max(height - overflowAbove, 0); -                    y = Math.max(yAbove - height, 0); -                } +        let x = elementRect.left; +        let width = Math.max(containerWidth, options.general.popupWidth); +        const overflowX = Math.max(x + width - limitX, 0); +        if (overflowX > 0) { +            if (x >= overflowX) { +                x -= overflowX;              } else { +                width = limitX; +                x = 0; +            } +        } + +        let y = 0; +        let height = Math.max(containerHeight, options.general.popupHeight); +        const yBelow = elementRect.bottom + options.general.popupOffset; +        const yAbove = elementRect.top - options.general.popupOffset; +        const overflowBelow = Math.max(yBelow + height - limitY, 0); +        const overflowAbove = Math.max(height - yAbove, 0); +        if (overflowBelow > 0 || overflowAbove > 0) { +            if (overflowBelow < overflowAbove) { +                height = Math.max(height - overflowBelow, 0);                  y = yBelow; +            } else { +                height = Math.max(height - overflowAbove, 0); +                y = Math.max(yAbove - height, 0);              } +        } else { +            y = yBelow; +        } + +        this.container.style.left = `${x}px`; +        this.container.style.top = `${y}px`; +        this.container.style.width = `${width}px`; +        this.container.style.height = `${height}px`; +        this.container.style.visibility = 'visible'; +    } -            this.container.style.left = `${x}px`; -            this.container.style.top = `${y}px`; -            this.container.style.width = `${width}px`; -            this.container.style.height = `${height}px`; -            this.container.style.visibility = 'visible'; -        }); +    async showOrphaned(elementRect, options) { +        await this.show(elementRect, options); +        this.invokeApi('orphaned');      }      hide() { @@ -95,22 +100,14 @@ class Popup {          return this.injected && this.container.style.visibility !== 'hidden';      } -    showTermDefs(elementRect, definitions, options, context) { -        this.show(elementRect, options).then(() => { -            this.invokeApi('showTermDefs', {definitions, options, context}); -        }); -    } - -    showKanjiDefs(elementRect, definitions, options, context) { -        this.show(elementRect, options).then(() => { -            this.invokeApi('showKanjiDefs', {definitions, options, context}); -        }); +    async termsShow(elementRect, definitions, options, context) { +        await this.show(elementRect, options); +        this.invokeApi('termsShow', {definitions, options, context});      } -    showOrphaned(elementRect, options) { -        this.show(elementRect, options).then(() => { -            this.invokeApi('showOrphaned'); -        }); +    async kanjiShow(elementRect, definitions, options, context) { +        await this.show(elementRect, options); +        this.invokeApi('kanjiShow', {definitions, options, context});      }      invokeApi(action, params={}) { diff --git a/ext/fg/js/source-element.js b/ext/fg/js/source-element.js deleted file mode 100644 index a8101382..00000000 --- a/ext/fg/js/source-element.js +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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 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.element === this.element && other.content === this.content; -    } -} diff --git a/ext/fg/js/source-range.js b/ext/fg/js/source.js index fa73b0a4..3b6ecb2a 100644 --- a/ext/fg/js/source-range.js +++ b/ext/fg/js/source.js @@ -1,5 +1,5 @@  /* - * Copyright (C) 2016  Alex Yatskov <alex@foosoft.net> + * 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 @@ -17,6 +17,10 @@   */ +/* + * TextSourceRange + */ +  class TextSourceRange {      constructor(range, content='') {          this.range = range; @@ -174,3 +178,67 @@ class TextSourceRange {          return state.remainder > 0;      }  } + + +/* + * TextSourceElement + */ + +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.element === this.element && other.content === this.content; +    } +} diff --git a/ext/fg/js/util.js b/ext/fg/js/util.js index c6270ce6..5eff4018 100644 --- a/ext/fg/js/util.js +++ b/ext/fg/js/util.js @@ -1,5 +1,5 @@  /* - * Copyright (C) 2016  Alex Yatskov <alex@foosoft.net> + * 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 @@ -17,11 +17,13 @@   */ -/* - * Background - */ +function utilAsync(func) { +    return function(...args) { +        func.apply(this, args); +    }; +} -function bgInvoke(action, params) { +function utilInvoke(action, params={}) {      return new Promise((resolve, reject) => {          try {              chrome.runtime.sendMessage({action, params}, ({result, error}) => { @@ -32,185 +34,8 @@ function bgInvoke(action, params) {                  }              });          } catch (e) { -            window.orphaned = true; +            window.yomichan_orphaned = true;              reject(e.message);          }      });  } - -function bgOptionsGet() { -    return bgInvoke('optionsGet', {}); -} - -function bgTermsFind(text) { -    return bgInvoke('termsFind', {text}); -} - -function bgKanjiFind(text) { -    return bgInvoke('kanjiFind', {text}); -} - -function bgTemplateRender(template, data) { -    return bgInvoke('templateRender', {data, template}); -} - -function bgDefinitionsAddable(definitions, modes) { -    return bgInvoke('definitionsAddable', {definitions, modes}).catch(() => null); -} - -function bgDefinitionAdd(definition, mode) { -    return bgInvoke('definitionAdd', {definition, mode}); -} - - -/* - * Document - */ - -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}; -} - -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.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; -} - -function docImposterDestroy() { -    for (const element of document.getElementsByClassName('yomichan-imposter')) { -        element.parentNode.removeChild(element); -    } -} - -function docRangeFromPoint(point) { -    const element = document.elementFromPoint(point.x, point.y); -    if (element !== null) { -        if (element.nodeName === 'IMG' || element.nodeName === 'BUTTON') { -            return new TextSourceElement(element); -        } else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') { -            docImposterCreate(element); -        } -    } - -    if (!document.caretRangeFromPoint) { -        document.caretRangeFromPoint = (x, y) => { -            const position = document.caretPositionFromPoint(x,y); -            if (position === null) { -                return null; -            } - -            const range = document.createRange(); -            range.setStart(position.offsetNode, position.offset); -            range.setEnd(position.offsetNode, position.offset); -            return range; -        }; -    } - -    const range = document.caretRangeFromPoint(point.x, point.y); -    if (range !== null) { -        return new TextSourceRange(range); -    } - -    return null; -} - -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 -    }; -} |