aboutsummaryrefslogtreecommitdiff
path: root/ext/fg/js
diff options
context:
space:
mode:
authorAlex Yatskov <alex@foosoft.net>2019-10-05 10:14:07 -0700
committerAlex Yatskov <alex@foosoft.net>2019-10-05 10:14:07 -0700
commit7f5f3951f057b84864f7f37825804f9049ddab10 (patch)
tree1a724bbd4d8cf2b053f3e2b99b46ba41008508b8 /ext/fg/js
parentf4b6527ed6ed1f0f4f5a63b94766b20f3b90e6ec (diff)
parent46ab36180f486a19332c538401b4db12ffe1bda1 (diff)
Merge branch 'master' into testing
Diffstat (limited to 'ext/fg/js')
-rw-r--r--ext/fg/js/float.js107
-rw-r--r--ext/fg/js/frontend.js80
-rw-r--r--ext/fg/js/popup-proxy-host.js6
-rw-r--r--ext/fg/js/popup-proxy.js4
-rw-r--r--ext/fg/js/popup.js41
-rw-r--r--ext/fg/js/source.js190
6 files changed, 240 insertions, 188 deletions
diff --git a/ext/fg/js/float.js b/ext/fg/js/float.js
index fd7986b8..88842eef 100644
--- a/ext/fg/js/float.js
+++ b/ext/fg/js/float.js
@@ -19,7 +19,7 @@
class DisplayFloat extends Display {
constructor() {
- super($('#spinner'), $('#definitions'));
+ super(document.querySelector('#spinner'), document.querySelector('#definitions'));
this.autoPlayAudioTimer = null;
this.styleNode = null;
@@ -30,7 +30,7 @@ class DisplayFloat extends Display {
this.dependencies = Object.assign({}, this.dependencies, {docRangeFromPoint, docSentenceExtract});
- $(window).on('message', utilAsync(this.onMessage.bind(this)));
+ window.addEventListener('message', (e) => this.onMessage(e), false);
}
onError(error) {
@@ -42,8 +42,16 @@ class DisplayFloat extends Display {
}
onOrphaned() {
- $('#definitions').hide();
- $('#error-orphaned').show();
+ const definitions = document.querySelector('#definitions');
+ const errorOrphaned = document.querySelector('#error-orphaned');
+
+ if (definitions !== null) {
+ definitions.style.setProperty('display', 'none', 'important');
+ }
+
+ if (errorOrphaned !== null) {
+ errorOrphaned.style.setProperty('display', 'block', 'important');
+ }
}
onSearchClear() {
@@ -55,60 +63,25 @@ class DisplayFloat extends Display {
}
onMessage(e) {
- const handlers = {
- termsShow: ({definitions, options, context}) => {
- this.termsShow(definitions, options, context);
- },
-
- kanjiShow: ({definitions, options, context}) => {
- this.kanjiShow(definitions, options, context);
- },
-
- clearAutoPlayTimer: () => {
- this.clearAutoPlayTimer();
- },
-
- orphaned: () => {
- this.onOrphaned();
- },
-
- setOptions: (options) => {
- const css = options.general.customPopupCss;
- if (css) {
- this.setStyle(css);
- }
- },
-
- popupNestedInitialize: ({id, depth, parentFrameId, url}) => {
- this.optionsContext.depth = depth;
- this.optionsContext.url = url;
- popupNestedInitialize(id, depth, parentFrameId, url);
- }
- };
-
- const {action, params} = e.originalEvent.data;
- const handler = handlers[action];
- if (handler) {
- handler(params);
+ const {action, params} = e.data;
+ const handlers = DisplayFloat.messageHandlers;
+ if (handlers.hasOwnProperty(action)) {
+ const handler = handlers[action];
+ handler(this, params);
}
}
onKeyDown(e) {
- const handlers = {
- 67: /* c */ () => {
- if (e.ctrlKey && !window.getSelection().toString()) {
- this.onSelectionCopy();
- return true;
- }
+ const key = Display.getKeyFromEvent(e);
+ const handlers = DisplayFloat.onKeyDownHandlers;
+ if (handlers.hasOwnProperty(key)) {
+ const handler = handlers[key];
+ if (handler(this, e)) {
+ e.preventDefault();
+ return;
}
- };
-
- const handler = handlers[e.keyCode];
- if (handler && handler()) {
- e.preventDefault();
- } else {
- super.onKeyDown(e);
}
+ super.onKeyDown(e);
}
autoPlayAudio() {
@@ -123,6 +96,18 @@ class DisplayFloat extends Display {
}
}
+ initialize(options, popupInfo, url) {
+ const css = options.general.customPopupCss;
+ if (css) {
+ this.setStyle(css);
+ }
+
+ const {id, depth, parentFrameId} = popupInfo;
+ this.optionsContext.depth = depth;
+ this.optionsContext.url = url;
+ popupNestedInitialize(id, depth, parentFrameId, url);
+ }
+
setStyle(css) {
const parent = document.head;
@@ -138,4 +123,22 @@ class DisplayFloat extends Display {
}
}
+DisplayFloat.onKeyDownHandlers = {
+ 'C': (self, e) => {
+ if (e.ctrlKey && !window.getSelection().toString()) {
+ self.onSelectionCopy();
+ return true;
+ }
+ return false;
+ }
+};
+
+DisplayFloat.messageHandlers = {
+ termsShow: (self, {definitions, options, context}) => self.termsShow(definitions, options, context),
+ kanjiShow: (self, {definitions, options, context}) => self.kanjiShow(definitions, options, context),
+ clearAutoPlayTimer: (self) => self.clearAutoPlayTimer(),
+ orphaned: (self) => self.onOrphaned(),
+ initialize: (self, {options, popupInfo, url}) => self.initialize(options, popupInfo, url)
+};
+
window.yomichan_display = new DisplayFloat();
diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js
index 167e82c0..58dc0e4a 100644
--- a/ext/fg/js/frontend.js
+++ b/ext/fg/js/frontend.js
@@ -55,7 +55,7 @@ class Frontend {
try {
this.options = await apiOptionsGet(this.getOptionsContext());
- window.addEventListener('message', this.onFrameMessage.bind(this));
+ window.addEventListener('message', this.onWindowMessage.bind(this));
window.addEventListener('mousedown', this.onMouseDown.bind(this));
window.addEventListener('mousemove', this.onMouseMove.bind(this));
window.addEventListener('mouseover', this.onMouseOver.bind(this));
@@ -71,7 +71,7 @@ class Frontend {
window.addEventListener('contextmenu', this.onContextMenu.bind(this));
}
- chrome.runtime.onMessage.addListener(this.onBgMessage.bind(this));
+ chrome.runtime.onMessage.addListener(this.onRuntimeMessage.bind(this));
} catch (e) {
this.onError(e);
}
@@ -128,32 +128,24 @@ class Frontend {
}
this.popupTimerClear();
- this.searchClear();
+ this.searchClear(true);
}
onMouseOut(e) {
this.popupTimerClear();
}
- onFrameMessage(e) {
- const handlers = {
- popupClose: () => {
- this.searchClear();
- },
-
- selectionCopy: () => {
- document.execCommand('copy');
- }
- };
-
- const handler = handlers[e.data];
- if (handler) {
- handler();
+ onWindowMessage(e) {
+ const action = e.data;
+ const handlers = Frontend.windowMessageHandlers;
+ if (handlers.hasOwnProperty(action)) {
+ const handler = handlers[action];
+ handler(this);
}
}
onResize() {
- this.searchClear();
+ this.searchClear(true);
}
onClick(e) {
@@ -240,20 +232,11 @@ class Frontend {
this.contextMenuChecking = false;
}
- onBgMessage({action, params}, sender, callback) {
- const handlers = {
- optionsUpdate: () => {
- this.updateOptions();
- },
-
- popupSetVisible: ({visible}) => {
- this.popup.setVisible(visible);
- }
- };
-
- const handler = handlers[action];
- if (handler) {
- handler(params);
+ onRuntimeMessage({action, params}, sender, callback) {
+ const handlers = Frontend.runtimeMessageHandlers;
+ if (handlers.hasOwnProperty(action)) {
+ const handler = handlers[action];
+ handler(this, params);
callback();
}
}
@@ -265,7 +248,7 @@ class Frontend {
async updateOptions() {
this.options = await apiOptionsGet(this.getOptionsContext());
if (!this.options.enable) {
- this.searchClear();
+ this.searchClear(false);
}
}
@@ -320,7 +303,7 @@ class Frontend {
textSource.cleanup();
}
if (hideResults && this.options.scanning.autoHideResults) {
- this.searchClear();
+ this.searchClear(true);
}
this.pendingLookup = false;
@@ -333,7 +316,7 @@ class Frontend {
const searchText = textSource.text();
if (searchText.length === 0) {
- return;
+ return false;
}
const {definitions, length} = await apiTermsFind(searchText, this.getOptionsContext());
@@ -366,7 +349,7 @@ class Frontend {
const searchText = textSource.text();
if (searchText.length === 0) {
- return;
+ return false;
}
const definitions = await apiKanjiFind(searchText, this.getOptionsContext());
@@ -392,8 +375,8 @@ class Frontend {
return true;
}
- searchClear() {
- this.popup.hide();
+ searchClear(changeFocus) {
+ this.popup.hide(changeFocus);
this.popup.clearAutoPlayTimer();
if (this.options.scanning.selectText && this.textSourceLast) {
@@ -529,4 +512,25 @@ class Frontend {
}
}
+Frontend.windowMessageHandlers = {
+ popupClose: (self) => {
+ self.searchClear(true);
+ },
+
+ selectionCopy: () => {
+ document.execCommand('copy');
+ }
+};
+
+Frontend.runtimeMessageHandlers = {
+ optionsUpdate: (self) => {
+ self.updateOptions();
+ },
+
+ popupSetVisible: (self, {visible}) => {
+ self.popup.setVisible(visible);
+ }
+};
+
+
window.yomichan_frontend = Frontend.create();
diff --git a/ext/fg/js/popup-proxy-host.js b/ext/fg/js/popup-proxy-host.js
index 396f7556..cb9741be 100644
--- a/ext/fg/js/popup-proxy-host.js
+++ b/ext/fg/js/popup-proxy-host.js
@@ -40,7 +40,7 @@ class PopupProxyHost {
createNestedPopup: ({parentId}) => this.createNestedPopup(parentId),
show: ({id, elementRect, options}) => this.show(id, elementRect, options),
showOrphaned: ({id, elementRect, options}) => this.show(id, elementRect, options),
- hide: ({id}) => this.hide(id),
+ hide: ({id, changeFocus}) => this.hide(id, changeFocus),
setVisible: ({id, visible}) => this.setVisible(id, visible),
containsPoint: ({id, x, y}) => this.containsPoint(id, x, y),
termsShow: ({id, elementRect, writingMode, definitions, options, context}) => this.termsShow(id, elementRect, writingMode, definitions, options, context),
@@ -98,9 +98,9 @@ class PopupProxyHost {
return await popup.showOrphaned(elementRect, options);
}
- async hide(id) {
+ async hide(id, changeFocus) {
const popup = this.getPopup(id);
- return popup.hide();
+ return popup.hide(changeFocus);
}
async setVisible(id, visible) {
diff --git a/ext/fg/js/popup-proxy.js b/ext/fg/js/popup-proxy.js
index 235e1730..072cebc9 100644
--- a/ext/fg/js/popup-proxy.js
+++ b/ext/fg/js/popup-proxy.js
@@ -58,11 +58,11 @@ class PopupProxy {
return await this.invokeHostApi('showOrphaned', {id, elementRect, options});
}
- async hide() {
+ async hide(changeFocus) {
if (this.id === null) {
return;
}
- return await this.invokeHostApi('hide', {id: this.id});
+ return await this.invokeHostApi('hide', {id: this.id, changeFocus});
}
async setVisible(visible) {
diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js
index 08965084..9dff6f28 100644
--- a/ext/fg/js/popup.js
+++ b/ext/fg/js/popup.js
@@ -56,17 +56,19 @@ class Popup {
return new Promise((resolve) => {
const parentFrameId = (typeof this.frameId === 'number' ? this.frameId : null);
this.container.addEventListener('load', () => {
- this.invokeApi('popupNestedInitialize', {
- id: this.id,
- depth: this.depth,
- parentFrameId,
+ this.invokeApi('initialize', {
+ options: {
+ general: {
+ customPopupCss: options.general.customPopupCss
+ }
+ },
+ popupInfo: {
+ id: this.id,
+ depth: this.depth,
+ parentFrameId
+ },
url: this.url
});
- this.invokeApi('setOptions', {
- general: {
- customPopupCss: options.general.customPopupCss
- }
- });
resolve();
});
this.observeFullscreen();
@@ -105,7 +107,7 @@ class Popup {
container.style.height = `${height}px`;
container.style.visibility = 'visible';
- this.hideChildren();
+ this.hideChildren(true);
}
static getPositionForHorizontalText(elementRect, width, height, maxWidth, maxHeight, optionsGeneral) {
@@ -206,16 +208,21 @@ class Popup {
this.invokeApi('orphaned');
}
- hide() {
- this.hideChildren();
+ hide(changeFocus) {
+ if (this.isContainerHidden()) {
+ changeFocus = false;
+ }
+ this.hideChildren(changeFocus);
this.hideContainer();
- this.focusParent();
+ if (changeFocus) {
+ this.focusParent();
+ }
}
- hideChildren() {
- // recursively hides all children
- if (this.child && !this.child.isContainerHidden()) {
- this.child.hide();
+ hideChildren(changeFocus) {
+ // Recursively hides all children.
+ if (this.child !== null && !this.child.isContainerHidden()) {
+ this.child.hide(changeFocus);
}
}
diff --git a/ext/fg/js/source.js b/ext/fg/js/source.js
index 4642de50..ee4f58e2 100644
--- a/ext/fg/js/source.js
+++ b/ext/fg/js/source.js
@@ -83,120 +83,142 @@ class TextSourceRange {
}
static shouldEnter(node) {
- if (node.nodeType !== 1) {
- return false;
- }
-
- const skip = ['RT', 'SCRIPT', 'STYLE'];
- if (skip.includes(node.nodeName.toUpperCase())) {
- return false;
+ switch (node.nodeName.toUpperCase()) {
+ case 'RT':
+ case 'SCRIPT':
+ case 'STYLE':
+ return false;
}
const style = window.getComputedStyle(node);
- const hidden =
+ return !(
style.visibility === 'hidden' ||
style.display === 'none' ||
- parseFloat(style.fontSize) === 0;
+ parseFloat(style.fontSize) === 0);
+ }
- return !hidden;
+ static getRubyElement(node) {
+ node = TextSourceRange.getParentElement(node);
+ if (node !== null && node.nodeName.toUpperCase() === "RT") {
+ node = node.parentNode;
+ return (node !== null && node.nodeName.toUpperCase() === "RUBY") ? node : null;
+ }
+ return null;
}
static seekForward(node, offset, length) {
const state = {node, offset, remainder: length, content: ''};
- if (!TextSourceRange.seekForwardHelper(node, state)) {
- return state;
+ const TEXT_NODE = Node.TEXT_NODE;
+ const ELEMENT_NODE = Node.ELEMENT_NODE;
+ let resetOffset = false;
+
+ const ruby = TextSourceRange.getRubyElement(node);
+ if (ruby !== null) {
+ node = ruby;
+ resetOffset = true;
}
- 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;
+ while (node !== null) {
+ let visitChildren = true;
+ const nodeType = node.nodeType;
+
+ if (nodeType === TEXT_NODE) {
+ state.node = node;
+ if (TextSourceRange.seekForwardTextNode(state, resetOffset)) {
+ break;
}
+ resetOffset = true;
+ } else if (nodeType === ELEMENT_NODE) {
+ visitChildren = TextSourceRange.shouldEnter(node);
}
+
+ node = TextSourceRange.getNextNode(node, visitChildren);
}
return state;
}
- static seekForwardHelper(node, state) {
- if (node.nodeType === 3 && node.parentElement && TextSourceRange.shouldEnter(node.parentElement)) {
- const offset = state.node === node ? state.offset : 0;
-
- let consumed = 0;
- let stripped = 0;
- while (state.remainder - consumed > 0) {
- const currentChar = node.nodeValue[offset + consumed + stripped];
- if (!currentChar) {
- break;
- } else if (currentChar.match(IGNORE_TEXT_PATTERN)) {
- stripped++;
- } else {
- consumed++;
- state.content += currentChar;
- }
- }
-
- state.node = node;
- state.offset = offset + consumed + stripped;
- state.remainder -= consumed;
- } else if (TextSourceRange.shouldEnter(node)) {
- for (let i = 0; i < node.childNodes.length; ++i) {
- if (!TextSourceRange.seekForwardHelper(node.childNodes[i], state)) {
+ static seekForwardTextNode(state, resetOffset) {
+ const nodeValue = state.node.nodeValue;
+ const nodeValueLength = nodeValue.length;
+ let content = state.content;
+ let offset = resetOffset ? 0 : state.offset;
+ let remainder = state.remainder;
+ let result = false;
+
+ for (; offset < nodeValueLength; ++offset) {
+ const c = nodeValue[offset];
+ if (!IGNORE_TEXT_PATTERN.test(c)) {
+ content += c;
+ if (--remainder <= 0) {
+ result = true;
+ ++offset;
break;
}
}
}
- return state.remainder > 0;
+ state.offset = offset;
+ state.content = content;
+ state.remainder = remainder;
+ return result;
}
static seekBackward(node, offset, length) {
const state = {node, offset, remainder: length, content: ''};
- if (!TextSourceRange.seekBackwardHelper(node, state)) {
- return state;
+ const TEXT_NODE = Node.TEXT_NODE;
+ const ELEMENT_NODE = Node.ELEMENT_NODE;
+ let resetOffset = false;
+
+ const ruby = TextSourceRange.getRubyElement(node);
+ if (ruby !== null) {
+ node = ruby;
+ resetOffset = true;
}
- 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;
+ while (node !== null) {
+ let visitChildren = true;
+ const nodeType = node.nodeType;
+
+ if (nodeType === TEXT_NODE) {
+ state.node = node;
+ if (TextSourceRange.seekBackwardTextNode(state, resetOffset)) {
+ break;
}
+ resetOffset = true;
+ } else if (nodeType === ELEMENT_NODE) {
+ visitChildren = TextSourceRange.shouldEnter(node);
}
+
+ node = TextSourceRange.getPreviousNode(node, visitChildren);
}
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;
-
- let consumed = 0;
- let stripped = 0;
- while (state.remainder - consumed > 0) {
- const currentChar = node.nodeValue[offset - 1 - consumed - stripped]; // negative indices are undefined in JS
- if (!currentChar) {
- break;
- } else if (currentChar.match(IGNORE_TEXT_PATTERN)) {
- stripped++;
- } else {
- consumed++;
- state.content = currentChar + state.content;
- }
- }
-
- state.node = node;
- state.offset = offset - consumed - stripped;
- 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)) {
+ static seekBackwardTextNode(state, resetOffset) {
+ const nodeValue = state.node.nodeValue;
+ let content = state.content;
+ let offset = resetOffset ? nodeValue.length : state.offset;
+ let remainder = state.remainder;
+ let result = false;
+
+ for (; offset > 0; --offset) {
+ const c = nodeValue[offset - 1];
+ if (!IGNORE_TEXT_PATTERN.test(c)) {
+ content = c + content;
+ if (--remainder <= 0) {
+ result = true;
+ --offset;
break;
}
}
}
- return state.remainder > 0;
+ state.offset = offset;
+ state.content = content;
+ state.remainder = remainder;
+ return result;
}
static getParentElement(node) {
@@ -219,22 +241,38 @@ class TextSourceRange {
static getNodesInRange(range) {
const end = range.endContainer;
const nodes = [];
- for (let node = range.startContainer; node !== null; node = TextSourceRange.getNextNode(node)) {
+ for (let node = range.startContainer; node !== null; node = TextSourceRange.getNextNode(node, true)) {
nodes.push(node);
if (node === end) { break; }
}
return nodes;
}
- static getNextNode(node) {
- let next = node.firstChild;
+ static getNextNode(node, visitChildren) {
+ let next = visitChildren ? node.firstChild : null;
if (next === null) {
while (true) {
next = node.nextSibling;
if (next !== null) { break; }
next = node.parentNode;
- if (node === null) { break; }
+ if (next === null) { break; }
+
+ node = next;
+ }
+ }
+ return next;
+ }
+
+ static getPreviousNode(node, visitChildren) {
+ let next = visitChildren ? node.lastChild : null;
+ if (next === null) {
+ while (true) {
+ next = node.previousSibling;
+ if (next !== null) { break; }
+
+ next = node.parentNode;
+ if (next === null) { break; }
node = next;
}