From 43ef2fe59513ffaa2acff4c8825bd4208151ec05 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sat, 7 Jul 2018 11:42:06 +0900 Subject: [PATCH 01/21] Install redux --- package-lock.json | 18 ++++++++++++++++++ package.json | 1 + 2 files changed, 19 insertions(+) diff --git a/package-lock.json b/package-lock.json index 7c59307..56212fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11418,6 +11418,24 @@ } } }, + "redux": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.0.tgz", + "integrity": "sha512-NnnHF0h0WVE/hXyrB6OlX67LYRuaf/rJcbWvnHHEPCF/Xa/AZpwhs/20WyqzQae5x4SD2F9nPObgBh2rxAgLiA==", + "dev": true, + "requires": { + "loose-envify": "^1.1.0", + "symbol-observable": "^1.2.0" + }, + "dependencies": { + "symbol-observable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", + "dev": true + } + } + }, "regenerate": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", diff --git a/package.json b/package.json index cf0d241..50461be 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "node-firefox-connect": "^1.2.0", "node-sass": "^4.9.0", "preact": "^8.2.9", + "redux": "^4.0.0", "sass-loader": "^7.0.1", "sinon-chrome": "^2.3.2", "style-loader": "^0.21.0", From dbe7e1b185553e8a00008ca0c6c5aab96c1a9626 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sat, 7 Jul 2018 11:53:40 +0900 Subject: [PATCH 02/21] Install redux-promise --- package-lock.json | 19 +++++++++++++++++++ package.json | 1 + 2 files changed, 20 insertions(+) diff --git a/package-lock.json b/package-lock.json index 56212fb..04b506b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5352,6 +5352,15 @@ "readable-stream": "^2.0.4" } }, + "flux-standard-action": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/flux-standard-action/-/flux-standard-action-2.0.3.tgz", + "integrity": "sha512-HR2IjMkqJreoFm1Hx7hmMAtUFeo+ad8hPMYPo8o3YSWjbSq0sMwuXMbv4giB3TXngYB7+svkAJewQwwvwsE6xw==", + "dev": true, + "requires": { + "lodash": "^4.0.0" + } + }, "follow-redirects": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.4.1.tgz", @@ -11436,6 +11445,16 @@ } } }, + "redux-promise": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/redux-promise/-/redux-promise-0.6.0.tgz", + "integrity": "sha512-R2mGxJbPFgXyCNbFDE6LjTZhCEuACF54g1bxld3nqBhnRMX0OsUyWk77moF7UMGkUdl5WOAwc4BC5jOd1dunqQ==", + "dev": true, + "requires": { + "flux-standard-action": "^2.0.3", + "is-promise": "^2.1.0" + } + }, "regenerate": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", diff --git a/package.json b/package.json index 50461be..57bd7f4 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "node-sass": "^4.9.0", "preact": "^8.2.9", "redux": "^4.0.0", + "redux-promise": "^0.6.0", "sass-loader": "^7.0.1", "sinon-chrome": "^2.3.2", "style-loader": "^0.21.0", From 4fa93663d7761a8ea67973a5c4f317f4138518ed Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sat, 7 Jul 2018 14:30:48 +0900 Subject: [PATCH 03/21] Fix lint --- src/background/actions/command.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/background/actions/command.js b/src/background/actions/command.js index fb8ff98..af7c546 100644 --- a/src/background/actions/command.js +++ b/src/background/actions/command.js @@ -123,7 +123,7 @@ const exec = (tab, line, settings) => { return tabcloseCommand(); case 'qa': case 'quitall': - return tabcloseAllCommand() + return tabcloseAllCommand(); case '': return Promise.resolve(); } From 85b4bd5b073af729ff325e90fa3c9078f90277ac Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sat, 7 Jul 2018 14:33:14 +0900 Subject: [PATCH 04/21] Use pure redux on console --- src/console/actions/console.js | 30 ++++++++++++-- src/console/components/console.js | 61 +++++++++------------------- src/console/index.js | 8 +++- test/console/actions/console.test.js | 25 ++++-------- 4 files changed, 61 insertions(+), 63 deletions(-) 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/test/console/actions/console.test.js b/test/console/actions/console.test.js index 77855cd..10cd9fe 100644 --- a/test/console/actions/console.test.js +++ b/test/console/actions/console.test.js @@ -23,14 +23,6 @@ describe("console actions", () => { }); }); - describe("showInfo", () => { - it('create CONSOLE_SHOW_INFO action', () => { - let action = consoleActions.showInfo('an info'); - expect(action.type).to.equal(actions.CONSOLE_SHOW_INFO); - expect(action.text).to.equal('an info'); - }); - }); - describe("showError", () => { it('create CONSOLE_SHOW_ERROR action', () => { let action = consoleActions.showError('an error'); @@ -39,6 +31,14 @@ describe("console actions", () => { }); }); + describe("showInfo", () => { + it('create CONSOLE_SHOW_INFO action', () => { + let action = consoleActions.showInfo('an info'); + expect(action.type).to.equal(actions.CONSOLE_SHOW_INFO); + expect(action.text).to.equal('an info'); + }); + }); + describe("hideCommand", () => { it('create CONSOLE_HIDE_COMMAND action', () => { let action = consoleActions.hideCommand(); @@ -54,15 +54,6 @@ describe("console actions", () => { }); }); - describe("setCompletions", () => { - it('create CONSOLE_SET_COMPLETIONS action', () => { - let action = consoleActions.setCompletions('query', [1, 2, 3]); - expect(action.type).to.equal(actions.CONSOLE_SET_COMPLETIONS); - expect(action.completionSource).to.deep.equal('query'); - expect(action.completions).to.deep.equal([1, 2, 3]); - }); - }); - describe("completionPrev", () => { it('create CONSOLE_COMPLETION_PREV action', () => { let action = consoleActions.completionPrev(); From 327144a3aa962b51d9b143941be4e626abbc7ee7 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sat, 7 Jul 2018 16:58:09 +0900 Subject: [PATCH 05/21] [wip] content --- src/content/actions/operation.js | 58 ++++++++++++++------- src/content/actions/setting.js | 10 +++- src/content/components/common/index.js | 33 ++++++++++-- src/content/components/common/keymapper.js | 5 +- src/content/components/top-content/index.js | 29 ----------- src/content/index.js | 9 +++- 6 files changed, 86 insertions(+), 58 deletions(-) diff --git a/src/content/actions/operation.js b/src/content/actions/operation.js index 40ac52d..a1761fc 100644 --- a/src/content/actions/operation.js +++ b/src/content/actions/operation.js @@ -20,58 +20,78 @@ const exec = (operation, repeat, settings) => { case operations.ADDON_TOGGLE_ENABLED: return addonActions.toggleEnabled(); 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..50c09ca 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 = { '': { 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 { load }; diff --git a/src/content/components/common/index.js b/src/content/components/common/index.js index 6437011..3a23956 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 re from 'shared/utils/re'; export default class Common { constructor(win, store) { @@ -14,8 +15,10 @@ 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(); @@ -42,14 +45,34 @@ export default class Common { enabled, }); } + + let blacklist = JSON.stringify(this.store.getState().setting.blacklist); + if (blacklist !== this.prevBlacklist) { + this.prevBlacklist = blacklist; + + this.disableIfBlack(this.store.getState().setting.blacklist); + } } - async reloadSettings() { + 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()); + } + } + + reloadSettings() { try { - let settings = await browser.runtime.sendMessage({ - type: messages.SETTINGS_QUERY, - }); - this.store.dispatch(settingActions.set(settings)); + this.store.dispatch(settingActions.load()); } 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..326bdd9 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,8 @@ 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); + 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..d6743ce 100644 --- a/src/content/index.js +++ b/src/content/index.js @@ -1,10 +1,15 @@ 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 From cf0dcf25226611590d76d263b5a4acdc89510d09 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sat, 7 Jul 2018 17:52:01 +0900 Subject: [PATCH 06/21] blacklist as an util --- src/content/components/common/index.js | 31 ++++++------------- src/shared/blacklists.js | 13 ++++++++ test/shared/blacklists.test.js | 42 ++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 21 deletions(-) create mode 100644 src/shared/blacklists.js create mode 100644 test/shared/blacklists.test.js diff --git a/src/content/components/common/index.js b/src/content/components/common/index.js index 3a23956..e81bf1f 100644 --- a/src/content/components/common/index.js +++ b/src/content/components/common/index.js @@ -5,6 +5,7 @@ import * as settingActions from 'content/actions/setting'; import messages from 'shared/messages'; import * as addonActions from '../../actions/addon'; import * as re from 'shared/utils/re'; +import * as blacklists from 'shared/blacklists'; export default class Common { constructor(win, store) { @@ -46,27 +47,15 @@ export default class Common { }); } - let blacklist = JSON.stringify(this.store.getState().setting.blacklist); - if (blacklist !== this.prevBlacklist) { - this.prevBlacklist = blacklist; - - this.disableIfBlack(this.store.getState().setting.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()); + let blacklist = this.store.getState().setting.blacklist; + let str = JSON.stringify(blacklist) + if (blacklist !== str) { + this.prevBlacklist = str; + if (blacklists.includes(blacklist, this.win.location)) { + this.store.dispatch(addonActions.disable()); + } else { + this.store.dispatch(addonActions.enable()); + } } } diff --git a/src/shared/blacklists.js b/src/shared/blacklists.js new file mode 100644 index 0000000..21e3e14 --- /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/test/shared/blacklists.test.js b/test/shared/blacklists.test.js new file mode 100644 index 0000000..87e89c5 --- /dev/null +++ b/test/shared/blacklists.test.js @@ -0,0 +1,42 @@ +import { includes } from 'shared/blacklists'; + +describe("shared/blacklist", () => { + it('matches by *', () => { + let blacklist = ['*']; + + expect(includes(blacklist, 'https://github.com/abc')).to.be.true; + }) + + it('matches by hostname', () => { + let blacklist = ['github.com']; + + expect(includes(blacklist, 'https://github.com')).to.be.true; + expect(includes(blacklist, 'https://gist.github.com')).to.be.false; + expect(includes(blacklist, 'https://github.com/ueokande')).to.be.true; + expect(includes(blacklist, 'https://github.org')).to.be.false; + expect(includes(blacklist, 'https://google.com/search?q=github.org')).to.be.false; + }) + + it('matches by hostname with wildcard', () => { + let blacklist = ['*.github.com']; + + expect(includes(blacklist, 'https://github.com')).to.be.false; + expect(includes(blacklist, 'https://gist.github.com')).to.be.true; + }) + + it('matches by path', () => { + let blacklist = ['github.com/abc']; + + expect(includes(blacklist, 'https://github.com/abc')).to.be.true; + expect(includes(blacklist, 'https://github.com/abcdef')).to.be.false; + expect(includes(blacklist, 'https://gist.github.com/abc')).to.be.false; + }) + + it('matches by path with wildcard', () => { + let blacklist = ['github.com/abc*']; + + expect(includes(blacklist, 'https://github.com/abc')).to.be.true; + expect(includes(blacklist, 'https://github.com/abcdef')).to.be.true; + expect(includes(blacklist, 'https://gist.github.com/abc')).to.be.false; + }) +}); From efa1cb396733ac0bedb7c1e86fd1974fbc801135 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sat, 7 Jul 2018 22:05:22 +0900 Subject: [PATCH 07/21] Use official redux on content --- src/content/actions/addon.js | 22 ++++++++------ src/content/actions/index.js | 4 +-- src/content/actions/operation.js | 4 +-- src/content/actions/setting.js | 2 +- src/content/components/common/index.js | 35 +++++----------------- src/content/components/common/keymapper.js | 4 ++- src/content/index.js | 1 - src/content/reducers/addon.js | 10 ++----- src/shared/blacklists.js | 4 +-- test/content/actions/addon.test.js | 25 ---------------- test/content/reducers/addon.test.js | 24 ++------------- 11 files changed, 34 insertions(+), 101 deletions(-) delete mode 100644 test/content/actions/addon.test.js 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 a1761fc..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,7 +18,7 @@ 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: window.top.postMessage(JSON.stringify({ type: messages.FIND_NEXT, diff --git a/src/content/actions/setting.js b/src/content/actions/setting.js index 50c09ca..1c15dd7 100644 --- a/src/content/actions/setting.js +++ b/src/content/actions/setting.js @@ -34,4 +34,4 @@ const load = async() => { return set(settings); }; -export { load }; +export { set, load }; diff --git a/src/content/components/common/index.js b/src/content/components/common/index.js index e81bf1f..a1e71a1 100644 --- a/src/content/components/common/index.js +++ b/src/content/components/common/index.js @@ -4,7 +4,6 @@ import FollowComponent from './follow'; import * as settingActions from 'content/actions/setting'; import messages from 'shared/messages'; import * as addonActions from '../../actions/addon'; -import * as re from 'shared/utils/re'; import * as blacklists from 'shared/blacklists'; export default class Common { @@ -24,44 +23,26 @@ export default class Common { 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()); - } - } - - update() { - let enabled = this.store.getState().addon.enabled; - if (enabled !== this.prevEnabled) { - this.prevEnabled = enabled; - - browser.runtime.sendMessage({ - type: messages.ADDON_ENABLED_RESPONSE, - enabled, - }); - } - - let blacklist = this.store.getState().setting.blacklist; - let str = JSON.stringify(blacklist) - if (blacklist !== str) { - this.prevBlacklist = str; - if (blacklists.includes(blacklist, this.win.location)) { - this.store.dispatch(addonActions.disable()); - } else { - this.store.dispatch(addonActions.enable()); - } + this.store.dispatch(addonActions.setEnabled(!enabled)); } } reloadSettings() { try { - this.store.dispatch(settingActions.load()); + this.store.dispatch(settingActions.load()).then(({ value: settings }) => { + let enabled = !blacklists.includes( + settings.blacklist, this.win.location.href + ); + this.store.dispatch(addonActions.setEnabled(enabled)); + }); } 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 326bdd9..4c294b4 100644 --- a/src/content/components/common/keymapper.js +++ b/src/content/components/common/keymapper.js @@ -48,7 +48,9 @@ export default class KeymapperComponent { return true; } let operation = keymaps.get(matched[0]); - let act = 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/index.js b/src/content/index.js index d6743ce..3b0b49b 100644 --- a/src/content/index.js +++ b/src/content/index.js @@ -10,7 +10,6 @@ const store = createStore( applyMiddleware(promise), ); - if (window.self === window.top) { new TopContentComponent(window, store); // eslint-disable-line no-new } else { 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/shared/blacklists.js b/src/shared/blacklists.js index 21e3e14..19ed3f1 100644 --- a/src/shared/blacklists.js +++ b/src/shared/blacklists.js @@ -1,13 +1,13 @@ import * as re from 'shared/utils/re'; const includes = (blacklist, url) => { - let u = new URL(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/test/content/actions/addon.test.js b/test/content/actions/addon.test.js deleted file mode 100644 index 5f96372..0000000 --- a/test/content/actions/addon.test.js +++ /dev/null @@ -1,25 +0,0 @@ -import actions from 'content/actions'; -import * as addonActions from 'content/actions/addon'; - -describe("addon actions", () => { - describe("enable", () => { - it('create ADDON_ENABLE action', () => { - let action = addonActions.enable(); - expect(action.type).to.equal(actions.ADDON_ENABLE); - }); - }); - - describe("disable", () => { - it('create ADDON_DISABLE action', () => { - let action = addonActions.disable(); - expect(action.type).to.equal(actions.ADDON_DISABLE); - }); - }); - - describe("toggle", () => { - it('create ADDON_TOGGLE_ENABLED action', () => { - let action = addonActions.toggleEnabled(); - expect(action.type).to.equal(actions.ADDON_TOGGLE_ENABLED); - }); - }); -}); diff --git a/test/content/reducers/addon.test.js b/test/content/reducers/addon.test.js index 8c546d2..d4eb845 100644 --- a/test/content/reducers/addon.test.js +++ b/test/content/reducers/addon.test.js @@ -7,31 +7,11 @@ describe("addon reducer", () => { expect(state).to.have.property('enabled', true); }); - it('return next state for ADDON_ENABLE', () => { - let action = { type: actions.ADDON_ENABLE}; + it('return next state for ADDON_SET_ENABLED', () => { + let action = { type: actions.ADDON_SET_ENABLED, enabled: true }; let prev = { enabled: false }; let state = addonReducer(prev, action); expect(state.enabled).is.equal(true); }); - - it('return next state for ADDON_DISABLE', () => { - let action = { type: actions.ADDON_DISABLE}; - let prev = { enabled: true }; - let state = addonReducer(prev, action); - - expect(state.enabled).is.equal(false); - }); - - it('return next state for ADDON_TOGGLE_ENABLED', () => { - let action = { type: actions.ADDON_TOGGLE_ENABLED }; - let state = { enabled: false }; - - state = addonReducer(state, action); - expect(state.enabled).is.equal(true); - - state = addonReducer(state, action); - expect(state.enabled).is.equal(false); - }); - }); From d781dfc78636885df7cfe3616fd52a7e21d1f5e0 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sun, 8 Jul 2018 11:08:56 +0900 Subject: [PATCH 08/21] Use official redux on background --- src/background/actions/command.js | 68 +++++++++++-------- src/background/actions/console.js | 41 +++++++++++ src/background/actions/tab.js | 11 +-- src/background/components/operation.js | 94 ++++++++++++++------------ src/background/index.js | 17 ++--- 5 files changed, 144 insertions(+), 87 deletions(-) create mode 100644 src/background/actions/console.js diff --git a/src/background/actions/command.js b/src/background/actions/command.js index af7c546..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..0fa3cf8 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, 'win ' + 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(); From 212076bc5fb4800889d6802e5280dc0620a852ec Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sun, 8 Jul 2018 11:10:52 +0900 Subject: [PATCH 09/21] Install preact-redux --- package-lock.json | 6 ++++++ package.json | 1 + 2 files changed, 7 insertions(+) diff --git a/package-lock.json b/package-lock.json index 04b506b..13dd81f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10878,6 +10878,12 @@ "integrity": "sha512-ThuGXBmJS3VsT+jIP+eQufD3L8pRw/PY3FoCys6O9Pu6aF12Pn9zAJDX99TfwRAFOCEKm/P0lwiPTbqKMJp0fA==", "dev": true }, + "preact-redux": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/preact-redux/-/preact-redux-2.0.3.tgz", + "integrity": "sha1-lgpTXDImQ801mY8z8MLme8Hn6qs=", + "dev": true + }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", diff --git a/package.json b/package.json index 57bd7f4..b5c23ff 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "node-firefox-connect": "^1.2.0", "node-sass": "^4.9.0", "preact": "^8.2.9", + "preact-redux": "^2.0.3", "redux": "^4.0.0", "redux-promise": "^0.6.0", "sass-loader": "^7.0.1", From f2ac75fff8e112bd14bc391c2b40a565a14c65f5 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sun, 8 Jul 2018 11:38:48 +0900 Subject: [PATCH 10/21] Use official redux on settings --- src/settings/index.jsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) 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'); From 96649fef63f467d45aefee93dfb599546151c21d Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sun, 8 Jul 2018 11:39:56 +0900 Subject: [PATCH 11/21] See you my redux --- src/shared/store/index.js | 53 --------------- src/shared/store/provider.jsx | 15 ----- test/shared/store/index.test.js | 110 -------------------------------- 3 files changed, 178 deletions(-) delete mode 100644 src/shared/store/index.js delete mode 100644 src/shared/store/provider.jsx delete mode 100644 test/shared/store/index.test.js 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
- { this.props.children } -
; - } -} - -export default Provider; diff --git a/test/shared/store/index.test.js b/test/shared/store/index.test.js deleted file mode 100644 index 5b69b40..0000000 --- a/test/shared/store/index.test.js +++ /dev/null @@ -1,110 +0,0 @@ -import { createStore } from 'shared/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 df890379ca3e41bc20a80fca98a4c3363aecffa9 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sun, 8 Jul 2018 17:25:34 +0900 Subject: [PATCH 12/21] Clean setting --- src/settings/actions/index.js | 3 + src/settings/actions/setting.js | 51 ++++++-- src/settings/components/index.jsx | 168 +++++++------------------ src/settings/reducers/setting.js | 23 +++- test/settings/reducers/setting.test.js | 42 ++++++- 5 files changed, 144 insertions(+), 143 deletions(-) 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
Keybindings this.bindForm('keymaps', value)} />
Search Engines this.bindForm('search', value)} />
Blacklist this.bindForm('blacklist', value)} />
@@ -71,33 +45,33 @@ class SettingsComponent extends Component { Properties this.bindForm('properties', value)} />
; } - renderJsonFields() { + renderJsonFields(json, error) { return
; } 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 (
@@ -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/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/test/settings/reducers/setting.test.js b/test/settings/reducers/setting.test.js index b9579cf..d800394 100644 --- a/test/settings/reducers/setting.test.js +++ b/test/settings/reducers/setting.test.js @@ -1,21 +1,55 @@ import actions from 'settings/actions'; import settingReducer from 'settings/reducers/setting'; -describe("setting reducer", () => { +describe("settings setting reducer", () => { it('return the initial state', () => { let state = settingReducer(undefined, {}); expect(state).to.have.deep.property('json', ''); - expect(state).to.have.deep.property('value', {}); + expect(state).to.have.deep.property('form', null); + expect(state).to.have.deep.property('error', ''); }); it('return next state for SETTING_SET_SETTINGS', () => { let action = { type: actions.SETTING_SET_SETTINGS, + source: 'json', json: '{ "key": "value" }', - value: { key: 123 }, + form: {}, }; let state = settingReducer(undefined, action); + expect(state).to.have.deep.property('source', 'json'); expect(state).to.have.deep.property('json', '{ "key": "value" }'); - expect(state).to.have.deep.property('value', { key: 123 }); + expect(state).to.have.deep.property('form', {}); + }); + + it('return next state for SETTING_SHOW_ERROR', () => { + let action = { + type: actions.SETTING_SHOW_ERROR, + text: 'bad value', + json: '{}', + }; + let state = settingReducer(undefined, action); + expect(state).to.have.deep.property('error', 'bad value'); + expect(state).to.have.deep.property('json', '{}'); + }); + + it('return next state for SETTING_SWITCH_TO_FORM', () => { + let action = { + type: actions.SETTING_SWITCH_TO_FORM, + form: {}, + }; + let state = settingReducer(undefined, action); + expect(state).to.have.deep.property('form', {}); + expect(state).to.have.deep.property('source', 'form'); + }); + + it('return next state for SETTING_SWITCH_TO_JSON', () => { + let action = { + type: actions.SETTING_SWITCH_TO_JSON, + json: '{}', + }; + let state = settingReducer(undefined, action); + expect(state).to.have.deep.property('json', '{}'); + expect(state).to.have.deep.property('source', 'json'); }); }); From 43beccfe0f323e2363fe97bdb6bc0d71558fda47 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sun, 8 Jul 2018 21:06:49 +0900 Subject: [PATCH 13/21] Fix command --- src/background/components/operation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/background/components/operation.js b/src/background/components/operation.js index 0fa3cf8..ce93270 100644 --- a/src/background/components/operation.js +++ b/src/background/components/operation.js @@ -100,7 +100,7 @@ export default class BackgroundComponent { case operations.COMMAND_SHOW_WINOPEN: if (operation.alter) { // alter url - return consoleActions.showCommand(tab, 'win ' + tab.url); + return consoleActions.showCommand(tab, 'winopen ' + tab.url); } return consoleActions.showCommand(tab, 'winopen '); case operations.COMMAND_SHOW_BUFFER: From b69cc04856fd21d325193d56e212e4dbf07cb762 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Wed, 11 Jul 2018 21:01:22 +0900 Subject: [PATCH 14/21] Use combineReducers --- src/background/reducers/index.js | 23 +++++++--------------- src/content/reducers/index.js | 33 +++++++++----------------------- 2 files changed, 16 insertions(+), 40 deletions(-) diff --git a/src/background/reducers/index.js b/src/background/reducers/index.js index 78f855c..465f927 100644 --- a/src/background/reducers/index.js +++ b/src/background/reducers/index.js @@ -1,17 +1,8 @@ -import settingReducer from './setting'; -import findReducer from './find'; -import tabReducer from './tab'; +import { combineReducers } from 'redux'; +import setting from './setting'; +import find from './find'; +import tab from './tab'; -// Make setting reducer instead of re-use -const defaultState = { - setting: settingReducer(undefined, {}), - find: findReducer(undefined, {}), - tab: tabReducer(undefined, {}), -}; - -export default function reducer(state = defaultState, action = {}) { - return { ...state, - setting: settingReducer(state.setting, action), - find: findReducer(state.find, action), - tab: tabReducer(state.tab, action), }; -} +export default combineReducers({ + setting, find, tab, +}); diff --git a/src/content/reducers/index.js b/src/content/reducers/index.js index c3a474e..6e6a147 100644 --- a/src/content/reducers/index.js +++ b/src/content/reducers/index.js @@ -1,25 +1,10 @@ -import addonReducer from './addon'; -import findReducer from './find'; -import settingReducer from './setting'; -import inputReducer from './input'; -import followControllerReducer from './follow-controller'; +import { combineReducers } from 'redux'; +import addon from './addon'; +import find from './find'; +import setting from './setting'; +import input from './input'; +import followController from './follow-controller'; -// Make setting reducer instead of re-use -const defaultState = { - addon: addonReducer(undefined, {}), - find: findReducer(undefined, {}), - setting: settingReducer(undefined, {}), - input: inputReducer(undefined, {}), - followController: followControllerReducer(undefined, {}), -}; - -export default function reducer(state = defaultState, action = {}) { - return { - ...state, - addon: addonReducer(state.addon, action), - find: findReducer(state.find, action), - setting: settingReducer(state.setting, action), - input: inputReducer(state.input, action), - followController: followControllerReducer(state.followController, action), - }; -} +export default combineReducers({ + addon, find, setting, input, followController, +}); From 067da88d06fbffca323ecdbaf8b1011f88225219 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Wed, 11 Jul 2018 21:01:48 +0900 Subject: [PATCH 15/21] Move versions to background --- src/background/index.js | 2 +- src/{ => background}/shared/versions/index.js | 2 +- src/{ => background}/shared/versions/release-notes.js | 0 src/{ => background}/shared/versions/storage.js | 0 test/{ => background}/shared/versions/index.test.js | 4 ++-- test/{ => background}/shared/versions/storage.test.js | 2 +- 6 files changed, 5 insertions(+), 5 deletions(-) rename src/{ => background}/shared/versions/index.js (95%) rename src/{ => background}/shared/versions/release-notes.js (100%) rename src/{ => background}/shared/versions/storage.js (100%) rename test/{ => background}/shared/versions/index.test.js (91%) rename test/{ => background}/shared/versions/storage.test.js (92%) diff --git a/src/background/index.js b/src/background/index.js index 8c4eafc..1e4c078 100644 --- a/src/background/index.js +++ b/src/background/index.js @@ -6,7 +6,7 @@ import IndicatorComponent from 'background/components/indicator'; import reducers from 'background/reducers'; import { createStore, applyMiddleware } from 'redux'; import promise from 'redux-promise'; -import * as versions from 'shared/versions'; +import * as versions from './shared/versions'; const store = createStore( reducers, diff --git a/src/shared/versions/index.js b/src/background/shared/versions/index.js similarity index 95% rename from src/shared/versions/index.js rename to src/background/shared/versions/index.js index ba3d183..aa09c92 100644 --- a/src/shared/versions/index.js +++ b/src/background/shared/versions/index.js @@ -1,6 +1,6 @@ import * as storage from './storage'; import * as releaseNotes from './release-notes'; -import manifest from '../../../manifest.json'; +import manifest from '../../../../manifest.json'; const NOTIFICATION_ID = 'vimvixen-update'; diff --git a/src/shared/versions/release-notes.js b/src/background/shared/versions/release-notes.js similarity index 100% rename from src/shared/versions/release-notes.js rename to src/background/shared/versions/release-notes.js diff --git a/src/shared/versions/storage.js b/src/background/shared/versions/storage.js similarity index 100% rename from src/shared/versions/storage.js rename to src/background/shared/versions/storage.js diff --git a/test/shared/versions/index.test.js b/test/background/shared/versions/index.test.js similarity index 91% rename from test/shared/versions/index.test.js rename to test/background/shared/versions/index.test.js index d90f04c..d65dd9a 100644 --- a/test/shared/versions/index.test.js +++ b/test/background/shared/versions/index.test.js @@ -1,5 +1,5 @@ -import * as versions from 'shared/versions'; -import manifest from '../../../manifest.json'; +import * as versions from 'background/shared/versions'; +import manifest from '../../../../manifest.json'; describe("shared/versions/storage", () => { describe('#checkUpdated', () => { diff --git a/test/shared/versions/storage.test.js b/test/background/shared/versions/storage.test.js similarity index 92% rename from test/shared/versions/storage.test.js rename to test/background/shared/versions/storage.test.js index f541abf..f452516 100644 --- a/test/shared/versions/storage.test.js +++ b/test/background/shared/versions/storage.test.js @@ -1,4 +1,4 @@ -import * as storage from 'shared/versions/storage'; +import * as storage from 'background/shared/versions/storage'; describe("shared/versions/storage", () => { describe('#load', () => { From a28f6f916d77baf87e3c023abbd6494e009a8d12 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Wed, 11 Jul 2018 21:32:28 +0900 Subject: [PATCH 16/21] Complete console commands --- src/background/shared/completions/index.js | 41 +++++++++++++--------- src/shared/commands/docs.js | 11 ++++++ 2 files changed, 36 insertions(+), 16 deletions(-) create mode 100644 src/shared/commands/docs.js diff --git a/src/background/shared/completions/index.js b/src/background/shared/completions/index.js index d5875fe..22713e7 100644 --- a/src/background/shared/completions/index.js +++ b/src/background/shared/completions/index.js @@ -1,7 +1,19 @@ +import commandDocs from 'shared/commands/docs'; import * as tabs from './tabs'; import * as histories from './histories'; import * as bookmarks from './bookmarks'; +const completeCommands = (typing) => { + let keys = Object.keys(commandDocs); + return keys + .filter(name => name.startsWith(typing)) + .map(name => ({ + caption: name, + content: name, + url: commandDocs[name], + })); +}; + const getSearchCompletions = (command, keywords, searchConfig) => { let engineNames = Object.keys(searchConfig.engines); let engineItems = engineNames.filter(name => name.startsWith(keywords)) @@ -74,20 +86,21 @@ const getBufferCompletions = async(command, keywords, excludePinned) => { ]; }; -const getCompletions = (line, settings) => { - let typedWords = line.trim().split(/ +/); - let typing = ''; - if (!line.endsWith(' ')) { - typing = typedWords.pop(); - } - - if (typedWords.length === 0) { - return Promise.resolve([]); +const complete = (line, settings) => { + let trimmed = line.trimStart(); + let words = trimmed.split(/ +/); + let name = words[0]; + if (words.length === 1) { + return Promise.resolve([ + { + name: 'Console Command', + items: completeCommands(name), + } + ]); } - let name = typedWords.shift(); - let keywords = typedWords.concat(typing).join(' '); + let keywords = trimmed.slice(name.length).trimStart(); - switch (name) { + switch (words[0]) { case 'o': case 'open': case 't': @@ -112,8 +125,4 @@ const getCompletions = (line, settings) => { return Promise.resolve([]); }; -const complete = (line, settings) => { - return getCompletions(line, settings); -}; - export { complete }; diff --git a/src/shared/commands/docs.js b/src/shared/commands/docs.js new file mode 100644 index 0000000..c73eb71 --- /dev/null +++ b/src/shared/commands/docs.js @@ -0,0 +1,11 @@ +export default { + set: 'Set a value of the property', + open: 'Open a URL or search by keywords in current tab', + tabopen: 'Open a URL or search by keywords in new tab', + winopen: 'Open a URL or search by keywords in new window', + buffer: 'Sekect tabs by matched keywords', + bdelete: 'Close a certain tab matched by keywords', + bdeletes: 'Close all tabs matched by keywords', + quit: 'Close the current tab', + quitall: 'Close all tabs', +}; From 1e39fed6183bf3b10f48eb52868bb5ab3fe3134f Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Wed, 11 Jul 2018 21:58:41 +0900 Subject: [PATCH 17/21] Complete set commands --- src/background/shared/completions/index.js | 37 ++++++++++++++++++++++ src/shared/settings/properties.js | 8 ++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/background/shared/completions/index.js b/src/background/shared/completions/index.js index 22713e7..d630f33 100644 --- a/src/background/shared/completions/index.js +++ b/src/background/shared/completions/index.js @@ -2,6 +2,7 @@ import commandDocs from 'shared/commands/docs'; import * as tabs from './tabs'; import * as histories from './histories'; import * as bookmarks from './bookmarks'; +import * as properties from 'shared/settings/properties'; const completeCommands = (typing) => { let keys = Object.keys(commandDocs); @@ -86,6 +87,40 @@ const getBufferCompletions = async(command, keywords, excludePinned) => { ]; }; +const getSetCompletions = (command, keywords) => { + let keys = Object.keys(properties.docs).filter( + name => name.startsWith(keywords) + ); + let items = keys.map((key) => { + if (properties.types[key] === 'boolean') { + return [ + { + caption: key, + content: command + ' ' + key, + url: 'Enable ' + properties.docs[key], + }, { + caption: 'no' + key, + content: command + ' no' + key, + url: 'Disable ' + properties.docs[key], + } + ]; + } + return [ + { + caption: key, + content: command + ' ' + key, + url: 'Set ' + properties.docs[key], + } + ]; + }).flat(); + return Promise.resolve([ + { + name: 'Properties', + items, + } + ]); +}; + const complete = (line, settings) => { let trimmed = line.trimStart(); let words = trimmed.split(/ +/); @@ -121,6 +156,8 @@ const complete = (line, settings) => { case 'bdelete': case 'bdeletes': return getBufferCompletions(name, keywords, true); + case 'set': + return getSetCompletions(name, keywords); } return Promise.resolve([]); }; diff --git a/src/shared/settings/properties.js b/src/shared/settings/properties.js index 4bda8d6..b392cbb 100644 --- a/src/shared/settings/properties.js +++ b/src/shared/settings/properties.js @@ -15,4 +15,10 @@ const defaults = { adjacenttab: true, }; -export { types, defaults }; +const docs = { + hintchars: 'Hint characters on follow mode', + smoothscroll: 'smooth scroll', + adjacenttab: 'open adjacent tabs', +}; + +export { types, defaults, docs }; From f555e1348c8e383983487a79f797fe06540862f4 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Fri, 13 Jul 2018 21:49:34 +0900 Subject: [PATCH 18/21] Fix command and property completions --- src/background/shared/completions/index.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/background/shared/completions/index.js b/src/background/shared/completions/index.js index d630f33..4fd37fd 100644 --- a/src/background/shared/completions/index.js +++ b/src/background/shared/completions/index.js @@ -113,6 +113,9 @@ const getSetCompletions = (command, keywords) => { } ]; }).flat(); + if (items.length === 0) { + return Promise.resolve([]); + } return Promise.resolve([ { name: 'Properties', @@ -126,6 +129,10 @@ const complete = (line, settings) => { let words = trimmed.split(/ +/); let name = words[0]; if (words.length === 1) { + let items = completeCommands(name); + if (items.length === 0) { + return Promise.resolve([]); + } return Promise.resolve([ { name: 'Console Command', From 8db779388f260631286332aec6cb6a448a29d791 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Fri, 13 Jul 2018 21:59:23 +0900 Subject: [PATCH 19/21] Fix error on no completion items --- src/console/reducers/index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/console/reducers/index.js b/src/console/reducers/index.js index 71b0776..043689c 100644 --- a/src/console/reducers/index.js +++ b/src/console/reducers/index.js @@ -11,6 +11,9 @@ const defaultState = { }; const nextSelection = (state) => { + if (state.completions.length === 0) { + return [-1, -1]; + }; if (state.groupSelection < 0) { return [0, 0]; } From ccc6a31ddeab78660305d9088e8260156b251779 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Fri, 13 Jul 2018 22:09:04 +0900 Subject: [PATCH 20/21] fix --- src/background/shared/completions/index.js | 2 +- src/console/reducers/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/background/shared/completions/index.js b/src/background/shared/completions/index.js index 4fd37fd..d0d00ef 100644 --- a/src/background/shared/completions/index.js +++ b/src/background/shared/completions/index.js @@ -130,7 +130,7 @@ const complete = (line, settings) => { let name = words[0]; if (words.length === 1) { let items = completeCommands(name); - if (items.length === 0) { + if (items.length === 0) { return Promise.resolve([]); } return Promise.resolve([ diff --git a/src/console/reducers/index.js b/src/console/reducers/index.js index 043689c..7dcad17 100644 --- a/src/console/reducers/index.js +++ b/src/console/reducers/index.js @@ -13,7 +13,7 @@ const defaultState = { const nextSelection = (state) => { if (state.completions.length === 0) { return [-1, -1]; - }; + } if (state.groupSelection < 0) { return [0, 0]; } From 803e6ea7af60107182356b5fda8e3c2ddfacaefa Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Fri, 13 Jul 2018 22:14:19 +0900 Subject: [PATCH 21/21] Replace flat with reduce-concat --- src/background/shared/completions/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/background/shared/completions/index.js b/src/background/shared/completions/index.js index d0d00ef..9ca13f7 100644 --- a/src/background/shared/completions/index.js +++ b/src/background/shared/completions/index.js @@ -112,7 +112,8 @@ const getSetCompletions = (command, keywords) => { url: 'Set ' + properties.docs[key], } ]; - }).flat(); + }); + items = items.reduce((acc, val) => acc.concat(val), []); if (items.length === 0) { return Promise.resolve([]); }