diff options
author | Alex Yatskov <alex@foosoft.net> | 2017-08-17 19:39:32 -0700 |
---|---|---|
committer | Alex Yatskov <alex@foosoft.net> | 2017-08-17 19:39:32 -0700 |
commit | 39fbabfe626e7ae2c2e0c1dd45bda33dd5dab66c (patch) | |
tree | 334bc2282389c910ed9f7d724458b6d8b650311a /ext/fg/js/document.js | |
parent | 65c100fe16c4829971055da384da90b1a1cddcd5 (diff) | |
parent | 7586572fbaab7de698ec13f8712cc95e24ab6273 (diff) |
Merge branch 'master' into firefox-amo
Diffstat (limited to 'ext/fg/js/document.js')
-rw-r--r-- | ext/fg/js/document.js | 163 |
1 files changed, 163 insertions, 0 deletions
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 + }; +} |