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'); - }); - }); - }); -});