commit
ccc81312a1
14 changed files with 126 additions and 27 deletions
|
@ -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',
|
||||
|
|
|
@ -13,11 +13,4 @@ const clearKeys = () => {
|
|||
};
|
||||
};
|
||||
|
||||
const setKeymaps = (keymaps) => {
|
||||
return {
|
||||
type: actions.INPUT_SET_KEYMAPS,
|
||||
keymaps,
|
||||
};
|
||||
};
|
||||
|
||||
export { keyPress, clearKeys, setKeymaps };
|
||||
export { keyPress, clearKeys };
|
||||
|
|
10
src/content/actions/setting.js
Normal file
10
src/content/actions/setting.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
import actions from 'content/actions';
|
||||
|
||||
const set = (value) => {
|
||||
return {
|
||||
type: actions.SETTING_SET,
|
||||
value,
|
||||
};
|
||||
};
|
||||
|
||||
export { set };
|
|
@ -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));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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),
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
13
src/content/reducers/setting.js
Normal file
13
src/content/reducers/setting.js
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
6
src/shared/utils/re.js
Normal file
6
src/shared/utils/re.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
const fromWildcard = (pattern) => {
|
||||
let regexStr = '^' + pattern.replace(/\*/g, '.*') + '$';
|
||||
return new RegExp(regexStr);
|
||||
};
|
||||
|
||||
export { fromWildcard };
|
|
@ -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];
|
||||
});
|
||||
|
|
13
test/content/actions/setting.test.js
Normal file
13
test/content/actions/setting.test.js
Normal file
|
@ -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' });
|
||||
});
|
||||
});
|
||||
});
|
18
test/content/reducers/setting.test.js
Normal file
18
test/content/reducers/setting.test.js
Normal file
|
@ -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
|
||||
});
|
||||
});
|
20
test/shared/util/re.test.js
Normal file
20
test/shared/util/re.test.js
Normal file
|
@ -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);
|
||||
})
|
||||
});
|
Reference in a new issue