From 0ae39f1b67e269216ce3d45b870e448f6dbf21d7 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Wed, 13 Sep 2017 21:11:01 +0900 Subject: [PATCH 01/12] add simple store --- src/store/index.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/store/index.js diff --git a/src/store/index.js b/src/store/index.js new file mode 100644 index 0000000..bcb65d6 --- /dev/null +++ b/src/store/index.js @@ -0,0 +1,27 @@ +class Store { + constructor(reducer, catcher) { + this.reducer = reducer; + this.catcher = catcher; + this.state = this.reducer(undefined, {}); + } + + dispatch(action) { + if (action instanceof Promise) { + action.then((a) => { + this.state = this.reducer(this.state, a); + }).catch(this.catcher) + } else { + try { + this.state = this.reducer(this.state, action); + } catch (e) { + this.catcher(e); + } + } + } +} + +const empty = () => {}; + +export function createStore(reducer, catcher = empty) { + return new Store(reducer, catcher); +} From b6e5153c1f67a68434ad3db7d3726f129cc14aa4 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Wed, 13 Sep 2017 21:23:55 +0900 Subject: [PATCH 02/12] move background actions to operations --- src/actions/index.js | 8 -------- src/actions/operation.js | 27 +++++++++++++++++++++++++++ src/background/index.js | 9 +++++++++ src/background/keys.js | 19 ++++++++++--------- src/operations/index.js | 10 ++++++++++ src/reducers/background.js | 16 ---------------- 6 files changed, 56 insertions(+), 33 deletions(-) create mode 100644 src/actions/operation.js create mode 100644 src/operations/index.js diff --git a/src/actions/index.js b/src/actions/index.js index 63d5f6f..ae29238 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -7,14 +7,6 @@ export default { // Background commands BACKGROUND_REQUEST_COMPLETIONS: 'vimvixen.background.request.completions', - TABS_CLOSE: 'tabs.close', - TABS_REOPEN: 'tabs.reopen', - TABS_PREV: 'tabs.prev', - TABS_NEXT: 'tabs.next', - TABS_RELOAD: 'tabs.reload', - ZOOM_IN: 'zoom.in', - ZOOM_OUT: 'zoom.out', - ZOOM_NEUTRAL: 'zoom.neutral', // content commands CMD_OPEN: 'cmd.open', diff --git a/src/actions/operation.js b/src/actions/operation.js new file mode 100644 index 0000000..4106076 --- /dev/null +++ b/src/actions/operation.js @@ -0,0 +1,27 @@ +import operations from '../operations'; +import * as tabs from '../background/tabs'; +import * as zooms from '../background/zooms'; + +export function exec(operation, sender) { + switch (operation.type) { + case operations.TABS_CLOSE: + return tabs.closeTab(sender.tab.id); + case operations.TABS_REOPEN: + return tabs.reopenTab(); + case operations.TABS_PREV: + return tabs.selectPrevTab(sender.tab.index, operation.count); + case operations.TABS_NEXT: + return tabs.selectNextTab(sender.tab.index, operation.count); + case operations.TABS_RELOAD: + return tabs.reload(sender.tab, operation.cache); + case operations.ZOOM_IN: + return zooms.zoomIn(); + case operations.ZOOM_OUT: + return zooms.zoomOut(); + case operations.ZOOM_NEUTRAL: + return zooms.neutral(); + default: + return Promise.resolve(); + } +} + diff --git a/src/background/index.js b/src/background/index.js index e72cab0..ab99d39 100644 --- a/src/background/index.js +++ b/src/background/index.js @@ -1,9 +1,15 @@ import * as keys from './keys'; import * as inputActions from '../actions/input'; +import * as operationActions from '../actions/operation'; import backgroundReducers from '../reducers/background'; import commandReducer from '../reducers/command'; import inputReducers from '../reducers/input'; +import * as store from '../store' +const emptyReducer = (state, action) => state; +const emptyStore = store.createStore(emptyReducer, (e) => { + console.error('Vim-Vixen:', e); +}); let inputState = inputReducers(undefined, {}); const keyQueueChanged = (sender, prevState, state) => { @@ -21,6 +27,9 @@ const keyQueueChanged = (sender, prevState, state) => { return Promise.resolve(); } let action = keys.defaultKeymap[matched]; + emptyStore.dispatch(operationActions.exec(action, sender), (e) => { + console.error('Vim-Vixen:', e); + }); return handleMessage(inputActions.clearKeys(), sender).then(() => { return backgroundReducers(undefined, action, sender).then(() => { return browser.tabs.sendMessage(sender.tab.id, action); diff --git a/src/background/keys.js b/src/background/keys.js index 0ce53fa..72b333a 100644 --- a/src/background/keys.js +++ b/src/background/keys.js @@ -1,4 +1,5 @@ import actions from '../actions'; +import operations from '../operations'; const defaultKeymap = { ':': { type: actions.CMD_OPEN }, @@ -17,15 +18,15 @@ const defaultKeymap = { 'G': { type: actions.SCROLL_BOTTOM }, '0': { type: actions.SCROLL_LEFT }, '$': { type: actions.SCROLL_RIGHT }, - 'd': { type: actions.TABS_CLOSE }, - 'u': { type: actions.TABS_REOPEN }, - 'h': { type: actions.TABS_PREV, count: 1 }, - 'l': { type: actions.TABS_NEXT, count: 1 }, - 'r': { type: actions.TABS_RELOAD, cache: false }, - 'R': { type: actions.TABS_RELOAD, cache: true }, - 'zi': { type: actions.ZOOM_IN }, - 'zo': { type: actions.ZOOM_OUT }, - 'zz': { type: actions.ZOOM_NEUTRAL }, + 'd': { type: operations.TABS_CLOSE }, + 'u': { type: operations.TABS_REOPEN }, + 'h': { type: operations.TABS_PREV, count: 1 }, + 'l': { type: operations.TABS_NEXT, count: 1 }, + 'r': { type: operations.TABS_RELOAD, cache: false }, + 'R': { type: operations.TABS_RELOAD, cache: true }, + 'zi': { type: operations.ZOOM_IN }, + 'zo': { type: operations.ZOOM_OUT }, + 'zz': { type: operations.ZOOM_NEUTRAL }, 'f': { type: actions.FOLLOW_START, newTab: false }, 'F': { type: actions.FOLLOW_START, newTab: true }, 'H': { type: actions.HISTORY_PREV }, diff --git a/src/operations/index.js b/src/operations/index.js new file mode 100644 index 0000000..16c05f2 --- /dev/null +++ b/src/operations/index.js @@ -0,0 +1,10 @@ +export default { + TABS_CLOSE: 'tabs.close', + TABS_REOPEN: 'tabs.reopen', + TABS_PREV: 'tabs.prev', + TABS_NEXT: 'tabs.next', + TABS_RELOAD: 'tabs.reload', + ZOOM_IN: 'zoom.in', + ZOOM_OUT: 'zoom.out', + ZOOM_NEUTRAL: 'zoom.neutral', +} diff --git a/src/reducers/background.js b/src/reducers/background.js index d7d7860..1bba13b 100644 --- a/src/reducers/background.js +++ b/src/reducers/background.js @@ -31,22 +31,6 @@ export default function reducer(state, action = {}, sender) { switch (action.type) { case actions.BACKGROUND_REQUEST_COMPLETIONS: return doCompletion(action.command, action.keywords, sender.tab.id); - case actions.TABS_CLOSE: - return tabs.closeTab(sender.tab.id); - case actions.TABS_REOPEN: - return tabs.reopenTab(); - case actions.TABS_PREV: - return tabs.selectPrevTab(sender.tab.index, action.count); - case actions.TABS_NEXT: - return tabs.selectNextTab(sender.tab.index, action.count); - case actions.TABS_RELOAD: - return tabs.reload(sender.tab, action.cache); - case actions.ZOOM_IN: - return zooms.zoomIn(); - case actions.ZOOM_OUT: - return zooms.zoomOut(); - case actions.ZOOM_NEUTRAL: - return zooms.neutral(); default: return Promise.resolve(); } From a61f37ad61da8766bf74d5a8b26e53390cb5b6c9 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Wed, 13 Sep 2017 21:55:09 +0900 Subject: [PATCH 03/12] content operations --- src/actions/index.js | 9 --------- src/actions/operation.js | 5 ++++- src/background/index.js | 2 +- src/background/keys.js | 32 ++++++++++++++++---------------- src/content/index.js | 30 ++++++++++++++++++++++++++++++ src/operations/index.js | 11 +++++++++++ src/reducers/background.js | 1 - src/reducers/content.js | 30 ------------------------------ 8 files changed, 62 insertions(+), 58 deletions(-) diff --git a/src/actions/index.js b/src/actions/index.js index ae29238..754b7f0 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -12,15 +12,6 @@ export default { CMD_OPEN: 'cmd.open', CMD_TABS_OPEN: 'cmd.tabs.open', CMD_BUFFER: 'cmd.buffer', - SCROLL_LINES: 'scroll.lines', - SCROLL_PAGES: 'scroll.pages', - SCROLL_TOP: 'scroll.top', - SCROLL_BOTTOM: 'scroll.bottom', - SCROLL_LEFT: 'scroll.left', - SCROLL_RIGHT: 'scroll.right', - FOLLOW_START: 'follow.start', - HISTORY_PREV: 'history.prev', - HISTORY_NEXT: 'history.next', // User input INPUT_KEY_PRESS: 'input.key,press', diff --git a/src/actions/operation.js b/src/actions/operation.js index 4106076..352bfac 100644 --- a/src/actions/operation.js +++ b/src/actions/operation.js @@ -21,7 +21,10 @@ export function exec(operation, sender) { case operations.ZOOM_NEUTRAL: return zooms.neutral(); default: - return Promise.resolve(); + return browser.tabs.sendMessage(sender.tab.id, { + type: 'require.content.operation', + operation + }); } } diff --git a/src/background/index.js b/src/background/index.js index ab99d39..0c3adce 100644 --- a/src/background/index.js +++ b/src/background/index.js @@ -6,7 +6,7 @@ import commandReducer from '../reducers/command'; import inputReducers from '../reducers/input'; import * as store from '../store' -const emptyReducer = (state, action) => state; +const emptyReducer = (state) => state; const emptyStore = store.createStore(emptyReducer, (e) => { console.error('Vim-Vixen:', e); }); diff --git a/src/background/keys.js b/src/background/keys.js index 72b333a..0f73bf0 100644 --- a/src/background/keys.js +++ b/src/background/keys.js @@ -6,18 +6,18 @@ const defaultKeymap = { 'o': { type: actions.CMD_TABS_OPEN, alter: false }, 'O': { type: actions.CMD_TABS_OPEN, alter: true }, 'b': { type: actions.CMD_BUFFER }, - 'k': { type: actions.SCROLL_LINES, count: -1 }, - 'j': { type: actions.SCROLL_LINES, count: 1 }, - '': { type: actions.SCROLL_LINES, count: -1 }, - '': { type: actions.SCROLL_LINES, count: 1 }, - '': { type: actions.SCROLL_PAGES, count: -0.5 }, - '': { type: actions.SCROLL_PAGES, count: 0.5 }, - '': { type: actions.SCROLL_PAGES, count: -1 }, - '': { type: actions.SCROLL_PAGES, count: 1 }, - 'gg': { type: actions.SCROLL_TOP }, - 'G': { type: actions.SCROLL_BOTTOM }, - '0': { type: actions.SCROLL_LEFT }, - '$': { type: actions.SCROLL_RIGHT }, + 'k': { type: operations.SCROLL_LINES, count: -1 }, + 'j': { type: operations.SCROLL_LINES, count: 1 }, + '': { type: operations.SCROLL_LINES, count: -1 }, + '': { type: operations.SCROLL_LINES, count: 1 }, + '': { type: operations.SCROLL_PAGES, count: -0.5 }, + '': { type: operations.SCROLL_PAGES, count: 0.5 }, + '': { type: operations.SCROLL_PAGES, count: -1 }, + '': { type: operations.SCROLL_PAGES, count: 1 }, + 'gg': { type: operations.SCROLL_TOP }, + 'G': { type: operations.SCROLL_BOTTOM }, + '0': { type: operations.SCROLL_LEFT }, + '$': { type: operations.SCROLL_RIGHT }, 'd': { type: operations.TABS_CLOSE }, 'u': { type: operations.TABS_REOPEN }, 'h': { type: operations.TABS_PREV, count: 1 }, @@ -27,10 +27,10 @@ const defaultKeymap = { 'zi': { type: operations.ZOOM_IN }, 'zo': { type: operations.ZOOM_OUT }, 'zz': { type: operations.ZOOM_NEUTRAL }, - 'f': { type: actions.FOLLOW_START, newTab: false }, - 'F': { type: actions.FOLLOW_START, newTab: true }, - 'H': { type: actions.HISTORY_PREV }, - 'L': { type: actions.HISTORY_NEXT }, + 'f': { type: operations.FOLLOW_START, newTab: false }, + 'F': { type: operations.FOLLOW_START, newTab: true }, + 'H': { type: operations.HISTORY_PREV }, + 'L': { type: operations.HISTORY_NEXT }, } const asKeymapChars = (keys) => { diff --git a/src/content/index.js b/src/content/index.js index 12d079f..1bba656 100644 --- a/src/content/index.js +++ b/src/content/index.js @@ -1,7 +1,11 @@ import '../console/console-frame.scss'; import * as inputActions from '../actions/input'; import * as consoleFrames from '../console/frames'; +import * as scrolls from '../content/scrolls'; +import * as histories from '../content/histories'; import actions from '../actions'; +import Follow from '../content/follow'; +import operations from '../operations'; import contentReducer from '../reducers/content'; consoleFrames.initialize(window.document); @@ -22,11 +26,37 @@ window.addEventListener("keypress", (e) => { }); }); +const execOperation = (operation) => { + switch (operation.type) { + case operations.SCROLL_LINES: + return scrolls.scrollLines(window, operation.count); + case operations.SCROLL_PAGES: + return scrolls.scrollPages(window, operation.count); + case operations.SCROLL_TOP: + return scrolls.scrollTop(window); + case operations.SCROLL_BOTTOM: + return scrolls.scrollBottom(window); + case operations.SCROLL_LEFT: + return scrolls.scrollLeft(window); + case operations.SCROLL_RIGHT: + return scrolls.scrollRight(window); + case operations.FOLLOW_START: + return new Follow(window.document, operation.newTab); + case operations.HISTORY_PREV: + return histories.prev(window); + case operations.HISTORY_NEXT: + return histories.next(window); + } +} + browser.runtime.onMessage.addListener((action) => { switch (action.type) { case actions.CONSOLE_HIDE: window.focus(); return consoleFrames.blur(window.document); + case 'require.content.operation': + execOperation(action.operation); + return Promise.resolve(); case 'vimvixen.command.enter': return browser.runtime.sendMessage({ type: 'event.cmd.enter', diff --git a/src/operations/index.js b/src/operations/index.js index 16c05f2..6c2f05a 100644 --- a/src/operations/index.js +++ b/src/operations/index.js @@ -1,4 +1,15 @@ export default { + SCROLL_LINES: 'scroll.lines', + SCROLL_PAGES: 'scroll.pages', + SCROLL_TOP: 'scroll.top', + SCROLL_BOTTOM: 'scroll.bottom', + SCROLL_LEFT: 'scroll.left', + SCROLL_RIGHT: 'scroll.right', + FOLLOW_START: 'follow.start', + HISTORY_PREV: 'history.prev', + HISTORY_NEXT: 'history.next', + + // background TABS_CLOSE: 'tabs.close', TABS_REOPEN: 'tabs.reopen', TABS_PREV: 'tabs.prev', diff --git a/src/reducers/background.js b/src/reducers/background.js index 1bba13b..7a279c9 100644 --- a/src/reducers/background.js +++ b/src/reducers/background.js @@ -1,5 +1,4 @@ import * as tabs from '../background/tabs'; -import * as zooms from '../background/zooms'; import * as consoleActions from '../actions/console'; import actions from '../actions'; diff --git a/src/reducers/content.js b/src/reducers/content.js index bcf1160..ce59b18 100644 --- a/src/reducers/content.js +++ b/src/reducers/content.js @@ -1,7 +1,4 @@ import * as consoleFrames from '../console/frames'; -import * as histories from '../content/histories'; -import * as scrolls from '../content/scrolls'; -import Follow from '../content/follow'; import actions from '../actions'; export default function reducer(state, action = {}) { @@ -17,32 +14,5 @@ export default function reducer(state, action = {}) { } case actions.CMD_BUFFER: return consoleFrames.showCommand('buffer '); - case actions.SCROLL_LINES: - scrolls.scrollLines(window, action.count); - break; - case actions.SCROLL_PAGES: - scrolls.scrollPages(window, action.count); - break; - case actions.SCROLL_TOP: - scrolls.scrollTop(window); - break; - case actions.SCROLL_BOTTOM: - scrolls.scrollBottom(window); - break; - case actions.SCROLL_LEFT: - scrolls.scrollLeft(window); - break; - case actions.SCROLL_RIGHT: - scrolls.scrollRight(window); - break; - case actions.FOLLOW_START: - new Follow(window.document, action.newTab); - break; - case actions.HISTORY_PREV: - histories.prev(window); - break; - case actions.HISTORY_NEXT: - histories.next(window); - break; } } From 9dc02b2fd8344facedfd7949899124c4d0722f68 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Thu, 14 Sep 2017 20:13:24 +0900 Subject: [PATCH 04/12] remove unused message --- src/content/index.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/content/index.js b/src/content/index.js index 1bba656..527d107 100644 --- a/src/content/index.js +++ b/src/content/index.js @@ -57,14 +57,6 @@ browser.runtime.onMessage.addListener((action) => { case 'require.content.operation': execOperation(action.operation); return Promise.resolve(); - case 'vimvixen.command.enter': - return browser.runtime.sendMessage({ - type: 'event.cmd.enter', - text: action.value - }).catch((err) => { - console.error("Vim Vixen:", err); - return consoleFrames.showError(err.message); - }); default: return Promise.resolve(); } From c42ac8fac48f9d56b54af4818917082fda9af21e Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Thu, 14 Sep 2017 21:14:13 +0900 Subject: [PATCH 05/12] improve store and reducers --- src/background/index.js | 8 +++----- src/reducers/index.js | 7 +++++++ src/store/index.js | 21 +++++++++++++++++++-- 3 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 src/reducers/index.js diff --git a/src/background/index.js b/src/background/index.js index 0c3adce..e9757b9 100644 --- a/src/background/index.js +++ b/src/background/index.js @@ -2,12 +2,12 @@ import * as keys from './keys'; import * as inputActions from '../actions/input'; import * as operationActions from '../actions/operation'; import backgroundReducers from '../reducers/background'; +import reducers from '../reducers'; import commandReducer from '../reducers/command'; import inputReducers from '../reducers/input'; import * as store from '../store' -const emptyReducer = (state) => state; -const emptyStore = store.createStore(emptyReducer, (e) => { +const backgroundStore = store.createStore(reducers, (e) => { console.error('Vim-Vixen:', e); }); let inputState = inputReducers(undefined, {}); @@ -27,9 +27,7 @@ const keyQueueChanged = (sender, prevState, state) => { return Promise.resolve(); } let action = keys.defaultKeymap[matched]; - emptyStore.dispatch(operationActions.exec(action, sender), (e) => { - console.error('Vim-Vixen:', e); - }); + backgroundStore.dispatch(operationActions.exec(action, sender)); return handleMessage(inputActions.clearKeys(), sender).then(() => { return backgroundReducers(undefined, action, sender).then(() => { return browser.tabs.sendMessage(sender.tab.id, action); diff --git a/src/reducers/index.js b/src/reducers/index.js new file mode 100644 index 0000000..d49af7d --- /dev/null +++ b/src/reducers/index.js @@ -0,0 +1,7 @@ +const defaultState = { +}; + +export default function reducer(state = defaultState/*, action = {}*/) { + return Object.assign({}, state, { + }); +} diff --git a/src/store/index.js b/src/store/index.js index bcb65d6..841fd17 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -3,21 +3,38 @@ class Store { this.reducer = reducer; this.catcher = catcher; this.state = this.reducer(undefined, {}); + this.subscribers = []; } dispatch(action) { if (action instanceof Promise) { action.then((a) => { - this.state = this.reducer(this.state, a); + this.transitNext(a); }).catch(this.catcher) } else { try { - this.state = this.reducer(this.state, action); + this.transitNext(action); } catch (e) { this.catcher(e); } } } + + getState() { + return this.state; + } + + subscribe(callback) { + this.subscribers.push(callback); + } + + transitNext(action) { + let newState = this.reducer(this.state, action); + if (JSON.stringify(this.state) !== JSON.stringify(newState)) { + this.state = newState; + this.subscribers.forEach(f => f.call()) + } + } } const empty = () => {}; From 6127fdc285bc430b48259bd6e90b69623b4e76cc Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Thu, 14 Sep 2017 21:40:28 +0900 Subject: [PATCH 06/12] use input as store/reducer --- src/actions/operation.js | 12 +++++----- src/background/index.js | 45 ++++++++++++++++++++------------------ src/reducers/background.js | 8 +++---- src/reducers/command.js | 10 ++++----- src/reducers/index.js | 6 ++++- 5 files changed, 44 insertions(+), 37 deletions(-) diff --git a/src/actions/operation.js b/src/actions/operation.js index 352bfac..8ca0f4b 100644 --- a/src/actions/operation.js +++ b/src/actions/operation.js @@ -2,18 +2,18 @@ import operations from '../operations'; import * as tabs from '../background/tabs'; import * as zooms from '../background/zooms'; -export function exec(operation, sender) { +export function exec(operation, tab) { switch (operation.type) { case operations.TABS_CLOSE: - return tabs.closeTab(sender.tab.id); + return tabs.closeTab(tab.id); case operations.TABS_REOPEN: return tabs.reopenTab(); case operations.TABS_PREV: - return tabs.selectPrevTab(sender.tab.index, operation.count); + return tabs.selectPrevTab(tab.index, operation.count); case operations.TABS_NEXT: - return tabs.selectNextTab(sender.tab.index, operation.count); + return tabs.selectNextTab(tab.index, operation.count); case operations.TABS_RELOAD: - return tabs.reload(sender.tab, operation.cache); + return tabs.reload(tab, operation.cache); case operations.ZOOM_IN: return zooms.zoomIn(); case operations.ZOOM_OUT: @@ -21,7 +21,7 @@ export function exec(operation, sender) { case operations.ZOOM_NEUTRAL: return zooms.neutral(); default: - return browser.tabs.sendMessage(sender.tab.id, { + return browser.tabs.sendMessage(tab.id, { type: 'require.content.operation', operation }); diff --git a/src/background/index.js b/src/background/index.js index e9757b9..d56fab8 100644 --- a/src/background/index.js +++ b/src/background/index.js @@ -4,49 +4,52 @@ import * as operationActions from '../actions/operation'; import backgroundReducers from '../reducers/background'; import reducers from '../reducers'; import commandReducer from '../reducers/command'; -import inputReducers from '../reducers/input'; import * as store from '../store' const backgroundStore = store.createStore(reducers, (e) => { console.error('Vim-Vixen:', e); }); -let inputState = inputReducers(undefined, {}); +backgroundStore.subscribe(() => { + browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => { + if (tabs.length > 0) { + return keyQueueChanged(tabs[0], backgroundStore.getState()); + } + }); +}); -const keyQueueChanged = (sender, prevState, state) => { - if (state.keys.length === 0) { +const keyQueueChanged = (sendToTab, state) => { + if (state.input.keys.length === 0) { return Promise.resolve(); } - let prefix = keys.asKeymapChars(state.keys); + let prefix = keys.asKeymapChars(state.input.keys); let matched = Object.keys(keys.defaultKeymap).filter((keys) => { return keys.startsWith(prefix); }); if (matched.length == 0) { - return handleMessage(inputActions.clearKeys(), sender); + return handleMessage(inputActions.clearKeys(), sendToTab); } else if (matched.length > 1 || matched.length === 1 && prefix !== matched[0]) { return Promise.resolve(); } let action = keys.defaultKeymap[matched]; - backgroundStore.dispatch(operationActions.exec(action, sender)); - return handleMessage(inputActions.clearKeys(), sender).then(() => { - return backgroundReducers(undefined, action, sender).then(() => { - return browser.tabs.sendMessage(sender.tab.id, action); + backgroundStore.dispatch(operationActions.exec(action, sendToTab)); + return handleMessage(inputActions.clearKeys(), sendToTab).then(() => { + return backgroundReducers(undefined, action, sendToTab).then(() => { + return browser.tabs.sendMessage(sendToTab.id, action); }); }); }; -const handleMessage = (action, sender) => { - let nextInputState = inputReducers(inputState, action); - if (JSON.stringify(nextInputState) !== JSON.stringify(inputState)) { - let prevState = inputState; - inputState = nextInputState; - return keyQueueChanged(sender, prevState, inputState); - } - return backgroundReducers(undefined, action, sender).then(() => { - return commandReducer(undefined, action, sender).then(() => { - return browser.tabs.sendMessage(sender.tab.id, action); +const handleMessage = (action, sendToTab) => { + backgroundStore.dispatch(action); + + return backgroundReducers(undefined, action, sendToTab).then(() => { + return commandReducer(undefined, action, sendToTab).then(() => { + return browser.tabs.sendMessage(sendToTab.id, action); }); }); }; -browser.runtime.onMessage.addListener(handleMessage); +browser.runtime.onMessage.addListener((action, sender) => { + handleMessage(action, sender.tab); +}); diff --git a/src/reducers/background.js b/src/reducers/background.js index 7a279c9..ba934fd 100644 --- a/src/reducers/background.js +++ b/src/reducers/background.js @@ -2,7 +2,7 @@ import * as tabs from '../background/tabs'; import * as consoleActions from '../actions/console'; import actions from '../actions'; -const doCompletion = (command, keywords, sender) => { +const doCompletion = (command, keywords, tabId) => { if (command === 'buffer') { return tabs.getCompletions(keywords).then((tabs) => { let items = tabs.map((tab) => { @@ -18,18 +18,18 @@ const doCompletion = (command, keywords, sender) => { items: items }; return browser.tabs.sendMessage( - sender, + tabId, consoleActions.setCompletions([completions])); }); } return Promise.resolve(); }; -export default function reducer(state, action = {}, sender) { +export default function reducer(state, action = {}, sendToTab) { // TODO hide sender object switch (action.type) { case actions.BACKGROUND_REQUEST_COMPLETIONS: - return doCompletion(action.command, action.keywords, sender.tab.id); + return doCompletion(action.command, action.keywords, sendToTab.id); default: return Promise.resolve(); } diff --git a/src/reducers/command.js b/src/reducers/command.js index 7e03593..b645e29 100644 --- a/src/reducers/command.js +++ b/src/reducers/command.js @@ -1,23 +1,23 @@ import * as tabs from '../background/tabs'; import actions from '../actions'; -const cmdBuffer = (sender, arg) => { +const cmdBuffer = (tab, arg) => { if (isNaN(arg)) { - return tabs.selectByKeyword(sender.tab, arg); + return tabs.selectByKeyword(tab, arg); } else { let index = parseInt(arg, 10) - 1; return tabs.selectAt(index); } } -export default function reducer(state, action, sender) { +export default function reducer(state, action, sendToTab) { switch (action.type) { case actions.COMMAND_OPEN_URL: - return browser.tabs.update(sender.tab.id, { url: action.url }); + return browser.tabs.update(sendToTab.id, { url: action.url }); case actions.COMMAND_TABOPEN_URL: return browser.tabs.create({ url: action.url }); case actions.COMMAND_BUFFER: - return cmdBuffer(sender, action.keywords); + return cmdBuffer(sendToTab, action.keywords); default: return Promise.resolve(); } diff --git a/src/reducers/index.js b/src/reducers/index.js index d49af7d..6cc1a31 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -1,7 +1,11 @@ +import inputReducer from '../reducers/input'; + const defaultState = { + input: inputReducer(undefined, {}) }; -export default function reducer(state = defaultState/*, action = {}*/) { +export default function reducer(state = defaultState, action = {}) { return Object.assign({}, state, { + input: inputReducer(state.input, action) }); } From 83cb277ba2af2bc2f87ace1d97fa582a7043bcd5 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Thu, 14 Sep 2017 22:04:42 +0900 Subject: [PATCH 07/12] consome as store/reducers --- src/actions/background.js | 11 ----- src/actions/command.js | 81 ++++++++++++++++++++++++++------- src/actions/index.js | 12 ----- src/actions/operation.js | 12 +++++ src/background/index.js | 60 +++++++++++++++++------- src/background/keys.js | 9 ++-- src/console/console.js | 42 +++++++++-------- src/content/index.js | 19 ++++---- src/messages/index.js | 5 ++ src/operations/index.js | 5 ++ src/reducers/background.js | 36 --------------- src/reducers/command.js | 24 ---------- src/reducers/console.js | 1 - src/reducers/content.js | 18 -------- src/reducers/index.js | 7 ++- test/actions/background.test.js | 14 ------ test/actions/command.test.js | 51 --------------------- 17 files changed, 171 insertions(+), 236 deletions(-) delete mode 100644 src/actions/background.js create mode 100644 src/messages/index.js delete mode 100644 src/reducers/background.js delete mode 100644 src/reducers/command.js delete mode 100644 src/reducers/content.js delete mode 100644 test/actions/background.test.js delete mode 100644 test/actions/command.test.js diff --git a/src/actions/background.js b/src/actions/background.js deleted file mode 100644 index 40b901b..0000000 --- a/src/actions/background.js +++ /dev/null @@ -1,11 +0,0 @@ -import actions from '../actions'; - -export function requestCompletions(line) { - let command = line.split(' ', 1)[0]; - let keywords = line.replace(command + ' ', ''); - return { - type: actions.BACKGROUND_REQUEST_COMPLETIONS, - command, - keywords - }; -} diff --git a/src/actions/command.js b/src/actions/command.js index c983278..03f1e83 100644 --- a/src/actions/command.js +++ b/src/actions/command.js @@ -1,4 +1,5 @@ -import actions from '../actions'; +import * as tabs from '../background/tabs'; +import * as consoleActions from './console'; const normalizeUrl = (string) => { try { @@ -8,28 +9,76 @@ const normalizeUrl = (string) => { } } -export function exec(line) { - let name = line.split(' ')[0]; - let remaining = line.replace(name + ' ', ''); +const openCommand = (url) => { + return browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => { + if (tabs.length > 0) { + return browser.tabs.update(tabs[0].id, { url: url }); + } + }); +} +const tabopenCommand = (url) => { + return browser.tabs.create({ url: url }); +} + +const bufferCommand = (keywords) => { + return browser.tabs.query({ active: true, currentWindow: true }).then((tabss) => { + if (tabss.length > 0) { + if (isNaN(keywords)) { + return tabs.selectByKeyword(tabss[0], keywords); + } else { + let index = parseInt(keywords, 10) - 1; + return tabs.selectAt(index); + } + } + }); +} + +const doCommand = (name, remaining) => { switch (name) { case 'open': // TODO use search engined and pass keywords to them - return { - type: actions.COMMAND_OPEN_URL, - url: normalizeUrl(remaining) - }; + return openCommand(normalizeUrl(remaining)); case 'tabopen': - return { - type: actions.COMMAND_TABOPEN_URL, - url: normalizeUrl(remaining) - }; + return tabopenCommand(normalizeUrl(remaining)); case 'b': case 'buffer': - return { - type: actions.COMMAND_BUFFER, - keywords: remaining - }; + return bufferCommand(remaining); } throw new Error(name + ' command is not defined'); } + +const getCompletions = (command, keywords) => { + switch (command) { + case 'buffer': + return tabs.getCompletions(keywords).then((tabs) => { + let items = tabs.map((tab) => { + return { + caption: tab.title, + content: tab.title, + url: tab.url, + icon: tab.favIconUrl + } + }); + return [{ + name: "Buffers", + items: items + }]; + }); + } + return Promise.resolve([]); +}; + +export function exec(line) { + let name = line.split(' ')[0]; + let remaining = line.replace(name + ' ', ''); + return doCommand(name, remaining).then(() => { + return consoleActions.hide(); + }); +} + +export function complete(line) { + let command = line.split(' ', 1)[0]; + let keywords = line.replace(command + ' ', ''); + return getCompletions(command, keywords).then(consoleActions.setCompletions); +} diff --git a/src/actions/index.js b/src/actions/index.js index 754b7f0..977b3c2 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -5,19 +5,7 @@ export default { CONSOLE_SHOW_ERROR: 'vimvixen.console.show.error', CONSOLE_HIDE: 'vimvixen.console.hide', - // Background commands - BACKGROUND_REQUEST_COMPLETIONS: 'vimvixen.background.request.completions', - - // content commands - CMD_OPEN: 'cmd.open', - CMD_TABS_OPEN: 'cmd.tabs.open', - CMD_BUFFER: 'cmd.buffer', - // User input INPUT_KEY_PRESS: 'input.key,press', INPUT_CLEAR_KEYS: 'input.clear.keys', - - COMMAND_OPEN_URL: 'command.open.url', - COMMAND_TABOPEN_URL: 'command.tabopen.url', - COMMAND_BUFFER: 'command.buffer', }; diff --git a/src/actions/operation.js b/src/actions/operation.js index 8ca0f4b..2009818 100644 --- a/src/actions/operation.js +++ b/src/actions/operation.js @@ -1,4 +1,5 @@ import operations from '../operations'; +import * as consoleActions from './console'; import * as tabs from '../background/tabs'; import * as zooms from '../background/zooms'; @@ -20,6 +21,17 @@ export function exec(operation, tab) { return zooms.zoomOut(); case operations.ZOOM_NEUTRAL: return zooms.neutral(); + case operations.COMMAND_OPEN: + return consoleActions.showCommand(''); + case operations.COMMAND_TABS_OPEN: + if (operations.alter) { + // alter url + return consoleActions.showCommand('open ' + tab.url); + } else { + return consoleActions.showCommand('open '); + } + case operations.COMMAND_BUFFER: + return consoleActions.showCommand('buffer '); default: return browser.tabs.sendMessage(tab.id, { type: 'require.content.operation', diff --git a/src/background/index.js b/src/background/index.js index d56fab8..d2cfeb4 100644 --- a/src/background/index.js +++ b/src/background/index.js @@ -1,55 +1,81 @@ import * as keys from './keys'; import * as inputActions from '../actions/input'; import * as operationActions from '../actions/operation'; -import backgroundReducers from '../reducers/background'; +import * as commandActions from '../actions/command'; +import * as consoleActions from '../actions/console'; import reducers from '../reducers'; -import commandReducer from '../reducers/command'; +import messages from '../messages'; import * as store from '../store' +let prevInput = []; const backgroundStore = store.createStore(reducers, (e) => { console.error('Vim-Vixen:', e); }); backgroundStore.subscribe(() => { + let currentInput = backgroundStore.getState().input + if (JSON.stringify(prevInput) === JSON.stringify(currentInput)) { + return + } + prevInput = currentInput; + + if (currentInput.keys.length === 0) { + return; + } + browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => { if (tabs.length > 0) { return keyQueueChanged(tabs[0], backgroundStore.getState()); } }); }); +backgroundStore.subscribe(() => { + browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => { + if (tabs.length > 0) { + return browser.tabs.sendMessage(tabs[0].id, { + type: 'state.changed', + state: backgroundStore.getState() + }); + } + }); +}); const keyQueueChanged = (sendToTab, state) => { - if (state.input.keys.length === 0) { - return Promise.resolve(); - } - let prefix = keys.asKeymapChars(state.input.keys); let matched = Object.keys(keys.defaultKeymap).filter((keys) => { return keys.startsWith(prefix); }); if (matched.length == 0) { - return handleMessage(inputActions.clearKeys(), sendToTab); + backgroundStore.dispatch(inputActions.clearKeys()); + return Promise.resolve(); } else if (matched.length > 1 || matched.length === 1 && prefix !== matched[0]) { return Promise.resolve(); } let action = keys.defaultKeymap[matched]; backgroundStore.dispatch(operationActions.exec(action, sendToTab)); - return handleMessage(inputActions.clearKeys(), sendToTab).then(() => { - return backgroundReducers(undefined, action, sendToTab).then(() => { - return browser.tabs.sendMessage(sendToTab.id, action); - }); - }); + backgroundStore.dispatch(inputActions.clearKeys()); + return browser.tabs.sendMessage(sendToTab.id, action); }; const handleMessage = (action, sendToTab) => { backgroundStore.dispatch(action); - return backgroundReducers(undefined, action, sendToTab).then(() => { - return commandReducer(undefined, action, sendToTab).then(() => { - return browser.tabs.sendMessage(sendToTab.id, action); - }); - }); + return browser.tabs.sendMessage(sendToTab.id, action); }; browser.runtime.onMessage.addListener((action, sender) => { handleMessage(action, sender.tab); }); + +browser.runtime.onMessage.addListener((message) => { + switch (message.type) { + case messages.CONSOLE_BLURRED: + backgroundStore.dispatch(consoleActions.hide()); + break; + case messages.CONSOLE_ENTERED: + backgroundStore.dispatch(commandActions.exec(message.text)); + break; + case messages.CONSOLE_CHANGEED: + backgroundStore.dispatch(commandActions.complete(message.text)); + break; + } +}); diff --git a/src/background/keys.js b/src/background/keys.js index 0f73bf0..8d75aba 100644 --- a/src/background/keys.js +++ b/src/background/keys.js @@ -1,11 +1,10 @@ -import actions from '../actions'; import operations from '../operations'; const defaultKeymap = { - ':': { type: actions.CMD_OPEN }, - 'o': { type: actions.CMD_TABS_OPEN, alter: false }, - 'O': { type: actions.CMD_TABS_OPEN, alter: true }, - 'b': { type: actions.CMD_BUFFER }, + ':': { type: operations.COMMAND_OPEN }, + 'o': { type: operations.COMMAND_TABS_OPEN, alter: false }, + 'O': { type: operations.COMMAND_TABS_OPEN, alter: true }, + 'b': { type: operations.COMMAND_BUFFER }, 'k': { type: operations.SCROLL_LINES, count: -1 }, 'j': { type: operations.SCROLL_LINES, count: 1 }, '': { type: operations.SCROLL_LINES, count: -1 }, diff --git a/src/console/console.js b/src/console/console.js index 25ab36d..7e9bffa 100644 --- a/src/console/console.js +++ b/src/console/console.js @@ -1,18 +1,17 @@ import './console.scss'; -import * as backgroundActions from '../actions/background'; -import * as consoleActions from '../actions/console'; -import * as commandActions from '../actions/command'; import Completion from './completion'; -import consoleReducer from '../reducers/console'; +import messages from '../messages'; // TODO consider object-oriented var prevValue = ""; var completion = null; var completionOrigin = ""; -let state = consoleReducer(undefined, {}); +var prevState = {}; const handleBlur = () => { - return browser.runtime.sendMessage(consoleActions.hide()); + return browser.runtime.sendMessage({ + type: messages.CONSOLE_BLURRED, + }); }; const completeNext = () => { @@ -52,7 +51,10 @@ const handleKeydown = (e) => { case KeyboardEvent.DOM_VK_ESCAPE: return input.blur(); case KeyboardEvent.DOM_VK_RETURN: - return browser.runtime.sendMessage(commandActions.exec(e.target.value)); + return browser.runtime.sendMessage({ + type: messages.CONSOLE_ENTERED, + text: e.target.value + }); case KeyboardEvent.DOM_VK_TAB: if (e.shiftKey) { completePrev(); @@ -73,9 +75,10 @@ const handleKeyup = (e) => { return; } prevValue = e.target.value; - return browser.runtime.sendMessage( - backgroundActions.requestCompletions(e.target.value) - ); + return browser.runtime.sendMessage({ + type: messages.CONSOLE_CHANGEED, + text: e.target.value + }); }; window.addEventListener('load', () => { @@ -147,7 +150,7 @@ const updateCompletions = (completions) => { completionOrigin = input.value.split(' ')[0]; } -const update = (prevState, state) => { +const update = (state) => { let error = window.document.querySelector('#vimvixen-console-error'); let command = window.document.querySelector('#vimvixen-console-command'); let input = window.document.querySelector('#vimvixen-console-command-input'); @@ -156,25 +159,26 @@ const update = (prevState, state) => { error.textContent = state.errorText; command.style.display = state.commandShown ? 'block' : 'none'; - if (!prevState.commandShown && state.commandShown) { - // setup input on firstly shown + if (state.commandShown && !prevState.commandShown) { input.value = state.commandText; input.focus(); } - if (JSON.stringify(state.completions) !== JSON.stringify(prevState.completions)) { updateCompletions(state.completions); } + + prevState = state; } browser.runtime.onMessage.addListener((action) => { - let nextState = consoleReducer(state, action); - if (JSON.stringify(nextState) !== JSON.stringify(state)) { - update(state, nextState); - state = nextState; + if (action.type === 'state.changed') { + return update(action.state.console); } }); window.addEventListener('load', () => { - update({}, state); + let error = window.document.querySelector('#vimvixen-console-error'); + let command = window.document.querySelector('#vimvixen-console-command'); + error.style.display = 'none'; + command.style.display = 'none'; }); diff --git a/src/content/index.js b/src/content/index.js index 527d107..d81e83e 100644 --- a/src/content/index.js +++ b/src/content/index.js @@ -3,18 +3,11 @@ import * as inputActions from '../actions/input'; import * as consoleFrames from '../console/frames'; import * as scrolls from '../content/scrolls'; import * as histories from '../content/histories'; -import actions from '../actions'; import Follow from '../content/follow'; import operations from '../operations'; -import contentReducer from '../reducers/content'; consoleFrames.initialize(window.document); -browser.runtime.onMessage.addListener((action) => { - contentReducer(undefined, action); - return Promise.resolve(); -}); - window.addEventListener("keypress", (e) => { if (e.target instanceof HTMLInputElement) { return; @@ -49,11 +42,17 @@ const execOperation = (operation) => { } } +const update = (state) => { + if (!state.console.commandShown) { + window.focus(); + consoleFrames.blur(window.document); + } +} + browser.runtime.onMessage.addListener((action) => { switch (action.type) { - case actions.CONSOLE_HIDE: - window.focus(); - return consoleFrames.blur(window.document); + case 'state.changed': + return update(action.state); case 'require.content.operation': execOperation(action.operation); return Promise.resolve(); diff --git a/src/messages/index.js b/src/messages/index.js new file mode 100644 index 0000000..078ecaf --- /dev/null +++ b/src/messages/index.js @@ -0,0 +1,5 @@ +export default { + CONSOLE_BLURRED: 'console.blured', + CONSOLE_ENTERED: 'console.entered', + CONSOLE_CHANGEED: 'console.changed' +}; diff --git a/src/operations/index.js b/src/operations/index.js index 6c2f05a..05c7d78 100644 --- a/src/operations/index.js +++ b/src/operations/index.js @@ -1,4 +1,9 @@ export default { + // command + COMMAND_OPEN: 'cmd.open', + COMMAND_TABS_OPEN: 'cmd.tabs.open', + COMMAND_BUFFER: 'cmd.buffer', + SCROLL_LINES: 'scroll.lines', SCROLL_PAGES: 'scroll.pages', SCROLL_TOP: 'scroll.top', diff --git a/src/reducers/background.js b/src/reducers/background.js deleted file mode 100644 index ba934fd..0000000 --- a/src/reducers/background.js +++ /dev/null @@ -1,36 +0,0 @@ -import * as tabs from '../background/tabs'; -import * as consoleActions from '../actions/console'; -import actions from '../actions'; - -const doCompletion = (command, keywords, tabId) => { - if (command === 'buffer') { - return tabs.getCompletions(keywords).then((tabs) => { - let items = tabs.map((tab) => { - return { - caption: tab.title, - content: tab.title, - url: tab.url, - icon: tab.favIconUrl - } - }); - let completions = { - name: "Buffers", - items: items - }; - return browser.tabs.sendMessage( - tabId, - consoleActions.setCompletions([completions])); - }); - } - return Promise.resolve(); -}; - -export default function reducer(state, action = {}, sendToTab) { - // TODO hide sender object - switch (action.type) { - case actions.BACKGROUND_REQUEST_COMPLETIONS: - return doCompletion(action.command, action.keywords, sendToTab.id); - default: - return Promise.resolve(); - } -} diff --git a/src/reducers/command.js b/src/reducers/command.js deleted file mode 100644 index b645e29..0000000 --- a/src/reducers/command.js +++ /dev/null @@ -1,24 +0,0 @@ -import * as tabs from '../background/tabs'; -import actions from '../actions'; - -const cmdBuffer = (tab, arg) => { - if (isNaN(arg)) { - return tabs.selectByKeyword(tab, arg); - } else { - let index = parseInt(arg, 10) - 1; - return tabs.selectAt(index); - } -} - -export default function reducer(state, action, sendToTab) { - switch (action.type) { - case actions.COMMAND_OPEN_URL: - return browser.tabs.update(sendToTab.id, { url: action.url }); - case actions.COMMAND_TABOPEN_URL: - return browser.tabs.create({ url: action.url }); - case actions.COMMAND_BUFFER: - return cmdBuffer(sendToTab, action.keywords); - default: - return Promise.resolve(); - } -} diff --git a/src/reducers/console.js b/src/reducers/console.js index 3303802..31de654 100644 --- a/src/reducers/console.js +++ b/src/reducers/console.js @@ -31,7 +31,6 @@ export default function reducer(state = defaultState, action = {}) { return Object.assign({}, state, { errorShown: false, commandShown: false - }); default: return state; diff --git a/src/reducers/content.js b/src/reducers/content.js deleted file mode 100644 index ce59b18..0000000 --- a/src/reducers/content.js +++ /dev/null @@ -1,18 +0,0 @@ -import * as consoleFrames from '../console/frames'; -import actions from '../actions'; - -export default function reducer(state, action = {}) { - switch (action.type) { - case actions.CMD_OPEN: - return consoleFrames.showCommand(''); - case actions.CMD_TABS_OPEN: - if (action.alter) { - // alter url - return consoleFrames.showCommand('open ' + window.location.href); - } else { - return consoleFrames.showCommand('open '); - } - case actions.CMD_BUFFER: - return consoleFrames.showCommand('buffer '); - } -} diff --git a/src/reducers/index.js b/src/reducers/index.js index 6cc1a31..83a9a56 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -1,11 +1,14 @@ import inputReducer from '../reducers/input'; +import consoleReducer from '../reducers/console'; const defaultState = { - input: inputReducer(undefined, {}) + input: inputReducer(undefined, {}), + console: consoleReducer(undefined, {}) }; export default function reducer(state = defaultState, action = {}) { return Object.assign({}, state, { - input: inputReducer(state.input, action) + input: inputReducer(state.input, action), + console: consoleReducer(state.console, action) }); } diff --git a/test/actions/background.test.js b/test/actions/background.test.js deleted file mode 100644 index a3203ee..0000000 --- a/test/actions/background.test.js +++ /dev/null @@ -1,14 +0,0 @@ -import { expect } from "chai"; -import actions from '../../src/actions'; -import * as backgroundActions from '../../src/actions/background'; - -describe("background actions", () => { - describe("requestCompletions", () => { - it('create BACKGROUND_REQUEST_COMPLETIONS action', () => { - let action = backgroundActions.requestCompletions('buffer hoge fuga'); - expect(action.type).to.equal(actions.BACKGROUND_REQUEST_COMPLETIONS); - expect(action.command).to.equal('buffer'); - expect(action.keywords).to.equal('hoge fuga'); - }); - }); -}); diff --git a/test/actions/command.test.js b/test/actions/command.test.js deleted file mode 100644 index 01a67f2..0000000 --- a/test/actions/command.test.js +++ /dev/null @@ -1,51 +0,0 @@ -import { expect } from "chai"; -import actions from '../../src/actions'; -import * as commandActions from '../../src/actions/command'; - -describe("command actions", () => { - describe("exec", () => { - context("open command", () => { - it('create COMMAND_OPEN_URL acion with a full url', () => { - let action = commandActions.exec("open https://github.com/") - expect(action.type).to.equal(actions.COMMAND_OPEN_URL); - expect(action.url).to.equal('https://github.com/'); - }); - - it('create COMMAND_OPEN_URL acion with a domain name', () => { - let action = commandActions.exec("open github.com") - expect(action.type).to.equal(actions.COMMAND_OPEN_URL); - expect(action.url).to.equal('http://github.com'); - }); - }); - - context("tabopen command", () => { - it('create COMMAND_TABOPEN_URL acion with a full url', () => { - let action = commandActions.exec("tabopen https://github.com/") - expect(action.type).to.equal(actions.COMMAND_TABOPEN_URL); - expect(action.url).to.equal('https://github.com/'); - }); - - it('create COMMAND_TABOPEN_URL acion with a domain name', () => { - let action = commandActions.exec("tabopen github.com") - expect(action.type).to.equal(actions.COMMAND_TABOPEN_URL); - expect(action.url).to.equal('http://github.com'); - }); - }); - - context("buffer command", () => { - it('create COMMAND_BUFFER acion with a keywords', () => { - let action = commandActions.exec("buffer foo bar") - expect(action.type).to.equal(actions.COMMAND_BUFFER); - expect(action.keywords).to.equal('foo bar'); - }); - }); - - context("b command", () => { - it('create COMMAND_BUFFER acion with a keywords', () => { - let action = commandActions.exec("b foo bar") - expect(action.type).to.equal(actions.COMMAND_BUFFER); - expect(action.keywords).to.equal('foo bar'); - }); - }); - }); -}); From c7a3dd16e666b5f844bc98f19e58a3fe9967b5a4 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sat, 16 Sep 2017 20:26:13 +0900 Subject: [PATCH 08/12] message constants --- src/actions/operation.js | 3 ++- src/background/index.js | 2 +- src/console/console.js | 2 +- src/content/index.js | 5 +++-- src/messages/index.js | 3 +++ 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/actions/operation.js b/src/actions/operation.js index 2009818..e589b89 100644 --- a/src/actions/operation.js +++ b/src/actions/operation.js @@ -1,4 +1,5 @@ import operations from '../operations'; +import messages from '../messages'; import * as consoleActions from './console'; import * as tabs from '../background/tabs'; import * as zooms from '../background/zooms'; @@ -34,7 +35,7 @@ export function exec(operation, tab) { return consoleActions.showCommand('buffer '); default: return browser.tabs.sendMessage(tab.id, { - type: 'require.content.operation', + type: messages.CONTENT_OPERATION, operation }); } diff --git a/src/background/index.js b/src/background/index.js index d2cfeb4..f329a9c 100644 --- a/src/background/index.js +++ b/src/background/index.js @@ -32,7 +32,7 @@ backgroundStore.subscribe(() => { browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => { if (tabs.length > 0) { return browser.tabs.sendMessage(tabs[0].id, { - type: 'state.changed', + type: messages.STATE_UPDATE, state: backgroundStore.getState() }); } diff --git a/src/console/console.js b/src/console/console.js index 7e9bffa..d1547b8 100644 --- a/src/console/console.js +++ b/src/console/console.js @@ -171,7 +171,7 @@ const update = (state) => { } browser.runtime.onMessage.addListener((action) => { - if (action.type === 'state.changed') { + if (action.type === messages.STATE_UPDATE) { return update(action.state.console); } }); diff --git a/src/content/index.js b/src/content/index.js index d81e83e..43d632e 100644 --- a/src/content/index.js +++ b/src/content/index.js @@ -5,6 +5,7 @@ import * as scrolls from '../content/scrolls'; import * as histories from '../content/histories'; import Follow from '../content/follow'; import operations from '../operations'; +import messages from '../messages'; consoleFrames.initialize(window.document); @@ -51,9 +52,9 @@ const update = (state) => { browser.runtime.onMessage.addListener((action) => { switch (action.type) { - case 'state.changed': + case messages.STATE_UPDATE: return update(action.state); - case 'require.content.operation': + case messages.CONTENT_OPERATION: execOperation(action.operation); return Promise.resolve(); default: diff --git a/src/messages/index.js b/src/messages/index.js index 078ecaf..ad036a8 100644 --- a/src/messages/index.js +++ b/src/messages/index.js @@ -1,4 +1,7 @@ export default { + STATE_UPDATE: 'state.update', + CONTENT_OPERATION: 'content.operation', + CONSOLE_BLURRED: 'console.blured', CONSOLE_ENTERED: 'console.entered', CONSOLE_CHANGEED: 'console.changed' From 27702ef40236ca055e63373b2ee81d399d124cca Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sat, 16 Sep 2017 21:28:21 +0900 Subject: [PATCH 09/12] remove actions from content --- src/background/index.js | 13 +++---------- src/console/frames.js | 11 +---------- src/content/index.js | 11 +++++------ src/messages/index.js | 4 +++- 4 files changed, 12 insertions(+), 27 deletions(-) diff --git a/src/background/index.js b/src/background/index.js index f329a9c..cbd4721 100644 --- a/src/background/index.js +++ b/src/background/index.js @@ -56,18 +56,11 @@ const keyQueueChanged = (sendToTab, state) => { return browser.tabs.sendMessage(sendToTab.id, action); }; -const handleMessage = (action, sendToTab) => { - backgroundStore.dispatch(action); - - return browser.tabs.sendMessage(sendToTab.id, action); -}; - -browser.runtime.onMessage.addListener((action, sender) => { - handleMessage(action, sender.tab); -}); - browser.runtime.onMessage.addListener((message) => { switch (message.type) { + case messages.KEYDOWN: + backgroundStore.dispatch(inputActions.keyPress(message.code, message.ctrl)); + break; case messages.CONSOLE_BLURRED: backgroundStore.dispatch(consoleActions.hide()); break; diff --git a/src/console/frames.js b/src/console/frames.js index 0b6f3e2..ec1e38c 100644 --- a/src/console/frames.js +++ b/src/console/frames.js @@ -1,5 +1,4 @@ import './console-frame.scss'; -import * as consoleActions from '../actions/console'; const initialize = (doc) => { let iframe = doc.createElement('iframe'); @@ -11,17 +10,9 @@ const initialize = (doc) => { return iframe; } -const showCommand = (text) => { - return browser.runtime.sendMessage(consoleActions.showCommand(text)); -}; - -const showError = (text) => { - return browser.runtime.sendMessage(consoleActions.showError(text)); -} - const blur = (doc) => { let iframe = doc.getElementById('vimvixen-console-frame'); iframe.blur(); } -export { initialize, showCommand, showError, blur }; +export { initialize, blur }; diff --git a/src/content/index.js b/src/content/index.js index 43d632e..5d3735c 100644 --- a/src/content/index.js +++ b/src/content/index.js @@ -1,5 +1,4 @@ import '../console/console-frame.scss'; -import * as inputActions from '../actions/input'; import * as consoleFrames from '../console/frames'; import * as scrolls from '../content/scrolls'; import * as histories from '../content/histories'; @@ -13,11 +12,11 @@ window.addEventListener("keypress", (e) => { if (e.target instanceof HTMLInputElement) { return; } - browser.runtime.sendMessage(inputActions.keyPress(e.which, e.ctrlKey)) - .catch((err) => { - console.error("Vim Vixen:", err); - return consoleFrames.showError(err.message); - }); + browser.runtime.sendMessage({ + type: messages.KEYDOWN, + code: e.which, + ctrl: e.ctrl + }); }); const execOperation = (operation) => { diff --git a/src/messages/index.js b/src/messages/index.js index ad036a8..3bdecca 100644 --- a/src/messages/index.js +++ b/src/messages/index.js @@ -4,5 +4,7 @@ export default { CONSOLE_BLURRED: 'console.blured', CONSOLE_ENTERED: 'console.entered', - CONSOLE_CHANGEED: 'console.changed' + CONSOLE_CHANGEED: 'console.changed', + + KEYDOWN: 'keydown' }; From bf75603e1514e64cd608fb90f4c47afeec5c37f9 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sat, 16 Sep 2017 22:24:27 +0900 Subject: [PATCH 10/12] propagate sender object --- src/background/index.js | 63 +++++++++++++++++++++-------------------- src/store/index.js | 16 ++++++----- 2 files changed, 41 insertions(+), 38 deletions(-) diff --git a/src/background/index.js b/src/background/index.js index cbd4721..ef1b881 100644 --- a/src/background/index.js +++ b/src/background/index.js @@ -8,10 +8,13 @@ import messages from '../messages'; import * as store from '../store' let prevInput = []; -const backgroundStore = store.createStore(reducers, (e) => { +const backgroundStore = store.createStore(reducers, (e, sender) => { console.error('Vim-Vixen:', e); + if (sender) { + backgroundStore.dispatch(consoleActions.showError(e.message), sender); + } }); -backgroundStore.subscribe(() => { +backgroundStore.subscribe((sender) => { let currentInput = backgroundStore.getState().input if (JSON.stringify(prevInput) === JSON.stringify(currentInput)) { return @@ -21,54 +24,52 @@ backgroundStore.subscribe(() => { if (currentInput.keys.length === 0) { return; } - - browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => { - if (tabs.length > 0) { - return keyQueueChanged(tabs[0], backgroundStore.getState()); - } - }); + if (sender) { + return keyQueueChanged(backgroundStore.getState(), sender); + } }); -backgroundStore.subscribe(() => { - browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => { - if (tabs.length > 0) { - return browser.tabs.sendMessage(tabs[0].id, { - type: messages.STATE_UPDATE, - state: backgroundStore.getState() - }); - } - }); +backgroundStore.subscribe((sender) => { + if (sender) { + return browser.tabs.sendMessage(sender.tab.id, { + type: messages.STATE_UPDATE, + state: backgroundStore.getState() + }); + } }); -const keyQueueChanged = (sendToTab, state) => { +const keyQueueChanged = (state, sender) => { let prefix = keys.asKeymapChars(state.input.keys); let matched = Object.keys(keys.defaultKeymap).filter((keys) => { return keys.startsWith(prefix); }); if (matched.length == 0) { - backgroundStore.dispatch(inputActions.clearKeys()); + backgroundStore.dispatch(inputActions.clearKeys(), sender); return Promise.resolve(); } else if (matched.length > 1 || matched.length === 1 && prefix !== matched[0]) { return Promise.resolve(); } let action = keys.defaultKeymap[matched]; - backgroundStore.dispatch(operationActions.exec(action, sendToTab)); - backgroundStore.dispatch(inputActions.clearKeys()); - return browser.tabs.sendMessage(sendToTab.id, action); + backgroundStore.dispatch(operationActions.exec(action, sender.tab), sender); + backgroundStore.dispatch(inputActions.clearKeys(), sender); }; -browser.runtime.onMessage.addListener((message) => { +const handleMessage = (message, sender) => { switch (message.type) { case messages.KEYDOWN: - backgroundStore.dispatch(inputActions.keyPress(message.code, message.ctrl)); - break; + return backgroundStore.dispatch(inputActions.keyPress(message.code, message.ctrl), sender); case messages.CONSOLE_BLURRED: - backgroundStore.dispatch(consoleActions.hide()); - break; + return backgroundStore.dispatch(consoleActions.hide(), sender); case messages.CONSOLE_ENTERED: - backgroundStore.dispatch(commandActions.exec(message.text)); - break; + return backgroundStore.dispatch(commandActions.exec(message.text), sender); case messages.CONSOLE_CHANGEED: - backgroundStore.dispatch(commandActions.complete(message.text)); - break; + return backgroundStore.dispatch(commandActions.complete(message.text), sender); + } +} + +browser.runtime.onMessage.addListener((message, sender) => { + try { + handleMessage(message, sender); + } catch (e) { + backgroundStore.dispatch(consoleActions.showError(e.message), sender); } }); diff --git a/src/store/index.js b/src/store/index.js index 841fd17..a0a7791 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -6,16 +6,18 @@ class Store { this.subscribers = []; } - dispatch(action) { + dispatch(action, sender) { if (action instanceof Promise) { action.then((a) => { - this.transitNext(a); - }).catch(this.catcher) + this.transitNext(a, sender); + }).catch((e) => { + this.catcher(e, sender); + }); } else { try { - this.transitNext(action); + this.transitNext(action, sender); } catch (e) { - this.catcher(e); + this.catcher(e, sender); } } } @@ -28,11 +30,11 @@ class Store { this.subscribers.push(callback); } - transitNext(action) { + transitNext(action, sender) { let newState = this.reducer(this.state, action); if (JSON.stringify(this.state) !== JSON.stringify(newState)) { this.state = newState; - this.subscribers.forEach(f => f.call()) + this.subscribers.forEach(f => f(sender)); } } } From fb1d3b5962531a004bb14f7f78796f77149ee7e7 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sat, 16 Sep 2017 23:17:42 +0900 Subject: [PATCH 11/12] add store test and fix store --- src/store/index.js | 7 ++- test/store/index.test.js | 111 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 test/store/index.test.js diff --git a/src/store/index.js b/src/store/index.js index a0a7791..2d08296 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -2,8 +2,12 @@ class Store { constructor(reducer, catcher) { this.reducer = reducer; this.catcher = catcher; - this.state = this.reducer(undefined, {}); this.subscribers = []; + try { + this.state = this.reducer(undefined, {}); + } catch (e) { + catcher(e); + } } dispatch(action, sender) { @@ -20,6 +24,7 @@ class Store { this.catcher(e, sender); } } + return action } getState() { diff --git a/test/store/index.test.js b/test/store/index.test.js new file mode 100644 index 0000000..e19d50e --- /dev/null +++ b/test/store/index.test.js @@ -0,0 +1,111 @@ +import { expect } from "chai"; +import { createStore } from '../../src/store'; + +describe("Store class", () => { + const reducer = (state, action) => { + if (state == undefined) { + return 0; + } + return state + action; + }; + + describe("#dispatch", () => { + it('transit status by immediate action', () => { + let store = createStore(reducer); + store.dispatch(10); + expect(store.getState()).to.equal(10); + + store.dispatch(-20); + expect(store.getState()).to.equal(-10); + }); + + it('returns next state by immediate action', () => { + let store = createStore(reducer); + let dispatchedAction = store.dispatch(11); + expect(dispatchedAction).to.equal(11); + }); + + it('transit status by Promise action', () => { + let store = createStore(reducer); + let p1 = Promise.resolve(10); + + return store.dispatch(p1).then(() => { + expect(store.getState()).to.equal(10); + }).then(() => { + store.dispatch(Promise.resolve(-20)); + }).then(() => { + expect(store.getState()).to.equal(-10); + }); + }); + + it('returns next state by promise action', () => { + let store = createStore(reducer); + let dispatchedAction = store.dispatch(Promise.resolve(11)); + return dispatchedAction.then((value) => { + expect(value).to.equal(11); + }); + }); + }); + + describe("#subscribe", () => { + it('invoke callback', (done) => { + let store = createStore(reducer); + store.subscribe(() => { + expect(store.getState()).to.equal(15); + done(); + }); + store.dispatch(15); + }); + + it('propagate sender object', (done) => { + let store = createStore(reducer); + store.subscribe((sender) => { + expect(sender).to.equal('sender'); + done(); + }); + store.dispatch(15, 'sender'); + }); + }) + + describe("catcher", () => { + it('catch an error in reducer on initializing by immediate action', (done) => { + let store = createStore(() => { + throw new Error(); + }, (e) => { + expect(e).to.be.an('error'); + done(); + }); + }); + + it('catch an error in reducer on initializing by immediate action', (done) => { + let store = createStore((state, action) => { + if (state === undefined) return 0; + throw new Error(); + }, (e) => { + expect(e).to.be.an('error'); + done(); + }); + store.dispatch(20); + }); + + it('catch an error in reducer on initializing by promise action', (done) => { + let store = createStore((state, action) => { + if (state === undefined) return 0; + throw new Error(); + }, (e) => { + expect(e).to.be.an('error'); + done(); + }); + store.dispatch(Promise.resolve(20)); + }); + + it('catch an error in promise action', (done) => { + let store = createStore((state, action) => 0, (e) => { + expect(e).to.be.an('error'); + done(); + }); + store.dispatch(new Promise(() => { throw new Error() })); + }); + }) +}); + From ae394e28c0cbc8710d4937238c97328afddbca0f Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sat, 16 Sep 2017 23:24:37 +0900 Subject: [PATCH 12/12] keep error message --- src/reducers/console.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/reducers/console.js b/src/reducers/console.js index 31de654..27ccdc9 100644 --- a/src/reducers/console.js +++ b/src/reducers/console.js @@ -28,6 +28,10 @@ export default function reducer(state = defaultState, action = {}) { commandShown: false, }); case actions.CONSOLE_HIDE: + if (state.errorShown) { + // keep error message if shown + return state; + } return Object.assign({}, state, { errorShown: false, commandShown: false