diff options
-rw-r--r-- | .eslintrc | 5 | ||||
-rw-r--r-- | manifest.json | 5 | ||||
-rw-r--r-- | src/background/index.js | 46 | ||||
-rw-r--r-- | src/background/key-queue.js | 38 | ||||
-rw-r--r-- | src/background/keys.js | 28 | ||||
-rw-r--r-- | src/background/tabs.js | 23 | ||||
-rw-r--r-- | src/content/index.js | 54 | ||||
-rw-r--r-- | src/content/scrolls.js | 27 | ||||
-rw-r--r-- | src/index.js | 3 | ||||
-rw-r--r-- | src/module.js | 18 | ||||
-rw-r--r-- | src/shared/actions.js | 26 | ||||
-rw-r--r-- | webpack.config.js | 3 |
12 files changed, 251 insertions, 25 deletions
@@ -2,7 +2,8 @@ "env": { "es6": true, "node" : true, - "browser" : true + "browser" : true, + "webextensions": true }, "parser": "babel-eslint", "parserOptions": { @@ -20,7 +21,7 @@ "newline-before-return": "off", "multiline-ternary": "off", "max-statements": ["error", 15], - "no-console": ["error", { allow: ["warn", "error"] }], + "no-console": "off", "no-magic-numbers": ["error", { "ignore": [0, 1, 2] }], "no-param-reassign": "off", "no-ternary": "off", diff --git a/manifest.json b/manifest.json index 61c815a..2621c5e 100644 --- a/manifest.json +++ b/manifest.json @@ -8,5 +8,8 @@ "matches": [ "http://*/*", "https://*/*" ], "js": [ "build/index.js" ] } - ] + ], + "background": { + "scripts": ["build/background.js"] + } } diff --git a/src/background/index.js b/src/background/index.js new file mode 100644 index 0000000..604ea92 --- /dev/null +++ b/src/background/index.js @@ -0,0 +1,46 @@ +import * as actions from '../shared/actions'; +import * as tabs from './tabs'; +import KeyQueue from './key-queue'; + +const queue = new KeyQueue(); + +const keyDownHandle = (request) => { + return queue.push({ + code: request.code, + shift: request.shift, + ctrl: request.ctrl, + alt: request.alt, + meta: request.meta + }) +} + +const doBackgroundAction = (sender, action) => { + switch(action[0]) { + case actions.TABS_PREV: + tabs.selectPrevTab(sender.tab.index, actions[1] || 1); + break; + case actions.TABS_NEXT: + tabs.selectNextTab(sender.tab.index, actions[1] || 1); + break; + } +} + +browser.runtime.onMessage.addListener((request, sender, sendResponse) => { + let action = null; + + switch (request.type) { + case 'event.keydown': + action = keyDownHandle(request); + break; + } + + if (action == null) { + return; + } + + if (actions.isBackgroundAction(action[0])) { + doBackgroundAction(sender, action); + } else if (actions.isContentAction(action[0])) { + sendResponse(action); + } +}); diff --git a/src/background/key-queue.js b/src/background/key-queue.js new file mode 100644 index 0000000..e21399e --- /dev/null +++ b/src/background/key-queue.js @@ -0,0 +1,38 @@ +import * as keys from './keys'; +import * as actions from '../shared/actions'; + +const DEFAULT_KEYMAP = [ + { keys: [{ code: KeyboardEvent.DOM_VK_K }], action: [ actions.SCROLL_UP, 1 ]}, + { keys: [{ code: KeyboardEvent.DOM_VK_J }], action: [ actions.SCROLL_DOWN, 1 ]}, + { keys: [{ code: KeyboardEvent.DOM_VK_G }, { code: KeyboardEvent.DOM_VK_G }], action: [ actions.SCROLL_TOP ]}, + { keys: [{ code: KeyboardEvent.DOM_VK_G, shift: true }], action: [ actions.SCROLL_BOTTOM ]}, + { keys: [{ code: KeyboardEvent.DOM_VK_H }], action: [ actions.TABS_PREV, 1 ]}, + { keys: [{ code: KeyboardEvent.DOM_VK_L }], action: [ actions.TABS_NEXT, 1 ]}, +] + +export default class KeyQueue { + + constructor(keymap) { + this.data = []; + this.keymap = keymap; + } + + push(key) { + this.data.push(key); + let filtered = DEFAULT_KEYMAP.filter((map) => { + return keys.hasPrefix(map.keys, this.data) + }); + + if (filtered.length == 0) { + this.data = []; + return; + } else if (filtered.length == 1) { + let map = filtered[0]; + if (map.keys.length == this.data.length) { + this.data = []; + return map.action; + } + } + return null; + } +} diff --git a/src/background/keys.js b/src/background/keys.js new file mode 100644 index 0000000..2fd00a2 --- /dev/null +++ b/src/background/keys.js @@ -0,0 +1,28 @@ +const identifyKey = (key1, key2) => { + return (key1.code === key2.code) && + ((key1.shift || false) === (key2.shift || false)) && + ((key1.ctrl || false) === (key2.ctrl || false)) && + ((key1.alt || false) === (key2.alt || false)) && + ((key1.meta || false) === (key2.meta || false)); +}; + +const hasPrefix = (keys, prefix) => { + if (keys.length < prefix.length) { + return false; + } + for (let i = 0; i < prefix.length; ++i) { + if (!identifyKey(keys[i], prefix[i])) { + return false; + } + } + return true; +} + +const identifyKeys = (keys1, keys2) => { + if (keys1.length !== keys2.length) { + return false; + } + return hasPrefix(keys1, keys2); +} + +export { identifyKey, identifyKeys, hasPrefix }; diff --git a/src/background/tabs.js b/src/background/tabs.js new file mode 100644 index 0000000..000bd7d --- /dev/null +++ b/src/background/tabs.js @@ -0,0 +1,23 @@ +const selectPrevTab = (current, count) => { + chrome.tabs.query({ currentWindow: true }, (tabs) => { + if (tabs.length < 2) { + return; + } + let select = (current - count) % tabs.length + let id = tabs[select].id; + chrome.tabs.update(id, { active: true }) + }); +}; + +const selectNextTab = (current, count) => { + chrome.tabs.query({ currentWindow: true }, (tabs) => { + if (tabs.length < 2) { + return; + } + let select = (current + count + tabs.length) % tabs.length + let id = tabs[select].id; + chrome.tabs.update(id, { active: true }) + }); +}; + +export { selectNextTab, selectPrevTab }; diff --git a/src/content/index.js b/src/content/index.js new file mode 100644 index 0000000..03efc5e --- /dev/null +++ b/src/content/index.js @@ -0,0 +1,54 @@ +import * as scrolls from './scrolls'; +import * as actions from '../shared/actions'; + +const invokeEvent = (action) => { + if (typeof action === 'undefined' || action === null) { + return; + } + + switch (action[0]) { + case actions.SCROLL_UP: + scrolls.scrollUp(window, action[1] || 1); + break; + case actions.SCROLL_DOWN: + scrolls.scrollDown(window, action[1] || 1); + break; + case actions.SCROLL_TOP: + scrolls.scrollTop(window, action[1]); + break; + case actions.SCROLL_BOTTOM: + scrolls.scrollBottom(window, action[1]); + break; + } +} + +const isModifier = (code) => { + return code === KeyboardEvent.DOM_VK_SHIFT || + code === KeyboardEvent.DOM_VK_ALT || + code === KeyboardEvent.DOM_VK_CONTROL || + code === KeyboardEvent.DOM_VK_META; +} + +window.addEventListener("keydown", (e) => { + if (e.target instanceof HTMLInputElement) { + return; + } + if (isModifier(e.keyCode)) { + return; + } + + let request = { + type: 'event.keydown', + code: e.keyCode, + shift: e.shiftKey, + alt: e.altKey, + meta: e.metaKey, + ctrl: e.ctrlKey, + } + + browser.runtime.sendMessage(request) + .then(invokeEvent, + (err) => { + console.log(`Vim Vixen: ${err}`); + }); +}); diff --git a/src/content/scrolls.js b/src/content/scrolls.js new file mode 100644 index 0000000..2a233c2 --- /dev/null +++ b/src/content/scrolls.js @@ -0,0 +1,27 @@ +const SCROLL_DELTA = 48; + +const scrollUp = (page, count) => { + let x = page.scrollX; + let y = page.scrollY - SCROLL_DELTA * count; + page.scrollTo(x, y); +}; + +const scrollDown = (page, count) => { + let x = page.scrollX; + let y = page.scrollY + SCROLL_DELTA * count; + page.scrollTo(x, y); +}; + +const scrollTop = (page) => { + let x = page.scrollX; + let y = 0; + page.scrollTo(x, y); +}; + +const scrollBottom = (page) => { + let x = page.scrollX; + let y = page.scrollMaxY; + page.scrollTo(x, y); +}; + +export { scrollUp, scrollDown, scrollTop, scrollBottom } diff --git a/src/index.js b/src/index.js deleted file mode 100644 index 8c9b627..0000000 --- a/src/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import * as Module from './module'; - -Module.initialize() diff --git a/src/module.js b/src/module.js deleted file mode 100644 index e967a62..0000000 --- a/src/module.js +++ /dev/null @@ -1,18 +0,0 @@ -const initialize = () => { - let p = document.createElement("p"); - p.textContent = "Hello Vim Vixen"; - p.style.position = 'fixed'; - p.style.right = '0'; - p.style.bottom = '0'; - p.style.padding = '0rem .5rem'; - p.style.margin = '0'; - p.style.backgroundColor = 'lightgray'; - p.style.border = 'gray'; - p.style.boxShadow = '0 0 2px gray inset'; - p.style.borderRadius = '3px'; - p.style.fontFamily = 'monospace'; - - document.body.append(p) -} - -export { initialize }; diff --git a/src/shared/actions.js b/src/shared/actions.js new file mode 100644 index 0000000..3e3cbd0 --- /dev/null +++ b/src/shared/actions.js @@ -0,0 +1,26 @@ +export const TABS_PREV = 'tabs.prev'; +export const TABS_NEXT = 'tabs.next'; +export const SCROLL_UP = 'scroll.up'; +export const SCROLL_DOWN = 'scroll.down'; +export const SCROLL_TOP = 'scroll.top'; +export const SCROLL_BOTTOM = 'scroll.bottom'; + +const BACKGROUND_ACTION_SET = new Set([ + TABS_PREV, + TABS_NEXT +]); + +const CONTENT_ACTION_SET = new Set([ + SCROLL_UP, + SCROLL_DOWN, + SCROLL_TOP, + SCROLL_BOTTOM +]); + +export const isBackgroundAction = (action) => { + return BACKGROUND_ACTION_SET.has(action); +}; + +export const isContentAction = (action) => { + return CONTENT_ACTION_SET.has(action); +}; diff --git a/webpack.config.js b/webpack.config.js index 4b3ed7a..bb1568d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -5,7 +5,8 @@ const dist = path.resolve(__dirname, 'build'); module.exports = { entry: { - index: path.join(src, 'index.js') + index: path.join(src, 'content'), + background: path.join(src, 'background') }, output: { |