From a628610cbd9ea235987cef399021b0685c50f0e4 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Thu, 19 Sep 2019 22:03:26 -0400 Subject: [PATCH 1/7] Use KeyboardEvent.key for onKeyDown handlers --- ext/fg/js/float.js | 31 +++--- ext/mixed/js/display.js | 241 ++++++++++++++++++++++------------------ 2 files changed, 149 insertions(+), 123 deletions(-) diff --git a/ext/fg/js/float.js b/ext/fg/js/float.js index 2e952efb..8a05aa70 100644 --- a/ext/fg/js/float.js +++ b/ext/fg/js/float.js @@ -102,21 +102,16 @@ class DisplayFloat extends Display { } 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() { @@ -146,4 +141,14 @@ class DisplayFloat extends Display { } } +DisplayFloat.onKeyDownHandlers = { + 'C': (self, e) => { + if (e.ctrlKey && !window.getSelection().toString()) { + self.onSelectionCopy(); + return true; + } + return false; + } +}; + window.yomichan_display = new DisplayFloat(); diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 46016192..eb40a5df 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -150,117 +150,13 @@ class Display { } onKeyDown(e) { - const noteTryAdd = mode => { - const button = this.adderButtonFind(this.index, mode); - if (button !== null && !button.classList.contains('disabled')) { - this.noteAdd(this.definitions[this.index], mode); + const key = Display.getKeyFromEvent(e); + const handlers = Display.onKeyDownHandlers; + if (handlers.hasOwnProperty(key)) { + const handler = handlers[key]; + if (handler(this, e)) { + e.preventDefault(); } - }; - - const noteTryView = mode => { - const button = this.viewerButtonFind(this.index); - if (button !== null && !button.classList.contains('disabled')) { - apiNoteView(button.dataset.noteId); - } - }; - - const handlers = { - 27: /* escape */ () => { - this.onSearchClear(); - return true; - }, - - 33: /* page up */ () => { - if (e.altKey) { - this.entryScrollIntoView(this.index - 3, null, true); - return true; - } - }, - - 34: /* page down */ () => { - if (e.altKey) { - this.entryScrollIntoView(this.index + 3, null, true); - return true; - } - }, - - 35: /* end */ () => { - if (e.altKey) { - this.entryScrollIntoView(this.definitions.length - 1, null, true); - return true; - } - }, - - 36: /* home */ () => { - if (e.altKey) { - this.entryScrollIntoView(0, null, true); - return true; - } - }, - - 38: /* up */ () => { - if (e.altKey) { - this.entryScrollIntoView(this.index - 1, null, true); - return true; - } - }, - - 40: /* down */ () => { - if (e.altKey) { - this.entryScrollIntoView(this.index + 1, null, true); - return true; - } - }, - - 66: /* b */ () => { - if (e.altKey) { - this.sourceTermView(); - return true; - } - }, - - 69: /* e */ () => { - if (e.altKey) { - noteTryAdd('term-kanji'); - return true; - } - }, - - 75: /* k */ () => { - if (e.altKey) { - noteTryAdd('kanji'); - return true; - } - }, - - 82: /* r */ () => { - if (e.altKey) { - noteTryAdd('term-kana'); - return true; - } - }, - - 80: /* p */ () => { - if (e.altKey) { - const entry = this.getEntry(this.index); - if (entry !== null && entry.dataset.type === 'term') { - this.audioPlay(this.definitions[this.index], this.firstExpressionIndex); - } - - return true; - } - }, - - 86: /* v */ () => { - if (e.altKey) { - noteTryView(); - } - } - }; - - const handler = handlers[e.keyCode]; - if (handler && handler()) { - e.preventDefault(); } } @@ -459,6 +355,20 @@ class Display { } } + noteTryAdd(mode) { + const button = this.adderButtonFind(this.index, mode); + if (button !== null && !button.classList.contains('disabled')) { + this.noteAdd(this.definitions[this.index], mode); + } + } + + noteTryView() { + const button = this.viewerButtonFind(this.index); + if (button !== null && !button.classList.contains('disabled')) { + apiNoteView(button.dataset.noteId); + } + } + async noteAdd(definition, mode) { try { this.setSpinnerVisible(true); @@ -634,4 +544,115 @@ class Display { const documentRect = document.documentElement.getBoundingClientRect(); return elementRect.top - documentRect.top; } + + static getKeyFromEvent(event) { + const key = event.key; + return key.length === 1 ? key.toUpperCase() : key; + } } + +Display.onKeyDownHandlers = { + 'Escape': (self) => { + self.onSearchClear(); + return true; + }, + + 'PageUp': (self, e) => { + if (e.altKey) { + self.entryScrollIntoView(self.index - 3, null, true); + return true; + } + return false; + }, + + 'PageDown': (self, e) => { + if (e.altKey) { + self.entryScrollIntoView(self.index + 3, null, true); + return true; + } + return false; + }, + + 'End': (self, e) => { + if (e.altKey) { + self.entryScrollIntoView(self.definitions.length - 1, null, true); + return true; + } + return false; + }, + + 'Home': (self, e) => { + if (e.altKey) { + self.entryScrollIntoView(0, null, true); + return true; + } + return false; + }, + + 'ArrowUp': (self, e) => { + if (e.altKey) { + self.entryScrollIntoView(self.index - 1, null, true); + return true; + } + return false; + }, + + 'ArrowDown': (self, e) => { + if (e.altKey) { + self.entryScrollIntoView(self.index + 1, null, true); + return true; + } + return false; + }, + + 'B': (self, e) => { + if (e.altKey) { + self.sourceTermView(); + return true; + } + return false; + }, + + 'E': (self, e) => { + if (e.altKey) { + self.noteTryAdd('term-kanji'); + return true; + } + return false; + }, + + 'K': (self, e) => { + if (e.altKey) { + self.noteTryAdd('kanji'); + return true; + } + return false; + }, + + 'R': (self, e) => { + if (e.altKey) { + self.noteTryAdd('term-kana'); + return true; + } + return false; + }, + + 'P': (self, e) => { + if (e.altKey) { + const entry = self.getEntry(self.index); + if (entry !== null && entry.dataset.type === 'term') { + self.audioPlay(self.definitions[self.index], self.firstExpressionIndex); + } + return true; + } + return false; + }, + + 'V': (self, e) => { + if (e.altKey) { + self.noteTryView(); + return true; + } + return false; + } +}; From 7d15213916355a806ba687803ca94209e29f142c Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 2 Oct 2019 20:31:42 -0400 Subject: [PATCH 2/7] Use static object for frontend message handlers --- ext/fg/js/frontend.js | 64 +++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js index d5bb00c0..deec1ffd 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); } @@ -135,20 +135,12 @@ class Frontend { this.popupTimerClear(); } - onFrameMessage(e) { - const handlers = { - popupClose: () => { - this.searchClear(true); - }, - - 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); } } @@ -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(); } } @@ -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(); From bf382652a7be949d0bbfe176bb2876af2d0f7fa2 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 2 Oct 2019 20:46:35 -0400 Subject: [PATCH 3/7] Use static object for float message handlers --- ext/fg/js/float.js | 69 +++++++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/ext/fg/js/float.js b/ext/fg/js/float.js index 8a05aa70..8f561fec 100644 --- a/ext/fg/js/float.js +++ b/ext/fg/js/float.js @@ -63,41 +63,11 @@ 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.data; - const handler = handlers[action]; - if (handler) { - handler(params); + const handlers = DisplayFloat.messageHandlers; + if (handlers.hasOwnProperty(action)) { + const handler = handlers[action]; + handler(this, params); } } @@ -151,4 +121,35 @@ DisplayFloat.onKeyDownHandlers = { } }; +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(); + }, + + setOptions: (self, options) => { + const css = options.general.customPopupCss; + if (css) { + self.setStyle(css); + } + }, + + popupNestedInitialize: (self, {id, depth, parentFrameId, url}) => { + self.optionsContext.depth = depth; + self.optionsContext.url = url; + popupNestedInitialize(id, depth, parentFrameId, url); + } +}; + window.yomichan_display = new DisplayFloat(); From 10458c63e71303f27888c3dfca7b3f59d5a0702b Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 2 Oct 2019 20:46:48 -0400 Subject: [PATCH 4/7] Use static object for backend message handlers --- ext/bg/js/backend.js | 84 ++++++++++++-------------------------------- 1 file changed, 22 insertions(+), 62 deletions(-) diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 4068b760..3c5ad885 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -69,68 +69,13 @@ class Backend { } onMessage({action, params}, sender, callback) { - const forward = (promise, callback) => { - return promise.then(result => { - callback({result}); - }).catch(error => { - callback({error: error.toString ? error.toString() : error}); - }); - }; - - const handlers = { - optionsGet: ({optionsContext, callback}) => { - forward(apiOptionsGet(optionsContext), callback); - }, - - kanjiFind: ({text, optionsContext, callback}) => { - forward(apiKanjiFind(text, optionsContext), callback); - }, - - termsFind: ({text, optionsContext, callback}) => { - forward(apiTermsFind(text, optionsContext), callback); - }, - - definitionAdd: ({definition, mode, context, optionsContext, callback}) => { - forward(apiDefinitionAdd(definition, mode, context, optionsContext), callback); - }, - - definitionsAddable: ({definitions, modes, optionsContext, callback}) => { - forward(apiDefinitionsAddable(definitions, modes, optionsContext), callback); - }, - - noteView: ({noteId}) => { - forward(apiNoteView(noteId), callback); - }, - - templateRender: ({template, data, dynamic, callback}) => { - forward(apiTemplateRender(template, data, dynamic), callback); - }, - - commandExec: ({command, callback}) => { - forward(apiCommandExec(command), callback); - }, - - audioGetUrl: ({definition, source, callback}) => { - forward(apiAudioGetUrl(definition, source), callback); - }, - - screenshotGet: ({options}) => { - forward(apiScreenshotGet(options, sender), callback); - }, - - forward: ({action, params}) => { - forward(apiForward(action, params, sender), callback); - }, - - frameInformationGet: () => { - forward(apiFrameInformationGet(sender), callback); - } - }; - - const handler = handlers[action]; - if (handler) { - params.callback = callback; - handler(params); + const handlers = Backend.messageHandlers; + if (handlers.hasOwnProperty(action)) { + const handler = handlers[action]; + const promise = handler(params, sender); + promise + .then(result => callback({result})) + .catch(error => callback({error: typeof error.toString === 'function' ? error.toString() : error})); } return true; @@ -227,5 +172,20 @@ class Backend { } } +Backend.messageHandlers = { + optionsGet: ({optionsContext}) => apiOptionsGet(optionsContext), + kanjiFind: ({text, optionsContext}) => apiKanjiFind(text, optionsContext), + termsFind: ({text, optionsContext}) => apiTermsFind(text, optionsContext), + definitionAdd: ({definition, mode, context, optionsContext}) => apiDefinitionAdd(definition, mode, context, optionsContext), + definitionsAddable: ({definitions, modes, optionsContext}) => apiDefinitionsAddable(definitions, modes, optionsContext), + noteView: ({noteId}) => apiNoteView(noteId), + templateRender: ({template, data, dynamic}) => apiTemplateRender(template, data, dynamic), + commandExec: ({command}) => apiCommandExec(command), + audioGetUrl: ({definition, source}) => apiAudioGetUrl(definition, source), + screenshotGet: ({options}, sender) => apiScreenshotGet(options, sender), + forward: ({action, params}, sender) => apiForward(action, params, sender), + frameInformationGet: (params, sender) => apiFrameInformationGet(sender), +}; + window.yomichan_backend = new Backend(); window.yomichan_backend.prepare(); From 0d6177398d0a738430a555ce307ba0a61c15bf9a Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 2 Oct 2019 20:47:03 -0400 Subject: [PATCH 5/7] Use static object for api command handlers --- ext/bg/js/api.js | 52 ++++++++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js index 474fe604..222e7ffe 100644 --- a/ext/bg/js/api.js +++ b/ext/bg/js/api.js @@ -126,35 +126,35 @@ async function apiTemplateRender(template, data, dynamic) { } async function apiCommandExec(command) { - const handlers = { - search: () => { - chrome.tabs.create({url: chrome.extension.getURL('/bg/search.html')}); - }, - - help: () => { - chrome.tabs.create({url: 'https://foosoft.net/projects/yomichan/'}); - }, - - options: () => { - chrome.runtime.openOptionsPage(); - }, - - toggle: async () => { - const optionsContext = { - depth: 0, - url: window.location.href - }; - const options = await apiOptionsGet(optionsContext); - options.general.enable = !options.general.enable; - await apiOptionsSave('popup'); - } - }; - - const handler = handlers[command]; - if (handler) { + const handlers = apiCommandExec.handlers; + if (handlers.hasOwnProperty(command)) { + const handler = handlers[command]; handler(); } } +apiCommandExec.handlers = { + search: () => { + chrome.tabs.create({url: chrome.extension.getURL('/bg/search.html')}); + }, + + help: () => { + chrome.tabs.create({url: 'https://foosoft.net/projects/yomichan/'}); + }, + + options: () => { + chrome.runtime.openOptionsPage(); + }, + + toggle: async () => { + const optionsContext = { + depth: 0, + url: window.location.href + }; + const options = await apiOptionsGet(optionsContext); + options.general.enable = !options.general.enable; + await apiOptionsSave('popup'); + } +}; async function apiAudioGetUrl(definition, source) { return audioBuildUrl(definition, source); From 7380ada1f12c382d423133c2e9142f36a6634067 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 2 Oct 2019 20:57:48 -0400 Subject: [PATCH 6/7] Simplify onWheel handler --- ext/mixed/js/display.js | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index eb40a5df..dc64dbea 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -161,20 +161,12 @@ class Display { } onWheel(e) { - const handler = () => { - if (e.altKey) { - if (e.deltaY < 0) { // scroll up - this.entryScrollIntoView(this.index - 1, null, true); - return true; - } else if (e.deltaY > 0) { // scroll down - this.entryScrollIntoView(this.index + 1, null, true); - return true; - } + if (e.altKey) { + const delta = e.deltaY; + if (delta !== 0) { + this.entryScrollIntoView(this.index + (delta > 0 ? 1 : -1), null, true); + e.preventDefault(); } - }; - - if (handler()) { - e.preventDefault(); } } From fa7ee468c0b8b877bb6d94a031464525b1a87c6b Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 2 Oct 2019 21:11:06 -0400 Subject: [PATCH 7/7] Simplify float initialization --- ext/fg/js/float.js | 45 +++++++++++++++++---------------------------- ext/fg/js/popup.js | 20 +++++++++++--------- 2 files changed, 28 insertions(+), 37 deletions(-) diff --git a/ext/fg/js/float.js b/ext/fg/js/float.js index 8f561fec..88842eef 100644 --- a/ext/fg/js/float.js +++ b/ext/fg/js/float.js @@ -96,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; @@ -122,34 +134,11 @@ DisplayFloat.onKeyDownHandlers = { }; 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(); - }, - - setOptions: (self, options) => { - const css = options.general.customPopupCss; - if (css) { - self.setStyle(css); - } - }, - - popupNestedInitialize: (self, {id, depth, parentFrameId, url}) => { - self.optionsContext.depth = depth; - self.optionsContext.url = url; - popupNestedInitialize(id, depth, parentFrameId, url); - } + 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/popup.js b/ext/fg/js/popup.js index 64da9aef..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();