diff options
Diffstat (limited to 'src')
25 files changed, 412 insertions, 437 deletions
diff --git a/src/background/actions/command.js b/src/background/actions/command.js index fb8ff98..a7f619b 100644 --- a/src/background/actions/command.js +++ b/src/background/actions/command.js @@ -1,5 +1,5 @@ -import messages from 'shared/messages'; import actions from '../actions'; +import * as consoleActions from './console'; import * as tabs from '../shared/tabs'; import * as bookmarks from '../shared/bookmarks'; import * as parsers from 'shared/commands/parsers'; @@ -39,7 +39,7 @@ const winopenCommand = (url) => { const bufferCommand = async(keywords) => { if (keywords.length === 0) { - return Promise.resolve([]); + return; } let keywordsStr = keywords.join(' '); let got = await browser.tabs.query({ @@ -57,24 +57,18 @@ const bufferCommand = async(keywords) => { const addbookmarkCommand = async(tab, args) => { if (!args[0]) { - return; + return { type: '' }; } let item = await bookmarks.create(args.join(' '), tab.url); if (!item) { - return browser.tabs.sendMessage(tab.id, { - type: messages.CONSOLE_SHOW_ERROR, - text: 'Could not create a bookmark', - }); + return consoleActions.error(tab, 'Could not create a bookmark'); } - return browser.tabs.sendMessage(tab.id, { - type: messages.CONSOLE_SHOW_INFO, - text: 'Saved current page: ' + item.url, - }); + return consoleActions.info(tab, 'Saved current page: ' + item.url); }; const setCommand = (args) => { if (!args[0]) { - return Promise.resolve(); + return { type: '' }; } let [name, value] = parsers.parseSetOption(args[0], properties.types); @@ -85,49 +79,69 @@ const setCommand = (args) => { }; }; -// eslint-disable-next-line complexity -const exec = (tab, line, settings) => { +// eslint-disable-next-line complexity, max-lines-per-function +const doExec = async(tab, line, settings) => { let [name, args] = parsers.parseCommandLine(line); switch (name) { case 'o': case 'open': - return openCommand(parsers.normalizeUrl(args, settings.search)); + await openCommand(parsers.normalizeUrl(args, settings.search)); + break; case 't': case 'tabopen': - return tabopenCommand(parsers.normalizeUrl(args, settings.search)); + await tabopenCommand(parsers.normalizeUrl(args, settings.search)); + break; case 'w': case 'winopen': - return winopenCommand(parsers.normalizeUrl(args, settings.search)); + await winopenCommand(parsers.normalizeUrl(args, settings.search)); + break; case 'b': case 'buffer': - return bufferCommand(args); + await bufferCommand(args); + break; case 'bd': case 'bdel': case 'bdelete': - return tabs.closeTabByKeywords(args.join(' ')); + await tabs.closeTabByKeywords(args.join(' ')); + break; case 'bd!': case 'bdel!': case 'bdelete!': - return tabs.closeTabByKeywordsForce(args.join(' ')); + await tabs.closeTabByKeywordsForce(args.join(' ')); + break; case 'bdeletes': - return tabs.closeTabsByKeywords(args.join(' ')); + await tabs.closeTabsByKeywords(args.join(' ')); + break; case 'bdeletes!': - return tabs.closeTabsByKeywordsForce(args.join(' ')); + await tabs.closeTabsByKeywordsForce(args.join(' ')); + break; case 'addbookmark': return addbookmarkCommand(tab, args); case 'set': return setCommand(args); case 'q': case 'quit': - return tabcloseCommand(); + await tabcloseCommand(); + break; case 'qa': case 'quitall': - return tabcloseAllCommand() - case '': - return Promise.resolve(); + await tabcloseAllCommand(); + break; + default: + return consoleActions.error(tab, name + ' command is not defined'); + } + return { type: '' }; +}; + +// eslint-disable-next-line complexity +const exec = async(tab, line, settings) => { + try { + let action = await doExec(tab, line, settings); + return action; + } catch (e) { + return consoleActions.error(tab, e.toString()); } - throw new Error(name + ' command is not defined'); }; export { exec }; diff --git a/src/background/actions/console.js b/src/background/actions/console.js new file mode 100644 index 0000000..d385b2d --- /dev/null +++ b/src/background/actions/console.js @@ -0,0 +1,41 @@ +import messages from 'shared/messages'; + +const error = async(tab, text) => { + await browser.tabs.sendMessage(tab.id, { + type: messages.CONSOLE_SHOW_ERROR, + text, + }); + return { type: '' }; +}; + +const info = async(tab, text) => { + await browser.tabs.sendMessage(tab.id, { + type: messages.CONSOLE_SHOW_INFO, + text, + }); + return { type: '' }; +}; + +const showCommand = async(tab, command) => { + await browser.tabs.sendMessage(tab.id, { + type: messages.CONSOLE_SHOW_COMMAND, + command, + }); + return { type: '' }; +}; + +const showFind = async(tab) => { + await browser.tabs.sendMessage(tab.id, { + type: messages.CONSOLE_SHOW_FIND + }); + return { type: '' }; +}; + +const hide = async(tab) => { + await browser.tabs.sendMessage(tab.id, { + type: messages.CONSOLE_HIDE, + }); + return { type: '' }; +}; + +export { error, info, showCommand, showFind, hide }; diff --git a/src/background/actions/tab.js b/src/background/actions/tab.js index 5cf1e8c..0f32a90 100644 --- a/src/background/actions/tab.js +++ b/src/background/actions/tab.js @@ -4,21 +4,24 @@ const openNewTab = async( url, openerTabId, background = false, adjacent = false ) => { if (!adjacent) { - return browser.tabs.create({ url, active: !background }); + await browser.tabs.create({ url, active: !background }); + return { type: '' }; } let tabs = await browser.tabs.query({ active: true, currentWindow: true }); - return browser.tabs.create({ + await browser.tabs.create({ url, openerTabId, active: !background, index: tabs[0].index + 1 }); + return { type: '' }; }; -const openToTab = (url, tab) => { - return browser.tabs.update(tab.id, { url: url }); +const openToTab = async(url, tab) => { + await browser.tabs.update(tab.id, { url: url }); + return { type: '' }; }; const selected = (tabId) => { diff --git a/src/background/components/operation.js b/src/background/components/operation.js index 465baf0..ce93270 100644 --- a/src/background/components/operation.js +++ b/src/background/components/operation.js @@ -2,6 +2,7 @@ import messages from 'shared/messages'; import operations from 'shared/operations'; import * as tabs from '../shared//tabs'; import * as zooms from '../shared/zooms'; +import * as consoleActions from '../actions/console'; export default class BackgroundComponent { constructor(store) { @@ -23,101 +24,104 @@ export default class BackgroundComponent { switch (message.type) { case messages.BACKGROUND_OPERATION: return this.store.dispatch( - this.exec(message.operation, sender.tab), - sender); + this.exec(message.operation, sender.tab)); } } // eslint-disable-next-line complexity, max-lines-per-function - exec(operation, tab) { + async exec(operation, tab) { let tabState = this.store.getState().tab; switch (operation.type) { case operations.TAB_CLOSE: - return tabs.closeTab(tab.id); + await tabs.closeTab(tab.id); + break; case operations.TAB_CLOSE_FORCE: - return tabs.closeTabForce(tab.id); + await tabs.closeTabForce(tab.id); + break; case operations.TAB_REOPEN: - return tabs.reopenTab(); + await tabs.reopenTab(); + break; case operations.TAB_PREV: - return tabs.selectPrevTab(tab.index, operation.count); + await tabs.selectPrevTab(tab.index, operation.count); + break; case operations.TAB_NEXT: - return tabs.selectNextTab(tab.index, operation.count); + await tabs.selectNextTab(tab.index, operation.count); + break; case operations.TAB_FIRST: - return tabs.selectFirstTab(); + await tabs.selectFirstTab(); + break; case operations.TAB_LAST: - return tabs.selectLastTab(); + await tabs.selectLastTab(); + break; case operations.TAB_PREV_SEL: if (tabState.previousSelected > 0) { - return tabs.selectTab(tabState.previousSelected); + await tabs.selectTab(tabState.previousSelected); } break; case operations.TAB_RELOAD: - return tabs.reload(tab, operation.cache); + await tabs.reload(tab, operation.cache); + break; case operations.TAB_PIN: - return tabs.updateTabPinned(tab, true); + await tabs.updateTabPinned(tab, true); + break; case operations.TAB_UNPIN: - return tabs.updateTabPinned(tab, false); + await tabs.updateTabPinned(tab, false); + break; case operations.TAB_TOGGLE_PINNED: - return tabs.toggleTabPinned(tab); + await tabs.toggleTabPinned(tab); + break; case operations.TAB_DUPLICATE: - return tabs.duplicate(tab.id); + await tabs.duplicate(tab.id); + break; case operations.ZOOM_IN: - return zooms.zoomIn(); + await zooms.zoomIn(); + break; case operations.ZOOM_OUT: - return zooms.zoomOut(); + await zooms.zoomOut(); + break; case operations.ZOOM_NEUTRAL: - return zooms.neutral(); + await zooms.neutral(); + break; case operations.COMMAND_SHOW: - return this.sendConsoleShowCommand(tab, ''); + return consoleActions.showCommand(tab, ''); case operations.COMMAND_SHOW_OPEN: if (operation.alter) { // alter url - return this.sendConsoleShowCommand(tab, 'open ' + tab.url); + return consoleActions.showCommand(tab, 'open ' + tab.url); } - return this.sendConsoleShowCommand(tab, 'open '); + return consoleActions.showCommand(tab, 'open '); case operations.COMMAND_SHOW_TABOPEN: if (operation.alter) { // alter url - return this.sendConsoleShowCommand(tab, 'tabopen ' + tab.url); + return consoleActions.showCommand(tab, 'tabopen ' + tab.url); } - return this.sendConsoleShowCommand(tab, 'tabopen '); + return consoleActions.showCommand(tab, 'tabopen '); case operations.COMMAND_SHOW_WINOPEN: if (operation.alter) { // alter url - return this.sendConsoleShowCommand(tab, 'winopen ' + tab.url); + return consoleActions.showCommand(tab, 'winopen ' + tab.url); } - return this.sendConsoleShowCommand(tab, 'winopen '); + return consoleActions.showCommand(tab, 'winopen '); case operations.COMMAND_SHOW_BUFFER: - return this.sendConsoleShowCommand(tab, 'buffer '); + return consoleActions.showCommand(tab, 'buffer '); case operations.COMMAND_SHOW_ADDBOOKMARK: if (operation.alter) { - return this.sendConsoleShowCommand(tab, 'addbookmark ' + tab.title); + return consoleActions.showCommand(tab, 'addbookmark ' + tab.title); } - return this.sendConsoleShowCommand(tab, 'addbookmark '); + return consoleActions.showCommand(tab, 'addbookmark '); case operations.FIND_START: - return browser.tabs.sendMessage(tab.id, { - type: messages.CONSOLE_SHOW_FIND - }); + return consoleActions.showFind(tab); case operations.CANCEL: - return browser.tabs.sendMessage(tab.id, { - type: messages.CONSOLE_HIDE, - }); + return consoleActions.hide(tab); case operations.PAGE_SOURCE: - return browser.tabs.create({ + await browser.tabs.create({ url: 'view-source:' + tab.url, index: tab.index + 1, openerTabId: tab.id, }); - default: - return Promise.resolve(); + break; } - } - - sendConsoleShowCommand(tab, command) { - return browser.tabs.sendMessage(tab.id, { - type: messages.CONSOLE_SHOW_COMMAND, - command, - }); + return { type: '' }; } } diff --git a/src/background/index.js b/src/background/index.js index 02de53f..8c4eafc 100644 --- a/src/background/index.js +++ b/src/background/index.js @@ -1,22 +1,17 @@ import * as settingActions from 'background/actions/setting'; -import messages from 'shared/messages'; import BackgroundComponent from 'background/components/background'; import OperationComponent from 'background/components/operation'; import TabComponent from 'background/components/tab'; import IndicatorComponent from 'background/components/indicator'; import reducers from 'background/reducers'; -import { createStore } from 'shared/store'; +import { createStore, applyMiddleware } from 'redux'; +import promise from 'redux-promise'; import * as versions from 'shared/versions'; -const store = createStore(reducers, (e, sender) => { - console.error('Vim-Vixen:', e); - if (sender) { - return browser.tabs.sendMessage(sender.tab.id, { - type: messages.CONSOLE_SHOW_ERROR, - text: e.message, - }); - } -}); +const store = createStore( + reducers, + applyMiddleware(promise), +); const checkAndNotifyUpdated = async() => { let updated = await versions.checkUpdated(); diff --git a/src/console/actions/console.js b/src/console/actions/console.js index f80045f..3713a76 100644 --- a/src/console/actions/console.js +++ b/src/console/actions/console.js @@ -1,3 +1,4 @@ +import messages from 'shared/messages'; import actions from 'console/actions'; const hide = () => { @@ -34,11 +35,30 @@ const showInfo = (text) => { }; const hideCommand = () => { + window.top.postMessage(JSON.stringify({ + type: messages.CONSOLE_UNFOCUS, + }), '*'); return { type: actions.CONSOLE_HIDE_COMMAND, }; }; +const enterCommand = async(text) => { + await browser.runtime.sendMessage({ + type: messages.CONSOLE_ENTER_COMMAND, + text, + }); + return hideCommand(text); +}; + +const enterFind = (text) => { + window.top.postMessage(JSON.stringify({ + type: messages.CONSOLE_ENTER_FIND, + text, + }), '*'); + return hideCommand(); +}; + const setConsoleText = (consoleText) => { return { type: actions.CONSOLE_SET_CONSOLE_TEXT, @@ -46,11 +66,15 @@ const setConsoleText = (consoleText) => { }; }; -const setCompletions = (completionSource, completions) => { +const getCompletions = async(text) => { + let completions = await browser.runtime.sendMessage({ + type: messages.CONSOLE_QUERY_COMPLETIONS, + text, + }); return { type: actions.CONSOLE_SET_COMPLETIONS, - completionSource, completions, + completionSource: text, }; }; @@ -68,5 +92,5 @@ const completionPrev = () => { export { hide, showCommand, showFind, showError, showInfo, hideCommand, setConsoleText, - setCompletions, completionNext, completionPrev + enterCommand, enterFind, getCompletions, completionNext, completionPrev }; diff --git a/src/console/components/console.js b/src/console/components/console.js index 417c9f6..bd3e344 100644 --- a/src/console/components/console.js +++ b/src/console/components/console.js @@ -1,4 +1,3 @@ -import messages from 'shared/messages'; import * as consoleActions from 'console/actions/console'; const inputShownMode = (state) => { @@ -26,15 +25,22 @@ export default class ConsoleComponent { onBlur() { let state = this.store.getState(); - if (state.mode === 'command') { - this.hideCommand(); + if (state.mode === 'command' || state.mode === 'find') { + return this.store.dispatch(consoleActions.hideCommand()); } } doEnter(e) { e.stopPropagation(); e.preventDefault(); - return this.onEntered(e.target.value); + + let state = this.store.getState(); + let value = e.target.value; + if (state.mode === 'command') { + return this.store.dispatch(consoleActions.enterCommand(value)); + } else if (state.mode === 'find') { + return this.store.dispatch(consoleActions.enterFind(value)); + } } selectNext(e) { @@ -51,11 +57,11 @@ export default class ConsoleComponent { onKeyDown(e) { if (e.keyCode === KeyboardEvent.DOM_VK_ESCAPE && e.ctrlKey) { - return this.hideCommand(); + this.store.dispatch(consoleActions.hideCommand()); } switch (e.keyCode) { case KeyboardEvent.DOM_VK_ESCAPE: - return this.hideCommand(); + return this.store.dispatch(consoleActions.hideCommand()); case KeyboardEvent.DOM_VK_RETURN: return this.doEnter(e); case KeyboardEvent.DOM_VK_TAB: @@ -69,7 +75,7 @@ export default class ConsoleComponent { break; case KeyboardEvent.DOM_VK_OPEN_BRACKET: if (e.ctrlKey) { - return this.hideCommand(); + return this.store.dispatch(consoleActions.hideCommand()); } break; case KeyboardEvent.DOM_VK_M: @@ -90,32 +96,10 @@ export default class ConsoleComponent { } } - onEntered(value) { - let state = this.store.getState(); - if (state.mode === 'command') { - browser.runtime.sendMessage({ - type: messages.CONSOLE_ENTER_COMMAND, - text: value, - }); - this.hideCommand(); - } else if (state.mode === 'find') { - this.hideCommand(); - window.top.postMessage(JSON.stringify({ - type: messages.CONSOLE_ENTER_FIND, - text: value, - }), '*'); - } - } - - async onInput(e) { - this.store.dispatch(consoleActions.setConsoleText(e.target.value)); - - let source = e.target.value; - let completions = await browser.runtime.sendMessage({ - type: messages.CONSOLE_QUERY_COMPLETIONS, - text: source, - }); - this.store.dispatch(consoleActions.setCompletions(source, completions)); + onInput(e) { + let text = e.target.value; + this.store.dispatch(consoleActions.setConsoleText(text)); + this.store.dispatch(consoleActions.getCompletions(text)); } onInputShown(state) { @@ -126,17 +110,12 @@ export default class ConsoleComponent { window.focus(); if (state.mode === 'command') { - this.onInput({ target: input }); + let text = state.consoleText; + input.value = text; + this.store.dispatch(consoleActions.getCompletions(text)); } } - hideCommand() { - this.store.dispatch(consoleActions.hideCommand()); - window.top.postMessage(JSON.stringify({ - type: messages.CONSOLE_UNFOCUS, - }), '*'); - } - update() { let state = this.store.getState(); diff --git a/src/console/index.js b/src/console/index.js index 156456c..8724a44 100644 --- a/src/console/index.js +++ b/src/console/index.js @@ -3,10 +3,14 @@ import messages from 'shared/messages'; import CompletionComponent from 'console/components/completion'; import ConsoleComponent from 'console/components/console'; import reducers from 'console/reducers'; -import { createStore } from 'shared/store'; +import { createStore, applyMiddleware } from 'redux'; +import promise from 'redux-promise'; import * as consoleActions from 'console/actions/console'; -const store = createStore(reducers); +const store = createStore( + reducers, + applyMiddleware(promise), +); window.addEventListener('load', () => { let wrapper = document.querySelector('#vimvixen-console-completion'); diff --git a/src/content/actions/addon.js b/src/content/actions/addon.js index 8d38025..b30cf16 100644 --- a/src/content/actions/addon.js +++ b/src/content/actions/addon.js @@ -1,15 +1,19 @@ +import messages from 'shared/messages'; import actions from 'content/actions'; -const enable = () => { - return { type: actions.ADDON_ENABLE }; -}; +const enable = () => setEnabled(true); -const disable = () => { - return { type: actions.ADDON_DISABLE }; -}; +const disable = () => setEnabled(false); -const toggleEnabled = () => { - return { type: actions.ADDON_TOGGLE_ENABLED }; +const setEnabled = async(enabled) => { + await browser.runtime.sendMessage({ + type: messages.ADDON_ENABLED_RESPONSE, + enabled, + }); + return { + type: actions.ADDON_SET_ENABLED, + enabled, + }; }; -export { enable, disable, toggleEnabled }; +export { enable, disable, setEnabled }; diff --git a/src/content/actions/index.js b/src/content/actions/index.js index 7e32e12..1c51ab0 100644 --- a/src/content/actions/index.js +++ b/src/content/actions/index.js @@ -1,8 +1,6 @@ export default { // Enable/disable - ADDON_ENABLE: 'addon.enable', - ADDON_DISABLE: 'addon.disable', - ADDON_TOGGLE_ENABLED: 'addon.toggle.enabled', + ADDON_SET_ENABLED: 'addon.set.enabled', // Settings SETTING_SET: 'setting.set', diff --git a/src/content/actions/operation.js b/src/content/actions/operation.js index 40ac52d..c1bd1c8 100644 --- a/src/content/actions/operation.js +++ b/src/content/actions/operation.js @@ -9,7 +9,7 @@ import * as addonActions from './addon'; import * as properties from 'shared/settings/properties'; // eslint-disable-next-line complexity, max-lines-per-function -const exec = (operation, repeat, settings) => { +const exec = (operation, repeat, settings, addonEnabled) => { let smoothscroll = settings.properties.smoothscroll || properties.defaults.smoothscroll; switch (operation.type) { @@ -18,60 +18,80 @@ const exec = (operation, repeat, settings) => { case operations.ADDON_DISABLE: return addonActions.disable(); case operations.ADDON_TOGGLE_ENABLED: - return addonActions.toggleEnabled(); + return addonActions.setEnabled(!addonEnabled); case operations.FIND_NEXT: - return window.top.postMessage(JSON.stringify({ + window.top.postMessage(JSON.stringify({ type: messages.FIND_NEXT, }), '*'); + break; case operations.FIND_PREV: - return window.top.postMessage(JSON.stringify({ + window.top.postMessage(JSON.stringify({ type: messages.FIND_PREV, }), '*'); + break; case operations.SCROLL_VERTICALLY: - return scrolls.scrollVertically(operation.count, smoothscroll, repeat); + scrolls.scrollVertically(operation.count, smoothscroll, repeat); + break; case operations.SCROLL_HORIZONALLY: - return scrolls.scrollHorizonally(operation.count, smoothscroll, repeat); + scrolls.scrollHorizonally(operation.count, smoothscroll, repeat); + break; case operations.SCROLL_PAGES: - return scrolls.scrollPages(operation.count, smoothscroll, repeat); + scrolls.scrollPages(operation.count, smoothscroll, repeat); + break; case operations.SCROLL_TOP: - return scrolls.scrollTop(smoothscroll, repeat); + scrolls.scrollTop(smoothscroll, repeat); + break; case operations.SCROLL_BOTTOM: - return scrolls.scrollBottom(smoothscroll, repeat); + scrolls.scrollBottom(smoothscroll, repeat); + break; case operations.SCROLL_HOME: - return scrolls.scrollHome(smoothscroll, repeat); + scrolls.scrollHome(smoothscroll, repeat); + break; case operations.SCROLL_END: - return scrolls.scrollEnd(smoothscroll, repeat); + scrolls.scrollEnd(smoothscroll, repeat); + break; case operations.FOLLOW_START: - return window.top.postMessage(JSON.stringify({ + window.top.postMessage(JSON.stringify({ type: messages.FOLLOW_START, newTab: operation.newTab, background: operation.background, }), '*'); + break; case operations.NAVIGATE_HISTORY_PREV: - return navigates.historyPrev(window); + navigates.historyPrev(window); + break; case operations.NAVIGATE_HISTORY_NEXT: - return navigates.historyNext(window); + navigates.historyNext(window); + break; case operations.NAVIGATE_LINK_PREV: - return navigates.linkPrev(window); + navigates.linkPrev(window); + break; case operations.NAVIGATE_LINK_NEXT: - return navigates.linkNext(window); + navigates.linkNext(window); + break; case operations.NAVIGATE_PARENT: - return navigates.parent(window); + navigates.parent(window); + break; case operations.NAVIGATE_ROOT: - return navigates.root(window); + navigates.root(window); + break; case operations.FOCUS_INPUT: - return focuses.focusInput(); + focuses.focusInput(); + break; case operations.URLS_YANK: urls.yank(window); - return consoleFrames.postInfo(window.document, 'Current url yanked'); + consoleFrames.postInfo(window.document, 'Current url yanked'); + break; case operations.URLS_PASTE: - return urls.paste(window, operation.newTab ? operation.newTab : false); + urls.paste(window, operation.newTab ? operation.newTab : false); + break; default: browser.runtime.sendMessage({ type: messages.BACKGROUND_OPERATION, operation, }); } + return { type: '' }; }; export { exec }; diff --git a/src/content/actions/setting.js b/src/content/actions/setting.js index e34b6e0..1c15dd7 100644 --- a/src/content/actions/setting.js +++ b/src/content/actions/setting.js @@ -1,6 +1,7 @@ import actions from 'content/actions'; import * as keyUtils from 'shared/utils/keys'; import operations from 'shared/operations'; +import messages from 'shared/messages'; const reservedKeymaps = { '<Esc>': { type: operations.CANCEL }, @@ -26,4 +27,11 @@ const set = (value) => { }; }; -export { set }; +const load = async() => { + let settings = await browser.runtime.sendMessage({ + type: messages.SETTINGS_QUERY, + }); + return set(settings); +}; + +export { set, load }; diff --git a/src/content/components/common/index.js b/src/content/components/common/index.js index 6437011..a1e71a1 100644 --- a/src/content/components/common/index.js +++ b/src/content/components/common/index.js @@ -4,6 +4,7 @@ import FollowComponent from './follow'; import * as settingActions from 'content/actions/setting'; import messages from 'shared/messages'; import * as addonActions from '../../actions/addon'; +import * as blacklists from 'shared/blacklists'; export default class Common { constructor(win, store) { @@ -14,42 +15,34 @@ export default class Common { input.onKey(key => follow.key(key)); input.onKey(key => keymapper.key(key)); + this.win = win; this.store = store; this.prevEnabled = undefined; + this.prevBlacklist = undefined; this.reloadSettings(); messages.onMessage(this.onMessage.bind(this)); - store.subscribe(() => this.update()); } onMessage(message) { + let { enabled } = this.store.getState().addon; switch (message.type) { case messages.SETTINGS_CHANGED: return this.reloadSettings(); case messages.ADDON_TOGGLE_ENABLED: - return this.store.dispatch(addonActions.toggleEnabled()); + this.store.dispatch(addonActions.setEnabled(!enabled)); } } - update() { - let enabled = this.store.getState().addon.enabled; - if (enabled !== this.prevEnabled) { - this.prevEnabled = enabled; - - browser.runtime.sendMessage({ - type: messages.ADDON_ENABLED_RESPONSE, - enabled, - }); - } - } - - async reloadSettings() { + reloadSettings() { try { - let settings = await browser.runtime.sendMessage({ - type: messages.SETTINGS_QUERY, + this.store.dispatch(settingActions.load()).then(({ value: settings }) => { + let enabled = !blacklists.includes( + settings.blacklist, this.win.location.href + ); + this.store.dispatch(addonActions.setEnabled(enabled)); }); - this.store.dispatch(settingActions.set(settings)); } catch (e) { // Sometime sendMessage fails when background script is not ready. console.warn(e); diff --git a/src/content/components/common/keymapper.js b/src/content/components/common/keymapper.js index d9d1b2f..4c294b4 100644 --- a/src/content/components/common/keymapper.js +++ b/src/content/components/common/keymapper.js @@ -20,6 +20,7 @@ export default class KeymapperComponent { this.store = store; } + // eslint-disable-next-line max-statements key(key) { this.store.dispatch(inputActions.keyPress(key)); @@ -47,8 +48,10 @@ export default class KeymapperComponent { return true; } let operation = keymaps.get(matched[0]); - this.store.dispatch(operationActions.exec( - operation, key.repeat, state.setting)); + let act = operationActions.exec( + operation, key.repeat, state.setting, state.addon.enabled + ); + this.store.dispatch(act); this.store.dispatch(inputActions.clearKeys()); return true; } diff --git a/src/content/components/top-content/index.js b/src/content/components/top-content/index.js index a0d0480..e22e957 100644 --- a/src/content/components/top-content/index.js +++ b/src/content/components/top-content/index.js @@ -2,16 +2,13 @@ import CommonComponent from '../common'; import FollowController from './follow-controller'; import FindComponent from './find'; import * as consoleFrames from '../../console-frames'; -import * as addonActions from '../../actions/addon'; import messages from 'shared/messages'; -import * as re from 'shared/utils/re'; export default class TopContent { constructor(win, store) { this.win = win; this.store = store; - this.prevBlacklist = undefined; new CommonComponent(win, store); // eslint-disable-line no-new new FollowController(win, store); // eslint-disable-line no-new @@ -21,32 +18,6 @@ export default class TopContent { consoleFrames.initialize(this.win.document); messages.onMessage(this.onMessage.bind(this)); - - this.store.subscribe(() => this.update()); - } - - update() { - let blacklist = this.store.getState().setting.blacklist; - if (JSON.stringify(this.prevBlacklist) !== JSON.stringify(blacklist)) { - this.disableIfBlack(blacklist); - this.prevBlacklist = blacklist; - } - } - - disableIfBlack(blacklist) { - let loc = this.win.location; - let partial = loc.host + loc.pathname; - let matched = blacklist - .map((item) => { - let pattern = item.includes('/') ? item : item + '/*'; - return re.fromWildcard(pattern); - }) - .some(regex => regex.test(partial)); - if (matched) { - this.store.dispatch(addonActions.disable()); - } else { - this.store.dispatch(addonActions.enable()); - } } onMessage(message) { diff --git a/src/content/index.js b/src/content/index.js index 97a8b30..3b0b49b 100644 --- a/src/content/index.js +++ b/src/content/index.js @@ -1,10 +1,14 @@ import './console-frame.scss'; -import { createStore } from 'shared/store'; +import { createStore, applyMiddleware } from 'redux'; +import promise from 'redux-promise'; import reducers from 'content/reducers'; import TopContentComponent from './components/top-content'; import FrameContentComponent from './components/frame-content'; -const store = createStore(reducers); +const store = createStore( + reducers, + applyMiddleware(promise), +); if (window.self === window.top) { new TopContentComponent(window, store); // eslint-disable-line no-new diff --git a/src/content/reducers/addon.js b/src/content/reducers/addon.js index b881ca0..0def55a 100644 --- a/src/content/reducers/addon.js +++ b/src/content/reducers/addon.js @@ -6,15 +6,9 @@ const defaultState = { export default function reducer(state = defaultState, action = {}) { switch (action.type) { - case actions.ADDON_ENABLE: + case actions.ADDON_SET_ENABLED: return { ...state, - enabled: true, }; - case actions.ADDON_DISABLE: - return { ...state, - enabled: false, }; - case actions.ADDON_TOGGLE_ENABLED: - return { ...state, - enabled: !state.enabled, }; + enabled: action.enabled, }; default: return state; } diff --git a/src/settings/actions/index.js b/src/settings/actions/index.js index 8c212c2..016f2a5 100644 --- a/src/settings/actions/index.js +++ b/src/settings/actions/index.js @@ -1,4 +1,7 @@ export default { // Settings SETTING_SET_SETTINGS: 'setting.set.settings', + SETTING_SHOW_ERROR: 'setting.show.error', + SETTING_SWITCH_TO_FORM: 'setting.switch.to.form', + SETTING_SWITCH_TO_JSON: 'setting.switch.to.json', }; diff --git a/src/settings/actions/setting.js b/src/settings/actions/setting.js index 1219ba5..3bb24be 100644 --- a/src/settings/actions/setting.js +++ b/src/settings/actions/setting.js @@ -1,8 +1,9 @@ import actions from 'settings/actions'; import messages from 'shared/messages'; -import DefaultSettings from 'shared/settings/default'; -import * as settingsStorage from 'shared/settings/storage'; +import * as validator from 'shared/settings/validator'; +import KeymapsForm from '../components/form/keymaps-form'; import * as settingsValues from 'shared/settings/values'; +import * as settingsStorage from 'shared/settings/storage'; const load = async() => { let settings = await settingsStorage.loadRaw(); @@ -10,6 +11,18 @@ const load = async() => { }; const save = async(settings) => { + try { + if (settings.source === 'json') { + let value = JSON.parse(settings.json); + validator.validate(value); + } + } catch (e) { + return { + type: actions.SETTING_SHOW_ERROR, + error: e.toString(), + json: settings.json, + }; + } await settingsStorage.save(settings); await browser.runtime.sendMessage({ type: messages.SETTINGS_RELOAD @@ -17,21 +30,39 @@ const save = async(settings) => { return set(settings); }; -const set = (settings) => { - let value = JSON.parse(DefaultSettings.json); - if (settings.source === 'json') { - value = settingsValues.valueFromJson(settings.json); - } else if (settings.source === 'form') { - value = settingsValues.valueFromForm(settings.form); +const switchToForm = (json) => { + try { + validator.validate(JSON.parse(json)); + // AllowdOps filters operations, this is dirty dependency + let form = settingsValues.formFromJson(json, KeymapsForm.AllowdOps); + return { + type: actions.SETTING_SWITCH_TO_FORM, + form, + }; + } catch (e) { + return { + type: actions.SETTING_SHOW_ERROR, + error: e.toString(), + json, + }; } +}; +const switchToJson = (form) => { + let json = settingsValues.jsonFromForm(form); + return { + type: actions.SETTING_SWITCH_TO_JSON, + json, + }; +}; + +const set = (settings) => { return { type: actions.SETTING_SET_SETTINGS, source: settings.source, json: settings.json, form: settings.form, - value, }; }; -export { load, save }; +export { load, save, switchToForm, switchToJson }; diff --git a/src/settings/components/index.jsx b/src/settings/components/index.jsx index c479986..66dc940 100644 --- a/src/settings/components/index.jsx +++ b/src/settings/components/index.jsx @@ -1,5 +1,6 @@ import './site.scss'; import { h, Component } from 'preact'; +import { connect } from 'preact-redux'; import Input from './ui/input'; import SearchForm from './form/search-form'; import KeymapsForm from './form/keymaps-form'; @@ -7,63 +8,36 @@ import BlacklistForm from './form/blacklist-form'; import PropertiesForm from './form/properties-form'; import * as properties from 'shared/settings/properties'; import * as settingActions from 'settings/actions/setting'; -import * as validator from 'shared/settings/validator'; -import * as settingsValues from 'shared/settings/values'; const DO_YOU_WANT_TO_CONTINUE = 'Some settings in JSON can be lost when migrating. ' + 'Do you want to continue?'; class SettingsComponent extends Component { - constructor(props, context) { - super(props, context); - - this.state = { - settings: { - json: '', - }, - errors: { - json: '', - } - }; - this.context.store.subscribe(this.stateChanged.bind(this)); - } - componentDidMount() { - this.context.store.dispatch(settingActions.load()); - } - - stateChanged() { - let settings = this.context.store.getState(); - this.setState({ - settings: { - source: settings.source, - json: settings.json, - form: settings.form, - } - }); + this.props.dispatch(settingActions.load()); } - renderFormFields() { + renderFormFields(form) { return <div> <fieldset> <legend>Keybindings</legend> <KeymapsForm - value={this.state.settings.form.keymaps} + value={form.keymaps} onChange={value => this.bindForm('keymaps', value)} /> </fieldset> <fieldset> <legend>Search Engines</legend> <SearchForm - value={this.state.settings.form.search} + value={form.search} onChange={value => this.bindForm('search', value)} /> </fieldset> <fieldset> <legend>Blacklist</legend> <BlacklistForm - value={this.state.settings.form.blacklist} + value={form.blacklist} onChange={value => this.bindForm('blacklist', value)} /> </fieldset> @@ -71,33 +45,33 @@ class SettingsComponent extends Component { <legend>Properties</legend> <PropertiesForm types={properties.types} - value={this.state.settings.form.properties} + value={form.properties} onChange={value => this.bindForm('properties', value)} /> </fieldset> </div>; } - renderJsonFields() { + renderJsonFields(json, error) { return <div> <Input type='textarea' name='json' label='Plain JSON' spellCheck='false' - error={this.state.errors.json} - onChange={this.bindValue.bind(this)} - value={this.state.settings.json} + error={error} + onChange={this.bindJson.bind(this)} + value={json} /> </div>; } render() { let fields = null; - if (this.state.settings.source === 'form') { - fields = this.renderFormFields(); - } else if (this.state.settings.source === 'json') { - fields = this.renderJsonFields(); + if (this.props.source === 'form') { + fields = this.renderFormFields(this.props.form); + } else if (this.props.source === 'json') { + fields = this.renderJsonFields(this.props.json, this.props.error); } return ( <div> @@ -108,7 +82,7 @@ class SettingsComponent extends Component { id='setting-source-form' name='source' label='Use form' - checked={this.state.settings.source === 'form'} + checked={this.props.source === 'form'} value='form' onChange={this.bindSource.bind(this)} /> @@ -116,7 +90,7 @@ class SettingsComponent extends Component { type='radio' name='source' label='Use plain JSON' - checked={this.state.settings.source === 'json'} + checked={this.props.source === 'json'} value='json' onChange={this.bindSource.bind(this)} /> @@ -126,98 +100,44 @@ class SettingsComponent extends Component { ); } - validate(target) { - if (target.name === 'json') { - let settings = JSON.parse(target.value); - validator.validate(settings); - } - } - - validateValue(e) { - let next = { ...this.state }; - - next.errors.json = ''; - try { - this.validate(e.target); - } catch (err) { - next.errors.json = err.message; - } - next.settings[e.target.name] = e.target.value; - } - bindForm(name, value) { - let next = { ...this.state, - settings: { ...this.state.settings, - form: { ...this.state.settings.form }}}; - next.settings.form[name] = value; - this.setState(next); - this.context.store.dispatch(settingActions.save(next.settings)); - } - - bindValue(e) { - let next = { ...this.state }; - let error = false; - - next.errors.json = ''; - try { - this.validate(e.target); - } catch (err) { - next.errors.json = err.message; - error = true; - } - next.settings[e.target.name] = e.target.value; - - this.setState(this.state); - if (!error) { - this.context.store.dispatch(settingActions.save(next.settings)); - } - } - - migrateToForm() { - let b = window.confirm(DO_YOU_WANT_TO_CONTINUE); - if (!b) { - this.setState(this.state); - return; - } - try { - validator.validate(JSON.parse(this.state.settings.json)); - } catch (err) { - this.setState(this.state); - return; - } - - let form = settingsValues.formFromJson( - this.state.settings.json, KeymapsForm.AllowdOps); - let next = { ...this.state }; - next.settings.form = form; - next.settings.source = 'form'; - next.errors.json = ''; - - this.setState(next); - this.context.store.dispatch(settingActions.save(next.settings)); + let settings = { + source: this.props.source, + json: this.props.json, + form: { ...this.props.form }, + }; + settings.form[name] = value; + this.props.dispatch(settingActions.save(settings)); } - migrateToJson() { - let json = settingsValues.jsonFromForm(this.state.settings.form); - let next = { ...this.state }; - next.settings.json = json; - next.settings.source = 'json'; - next.errors.json = ''; - - this.setState(next); - this.context.store.dispatch(settingActions.save(next.settings)); + bindJson(e) { + let settings = { + source: this.props.source, + json: e.target.value, + form: this.props.form, + }; + this.props.dispatch(settingActions.save(settings)); } bindSource(e) { - let from = this.state.settings.source; + let from = this.props.source; let to = e.target.value; if (from === 'form' && to === 'json') { - this.migrateToJson(); + this.props.dispatch(settingActions.switchToJson(this.props.form)); } else if (from === 'json' && to === 'form') { - this.migrateToForm(); + let b = window.confirm(DO_YOU_WANT_TO_CONTINUE); + if (!b) { + return; + } + this.props.dispatch(settingActions.switchToForm(this.props.json)); } + + let settings = this.context.store.getState(); + this.props.dispatch(settingActions.save(settings)); } } -export default SettingsComponent; +const mapStateToProps = state => state; + +export default connect(mapStateToProps)(SettingsComponent); diff --git a/src/settings/index.jsx b/src/settings/index.jsx index eb251b4..8097d31 100644 --- a/src/settings/index.jsx +++ b/src/settings/index.jsx @@ -1,10 +1,14 @@ import { h, render } from 'preact'; import SettingsComponent from './components'; -import reducer from 'settings/reducers/setting'; -import Provider from 'shared/store/provider'; -import { createStore } from 'shared/store'; +import reducer from './reducers/setting'; +import { Provider } from 'preact-redux'; +import promise from 'redux-promise'; +import { createStore, applyMiddleware } from 'redux'; -const store = createStore(reducer); +const store = createStore( + reducer, + applyMiddleware(promise), +); document.addEventListener('DOMContentLoaded', () => { let wrapper = document.getElementById('vimvixen-settings'); diff --git a/src/settings/reducers/setting.js b/src/settings/reducers/setting.js index 70c6183..8e4a415 100644 --- a/src/settings/reducers/setting.js +++ b/src/settings/reducers/setting.js @@ -4,20 +4,33 @@ const defaultState = { source: '', json: '', form: null, - value: {} + error: '', }; export default function reducer(state = defaultState, action = {}) { switch (action.type) { case actions.SETTING_SET_SETTINGS: - return { + return { ...state, source: action.source, json: action.json, form: action.form, - value: action.value, - }; + errors: '', + error: '', }; + case actions.SETTING_SHOW_ERROR: + return { ...state, + error: action.text, + json: action.json, }; + case actions.SETTING_SWITCH_TO_FORM: + return { ...state, + error: '', + source: 'form', + form: action.form, }; + case actions.SETTING_SWITCH_TO_JSON: + return { ...state, + error: '', + source: 'json', + json: action.json, }; default: return state; } } - diff --git a/src/shared/blacklists.js b/src/shared/blacklists.js new file mode 100644 index 0000000..19ed3f1 --- /dev/null +++ b/src/shared/blacklists.js @@ -0,0 +1,13 @@ +import * as re from 'shared/utils/re'; + +const includes = (blacklist, url) => { + let u = new URL(url); + return blacklist.some((item) => { + if (!item.includes('/')) { + return re.fromWildcard(item).test(u.hostname); + } + return re.fromWildcard(item).test(u.hostname + u.pathname); + }); +}; + +export { includes }; diff --git a/src/shared/store/index.js b/src/shared/store/index.js deleted file mode 100644 index 2fafdf1..0000000 --- a/src/shared/store/index.js +++ /dev/null @@ -1,53 +0,0 @@ -class Store { - constructor(reducer, catcher) { - this.reducer = reducer; - this.catcher = catcher; - this.subscribers = []; - try { - this.state = this.reducer(undefined, {}); - } catch (e) { - catcher(e); - } - } - - dispatch(action, sender) { - if (action instanceof Promise) { - action.then((a) => { - this.transitNext(a, sender); - }).catch((e) => { - this.catcher(e, sender); - }); - } else { - try { - this.transitNext(action, sender); - } catch (e) { - this.catcher(e, sender); - } - } - return action; - } - - getState() { - return this.state; - } - - subscribe(callback) { - this.subscribers.push(callback); - } - - 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(sender)); - } - } -} - -const empty = () => {}; - -const createStore = (reducer, catcher = empty) => { - return new Store(reducer, catcher); -}; - -export { createStore }; diff --git a/src/shared/store/provider.jsx b/src/shared/store/provider.jsx deleted file mode 100644 index fe925aa..0000000 --- a/src/shared/store/provider.jsx +++ /dev/null @@ -1,15 +0,0 @@ -import { h, Component } from 'preact'; - -class Provider extends Component { - getChildContext() { - return { store: this.props.store }; - } - - render() { - return <div> - { this.props.children } - </div>; - } -} - -export default Provider; |