From c6eb5553d0477f96b5edd48012b4c4ab342f026c Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sun, 22 Oct 2017 17:45:16 +0900 Subject: [PATCH 1/2] add setting actions in content --- src/content/actions/index.js | 7 +++++-- src/content/actions/input.js | 9 +-------- src/content/actions/setting.js | 10 ++++++++++ src/content/components/common/index.js | 4 ++-- src/content/components/common/keymapper.js | 15 ++++++++------- src/content/reducers/index.js | 3 +++ src/content/reducers/input.js | 7 +------ src/content/reducers/setting.js | 13 +++++++++++++ test/content/actions/setting.test.js | 13 +++++++++++++ test/content/reducers/setting.test.js | 18 ++++++++++++++++++ 10 files changed, 74 insertions(+), 25 deletions(-) create mode 100644 src/content/actions/setting.js create mode 100644 src/content/reducers/setting.js create mode 100644 test/content/actions/setting.test.js create mode 100644 test/content/reducers/setting.test.js diff --git a/src/content/actions/index.js b/src/content/actions/index.js index 085d510..83fa7cf 100644 --- a/src/content/actions/index.js +++ b/src/content/actions/index.js @@ -1,12 +1,15 @@ export default { - // User input + // Enable/disable ADDON_ENABLE: 'addon.enable', ADDON_DISABLE: 'addon.disable', ADDON_TOGGLE_ENABLED: 'addon.toggle.enabled', + // Settings + SETTING_SET: 'setting.set', + + // User input INPUT_KEY_PRESS: 'input.key,press', INPUT_CLEAR_KEYS: 'input.clear.keys', - INPUT_SET_KEYMAPS: 'input.set.keymaps', // Completion COMPLETION_SET_ITEMS: 'completion.set.items', diff --git a/src/content/actions/input.js b/src/content/actions/input.js index 31cfee3..465a486 100644 --- a/src/content/actions/input.js +++ b/src/content/actions/input.js @@ -13,11 +13,4 @@ const clearKeys = () => { }; }; -const setKeymaps = (keymaps) => { - return { - type: actions.INPUT_SET_KEYMAPS, - keymaps, - }; -}; - -export { keyPress, clearKeys, setKeymaps }; +export { keyPress, clearKeys }; diff --git a/src/content/actions/setting.js b/src/content/actions/setting.js new file mode 100644 index 0000000..c874294 --- /dev/null +++ b/src/content/actions/setting.js @@ -0,0 +1,10 @@ +import actions from 'content/actions'; + +const set = (value) => { + return { + type: actions.SETTING_SET, + value, + }; +}; + +export { set }; diff --git a/src/content/components/common/index.js b/src/content/components/common/index.js index db0ac43..5f5531b 100644 --- a/src/content/components/common/index.js +++ b/src/content/components/common/index.js @@ -1,7 +1,7 @@ import InputComponent from './input'; import KeymapperComponent from './keymapper'; import FollowComponent from './follow'; -import * as inputActions from 'content/actions/input'; +import * as settingActions from 'content/actions/setting'; import messages from 'shared/messages'; export default class Common { @@ -40,7 +40,7 @@ export default class Common { browser.runtime.sendMessage({ type: messages.SETTINGS_QUERY, }).then((settings) => { - this.store.dispatch(inputActions.setKeymaps(settings.keymaps)); + this.store.dispatch(settingActions.set(settings)); }); } } diff --git a/src/content/components/common/keymapper.js b/src/content/components/common/keymapper.js index 5070cd8..44d8212 100644 --- a/src/content/components/common/keymapper.js +++ b/src/content/components/common/keymapper.js @@ -11,19 +11,20 @@ export default class KeymapperComponent { } key(key) { - let enabled = this.store.getState().addon.enabled; - this.store.dispatch(inputActions.keyPress(key)); - let input = this.store.getState().input; - let matched = Object.keys(input.keymaps).filter((keyStr) => { + let state = this.store.getState(); + let input = state.input; + let keymaps = state.setting.keymaps; + + let matched = Object.keys(keymaps).filter((keyStr) => { return keyStr.startsWith(input.keys); }); - if (!enabled) { + if (!state.addon.enabled) { // available keymaps are only ADDON_ENABLE and ADDON_TOGGLE_ENABLED if // the addon disabled matched = matched.filter((keys) => { - let type = input.keymaps[keys].type; + let type = keymaps[keys].type; return type === operations.ADDON_ENABLE || type === operations.ADDON_TOGGLE_ENABLED; }); @@ -35,7 +36,7 @@ export default class KeymapperComponent { matched.length === 1 && input.keys !== matched[0]) { return true; } - let operation = input.keymaps[matched]; + let operation = keymaps[matched]; this.store.dispatch(operationActions.exec(operation)); this.store.dispatch(inputActions.clearKeys()); return true; diff --git a/src/content/reducers/index.js b/src/content/reducers/index.js index 7cf318e..ae4a845 100644 --- a/src/content/reducers/index.js +++ b/src/content/reducers/index.js @@ -1,10 +1,12 @@ import addonReducer from './addon'; +import settingReducer from './setting'; import inputReducer from './input'; import followReducer from './follow'; // Make setting reducer instead of re-use const defaultState = { addon: addonReducer(undefined, {}), + setting: settingReducer(undefined, {}), input: inputReducer(undefined, {}), follow: followReducer(undefined, {}), }; @@ -12,6 +14,7 @@ const defaultState = { export default function reducer(state = defaultState, action = {}) { return Object.assign({}, state, { addon: addonReducer(state.addon, action), + setting: settingReducer(state.setting, action), input: inputReducer(state.input, action), follow: followReducer(state.follow, action), }); diff --git a/src/content/reducers/input.js b/src/content/reducers/input.js index c79b206..9457604 100644 --- a/src/content/reducers/input.js +++ b/src/content/reducers/input.js @@ -1,8 +1,7 @@ import actions from 'content/actions'; const defaultState = { - keys: '', - keymaps: {}, + keys: '' }; export default function reducer(state = defaultState, action = {}) { @@ -15,10 +14,6 @@ export default function reducer(state = defaultState, action = {}) { return Object.assign({}, state, { keys: '', }); - case actions.INPUT_SET_KEYMAPS: - return Object.assign({}, state, { - keymaps: action.keymaps, - }); default: return state; } diff --git a/src/content/reducers/setting.js b/src/content/reducers/setting.js new file mode 100644 index 0000000..22fac6f --- /dev/null +++ b/src/content/reducers/setting.js @@ -0,0 +1,13 @@ +import actions from 'content/actions'; + +const defaultState = {}; + +export default function reducer(state = defaultState, action = {}) { + switch (action.type) { + case actions.SETTING_SET: + return Object.assign({}, action.value); + default: + return state; + } +} + diff --git a/test/content/actions/setting.test.js b/test/content/actions/setting.test.js new file mode 100644 index 0000000..8855f04 --- /dev/null +++ b/test/content/actions/setting.test.js @@ -0,0 +1,13 @@ +import { expect } from "chai"; +import actions from 'content/actions'; +import * as settingActions from 'content/actions/setting'; + +describe("setting actions", () => { + describe("set", () => { + it('create SETTING_SET action', () => { + let action = settingActions.set({ red: 'apple', yellow: 'banana' }); + expect(action.type).to.equal(actions.SETTING_SET); + expect(action.value).to.deep.equal({ red: 'apple', yellow: 'banana' }); + }); + }); +}); diff --git a/test/content/reducers/setting.test.js b/test/content/reducers/setting.test.js new file mode 100644 index 0000000..50d1cf0 --- /dev/null +++ b/test/content/reducers/setting.test.js @@ -0,0 +1,18 @@ +import { expect } from "chai"; +import actions from 'content/actions'; +import settingReducer from 'content/reducers/setting'; + +describe("content setting reducer", () => { + it('return the initial state', () => { + let state = settingReducer(undefined, {}); + expect(state).to.deep.equal({}); + }); + + it('return next state for SETTING_SET', () => { + let newSettings = { red: 'apple', yellow: 'banana' }; + let action = { type: actions.SETTING_SET, value: newSettings }; + let state = settingReducer(undefined, action); + expect(state).to.deep.equal(newSettings); + expect(state).not.to.equal(newSettings); // assert deep copy + }); +}); From b9f2668ceab3d786ce3e587803ff30ec5c781ee3 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sun, 22 Oct 2017 22:17:00 +0900 Subject: [PATCH 2/2] add blacklist setting --- src/content/components/top-content/index.js | 26 ++++++++++++++++++++- src/shared/utils/re.js | 6 +++++ src/shared/validators/setting.js | 2 +- test/shared/util/re.test.js | 20 ++++++++++++++++ 4 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 src/shared/utils/re.js create mode 100644 test/shared/util/re.test.js diff --git a/src/content/components/top-content/index.js b/src/content/components/top-content/index.js index c318901..c4a8461 100644 --- a/src/content/components/top-content/index.js +++ b/src/content/components/top-content/index.js @@ -1,7 +1,9 @@ import CommonComponent from '../common'; import FollowController from './follow-controller'; 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 { @@ -11,17 +13,39 @@ export default class TopContent { new CommonComponent(win, store), new FollowController(win, store), ]; + this.store = store; + this.prevBlacklist = undefined; // TODO make component - consoleFrames.initialize(window.document); + consoleFrames.initialize(this.win.document); messages.onMessage(this.onMessage.bind(this)); } update() { + let blacklist = this.store.getState().setting.blacklist; + if (JSON.stringify(this.prevBlacklist) !== JSON.stringify(blacklist)) { + this.disableIfBlack(blacklist); + this.prevBlacklist = blacklist; + } + this.children.forEach(c => c.update()); } + 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()); + } + } + onMessage(message) { switch (message.type) { case messages.CONSOLE_HIDE_COMMAND: diff --git a/src/shared/utils/re.js b/src/shared/utils/re.js new file mode 100644 index 0000000..7db9091 --- /dev/null +++ b/src/shared/utils/re.js @@ -0,0 +1,6 @@ +const fromWildcard = (pattern) => { + let regexStr = '^' + pattern.replace(/\*/g, '.*') + '$'; + return new RegExp(regexStr); +}; + +export { fromWildcard }; diff --git a/src/shared/validators/setting.js b/src/shared/validators/setting.js index 5039ec2..949ab29 100644 --- a/src/shared/validators/setting.js +++ b/src/shared/validators/setting.js @@ -1,6 +1,6 @@ import operations from 'shared/operations'; -const VALID_TOP_KEYS = ['keymaps', 'search']; +const VALID_TOP_KEYS = ['keymaps', 'search', 'blacklist']; const VALID_OPERATION_VALUES = Object.keys(operations).map((key) => { return operations[key]; }); diff --git a/test/shared/util/re.test.js b/test/shared/util/re.test.js new file mode 100644 index 0000000..9ed6521 --- /dev/null +++ b/test/shared/util/re.test.js @@ -0,0 +1,20 @@ +import { expect } from 'chai'; +import * as re from 'shared/utils/re'; + +describe("re util", () => { + it('matches by pattern', () => { + let regex = re.fromWildcard('*.example.com/*'); + expect('foo.example.com/bar').to.match(regex); + expect('foo.example.com').not.to.match(regex); + expect('example.com/bar').not.to.match(regex); + + regex = re.fromWildcard('example.com/*') + expect('example.com/foo').to.match(regex); + expect('example.com/').to.match(regex); + + regex = re.fromWildcard('example.com/*bar') + expect('example.com/foobar').to.match(regex); + expect('example.com/bar').to.match(regex); + expect('example.com/foobarfoo').not.to.match(regex); + }) +});