Merge pull request #86 from ueokande/10-disable-temporary

Disable add-on temporary
jh-changes
Shin'ya Ueoka 7 years ago committed by GitHub
commit 7639e99b75
  1. 1
      README.md
  2. 15
      src/content/actions/addon.js
  3. 4
      src/content/actions/index.js
  4. 11
      src/content/actions/input.js
  5. 7
      src/content/actions/operation.js
  6. 4
      src/content/components/common/index.js
  7. 29
      src/content/components/common/input.js
  8. 16
      src/content/components/common/keymapper.js
  9. 24
      src/content/reducers/addon.js
  10. 3
      src/content/reducers/index.js
  11. 3
      src/shared/default-settings.js
  12. 5
      src/shared/operations.js
  13. 26
      test/content/actions/addon.test.js
  14. 8
      test/content/actions/input.test.js
  15. 102
      test/content/components/common/input.test.js
  16. 38
      test/content/reducers/addon.test.js

@ -47,6 +47,7 @@ The default mapping are shown in the following.
- <kbd>z</kbd><kbd>i</kbd>, <kbd>z</kbd><kbd>o</kbd>: zoom-in/zoom-out - <kbd>z</kbd><kbd>i</kbd>, <kbd>z</kbd><kbd>o</kbd>: zoom-in/zoom-out
- <kbd>z</kbd><kbd>z</kbd>: Set default zoom level - <kbd>z</kbd><kbd>z</kbd>: Set default zoom level
- <kbd>y</kbd>: copy URL in current tab - <kbd>y</kbd>: copy URL in current tab
- <kbd>Shift</kbd>+<kbd>Esc</kbd>: enable of disable the add-on in current tab.
### Console commands ### Console commands

@ -0,0 +1,15 @@
import actions from 'content/actions';
const enable = () => {
return { type: actions.ADDON_ENABLE };
};
const disable = () => {
return { type: actions.ADDON_DISABLE };
};
const toggleEnabled = () => {
return { type: actions.ADDON_TOGGLE_ENABLED };
};
export { enable, disable, toggleEnabled };

@ -1,5 +1,9 @@
export default { export default {
// User input // User input
ADDON_ENABLE: 'addon.enable',
ADDON_DISABLE: 'addon.disable',
ADDON_TOGGLE_ENABLED: 'addon.toggle.enabled',
INPUT_KEY_PRESS: 'input.key,press', INPUT_KEY_PRESS: 'input.key,press',
INPUT_CLEAR_KEYS: 'input.clear.keys', INPUT_CLEAR_KEYS: 'input.clear.keys',
INPUT_SET_KEYMAPS: 'input.set.keymaps', INPUT_SET_KEYMAPS: 'input.set.keymaps',

@ -1,16 +1,9 @@
import actions from 'content/actions'; import actions from 'content/actions';
const asKeymapChars = (key, ctrl) => { const keyPress = (key) => {
if (ctrl) {
return '<C-' + key.toUpperCase() + '>';
}
return key;
};
const keyPress = (key, ctrl) => {
return { return {
type: actions.INPUT_KEY_PRESS, type: actions.INPUT_KEY_PRESS,
key: asKeymapChars(key, ctrl), key,
}; };
}; };

@ -4,9 +4,16 @@ import * as scrolls from 'content/scrolls';
import * as navigates from 'content/navigates'; import * as navigates from 'content/navigates';
import * as urls from 'content/urls'; import * as urls from 'content/urls';
import * as consoleFrames from 'content/console-frames'; import * as consoleFrames from 'content/console-frames';
import * as addonActions from './addon';
const exec = (operation) => { const exec = (operation) => {
switch (operation.type) { switch (operation.type) {
case operations.ADDON_ENABLE:
return addonActions.enable();
case operations.ADDON_DISABLE:
return addonActions.disable();
case operations.ADDON_TOGGLE_ENABLED:
return addonActions.toggleEnabled();
case operations.SCROLL_VERTICALLY: case operations.SCROLL_VERTICALLY:
return scrolls.scrollVertically(window, operation.count); return scrolls.scrollVertically(window, operation.count);
case operations.SCROLL_HORIZONALLY: case operations.SCROLL_HORIZONALLY:

@ -10,8 +10,8 @@ export default class Common {
const input = new InputComponent(win.document.body, store); const input = new InputComponent(win.document.body, store);
const keymapper = new KeymapperComponent(store); const keymapper = new KeymapperComponent(store);
input.onKey((key, ctrl) => follow.key(key, ctrl)); input.onKey(key => follow.key(key));
input.onKey((key, ctrl) => keymapper.key(key, ctrl)); input.onKey(key => keymapper.key(key));
this.store = store; this.store = store;
this.children = [ this.children = [

@ -1,3 +1,21 @@
const modifierdKeyName = (name) => {
if (name.length === 1) {
return name.toUpperCase();
} else if (name === 'Escape') {
return 'Esc';
}
return name;
};
const mapKey = (e) => {
if (e.ctrlKey) {
return '<C-' + modifierdKeyName(e.key) + '>';
} else if (e.shiftKey && e.key.length !== 1) {
return '<S-' + modifierdKeyName(e.key) + '>';
}
return e.key;
};
export default class InputComponent { export default class InputComponent {
constructor(target) { constructor(target) {
this.pressed = {}; this.pressed = {};
@ -47,16 +65,15 @@ export default class InputComponent {
return; return;
} }
let stop = false; let key = mapKey(e);
for (let listener of this.onKeyListeners) { for (let listener of this.onKeyListeners) {
stop = stop || listener(e.key, e.ctrlKey); let stop = listener(key);
if (stop) {
break;
}
}
if (stop) { if (stop) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
break;
}
} }
} }

@ -1,5 +1,6 @@
import * as inputActions from 'content/actions/input'; import * as inputActions from 'content/actions/input';
import * as operationActions from 'content/actions/operation'; import * as operationActions from 'content/actions/operation';
import operations from 'shared/operations';
export default class KeymapperComponent { export default class KeymapperComponent {
constructor(store) { constructor(store) {
@ -9,13 +10,24 @@ export default class KeymapperComponent {
update() { update() {
} }
key(key, ctrl) { key(key) {
this.store.dispatch(inputActions.keyPress(key, ctrl)); let enabled = this.store.getState().addon.enabled;
this.store.dispatch(inputActions.keyPress(key));
let input = this.store.getState().input; let input = this.store.getState().input;
let matched = Object.keys(input.keymaps).filter((keyStr) => { let matched = Object.keys(input.keymaps).filter((keyStr) => {
return keyStr.startsWith(input.keys); return keyStr.startsWith(input.keys);
}); });
if (!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;
return type === operations.ADDON_ENABLE ||
type === operations.ADDON_TOGGLE_ENABLED;
});
}
if (matched.length === 0) { if (matched.length === 0) {
this.store.dispatch(inputActions.clearKeys()); this.store.dispatch(inputActions.clearKeys());
return false; return false;

@ -0,0 +1,24 @@
import actions from 'content/actions';
const defaultState = {
enabled: true,
};
export default function reducer(state = defaultState, action = {}) {
switch (action.type) {
case actions.ADDON_ENABLE:
return Object.assign({}, state, {
enabled: true,
});
case actions.ADDON_DISABLE:
return Object.assign({}, state, {
enabled: false,
});
case actions.ADDON_TOGGLE_ENABLED:
return Object.assign({}, state, {
enabled: !state.enabled,
});
default:
return state;
}
}

@ -1,14 +1,17 @@
import addonReducer from './addon';
import inputReducer from './input'; import inputReducer from './input';
import followReducer from './follow'; import followReducer from './follow';
// Make setting reducer instead of re-use // Make setting reducer instead of re-use
const defaultState = { const defaultState = {
addon: addonReducer(undefined, {}),
input: inputReducer(undefined, {}), input: inputReducer(undefined, {}),
follow: followReducer(undefined, {}), follow: followReducer(undefined, {}),
}; };
export default function reducer(state = defaultState, action = {}) { export default function reducer(state = defaultState, action = {}) {
return Object.assign({}, state, { return Object.assign({}, state, {
addon: addonReducer(state.addon, action),
input: inputReducer(state.input, action), input: inputReducer(state.input, action),
follow: followReducer(state.follow, action), follow: followReducer(state.follow, action),
}); });

@ -41,7 +41,8 @@ export default {
"]]": { "type": "navigate.link.next" }, "]]": { "type": "navigate.link.next" },
"gu": { "type": "navigate.parent" }, "gu": { "type": "navigate.parent" },
"gU": { "type": "navigate.root" }, "gU": { "type": "navigate.root" },
"y": { "type": "urls.yank" } "y": { "type": "urls.yank" },
"<S-Esc>": { "type": "addon.toggle.enabled" }
}, },
"search": { "search": {
"default": "google", "default": "google",

@ -1,4 +1,9 @@
export default { export default {
// Addons
ADDON_ENABLE: 'addon.enable',
ADDON_DISABLE: 'addon.disable',
ADDON_TOGGLE_ENABLED: 'addon.toggle.enabled',
// Command // Command
COMMAND_SHOW: 'command.show', COMMAND_SHOW: 'command.show',
COMMAND_SHOW_OPEN: 'command.show.open', COMMAND_SHOW_OPEN: 'command.show.open',

@ -0,0 +1,26 @@
import { expect } from "chai";
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);
});
});
});

@ -5,16 +5,10 @@ import * as inputActions from 'content/actions/input';
describe("input actions", () => { describe("input actions", () => {
describe("keyPress", () => { describe("keyPress", () => {
it('create INPUT_KEY_PRESS action', () => { it('create INPUT_KEY_PRESS action', () => {
let action = inputActions.keyPress('a', false); let action = inputActions.keyPress('a');
expect(action.type).to.equal(actions.INPUT_KEY_PRESS); expect(action.type).to.equal(actions.INPUT_KEY_PRESS);
expect(action.key).to.equal('a'); expect(action.key).to.equal('a');
}); });
it('create INPUT_KEY_PRESS action from key with ctrl', () => {
let action = inputActions.keyPress('b', true);
expect(action.type).to.equal(actions.INPUT_KEY_PRESS);
expect(action.key).to.equal('<C-B>');
});
}); });
describe("clearKeys", () => { describe("clearKeys", () => {

@ -0,0 +1,102 @@
import InputComponent from 'content/components/common/input';
import { expect } from "chai";
describe('InputComponent', () => {
it('register callbacks', () => {
let component = new InputComponent(window.document);
component.onKey((key) => {
expect(key).is.equals('a');
});
component.onKeyDown({ key: 'a' });
});
it('invoke callback once', () => {
let component = new InputComponent(window.document);
let a = 0, b = 0;
component.onKey((key) => {
if (key == 'a') {
++a;
} else {
key == 'b'
++b;
}
});
component.onKeyDown({ key: 'a' });
component.onKeyDown({ key: 'b' });
component.onKeyPress({ key: 'a' });
component.onKeyUp({ key: 'a' });
component.onKeyPress({ key: 'b' });
component.onKeyUp({ key: 'b' });
expect(a).is.equals(1);
expect(b).is.equals(1);
})
it('add prefix when ctrl pressed', () => {
let component = new InputComponent(window.document);
component.onKey((key) => {
expect(key).is.equals('<C-A>');
});
component.onKeyDown({ key: 'a', ctrlKey: true });
})
it('press X', () => {
let component = new InputComponent(window.document);
component.onKey((key) => {
expect(key).is.equals('X');
});
component.onKeyDown({ key: 'X', shiftKey: true });
})
it('press <Shift> + <Esc>', () => {
let component = new InputComponent(window.document);
component.onKey((key) => {
expect(key).is.equals('<S-Esc>');
});
component.onKeyDown({ key: 'Escape', shiftKey: true });
})
it('press <Ctrl> + <Esc>', () => {
let component = new InputComponent(window.document);
component.onKey((key) => {
expect(key).is.equals('<C-Esc>');
});
component.onKeyDown({ key: 'Escape', ctrlKey: true });
})
it('does not invoke only meta keys', () => {
let component = new InputComponent(window.document);
component.onKey((key) => {
expect.fail();
});
component.onKeyDown({ key: 'Shift' });
component.onKeyDown({ key: 'Control' });
component.onKeyDown({ key: 'Alt' });
component.onKeyDown({ key: 'OS' });
})
it('ignores events from input elements', () => {
['input', 'textarea', 'select'].forEach((name) => {
let target = window.document.createElement(name);
let component = new InputComponent(target);
component.onKey((key) => {
expect.fail();
});
component.onKeyDown({ key: 'x', target });
});
});
it('ignores events from contenteditable elements', () => {
let target = window.document.createElement('div');
let component = new InputComponent(target);
component.onKey((key) => {
expect.fail();
});
target.setAttribute('contenteditable', '');
component.onKeyDown({ key: 'x', target });
target.setAttribute('contenteditable', 'true');
component.onKeyDown({ key: 'x', target });
})
});

@ -0,0 +1,38 @@
import { expect } from "chai";
import actions from 'content/actions';
import addonReducer from 'content/reducers/addon';
describe("addon reducer", () => {
it('return the initial state', () => {
let state = addonReducer(undefined, {});
expect(state).to.have.property('enabled', true);
});
it('return next state for ADDON_ENABLE', () => {
let action = { type: actions.ADDON_ENABLE};
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);
});
});