Merge branch 'configurable-keymap'

jh-changes
Shin'ya Ueoka 7 years ago
commit 1145eb3478
  1. 10
      manifest.json
  2. 1
      src/actions/index.js
  3. 9
      src/actions/input.js
  4. 18
      src/actions/operation.js
  5. 39
      src/background/default-settings.js
  6. 20
      src/background/index.js
  7. 42
      src/background/keys.js
  8. 4
      src/content/index.js
  9. 4
      src/messages/index.js
  10. 31
      src/operations/index.js
  11. 6
      src/reducers/input.js
  12. 22
      src/settings/index.js
  13. 18
      src/settings/settings.html
  14. 8
      src/settings/settings.scss
  15. 6
      webpack.config.js

@ -15,11 +15,15 @@
] ]
}, },
"permissions": [ "permissions": [
"history",
"sessions", "sessions",
"tabs", "storage",
"history" "tabs"
], ],
"web_accessible_resources": [ "web_accessible_resources": [
"build/console.html" "build/console.html"
] ],
"options_ui": {
"page": "build/settings.html"
}
} }

@ -8,4 +8,5 @@ export default {
// User input // User input
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',
}; };

@ -14,4 +14,11 @@ const clearKeys = () => {
}; };
}; };
export { keyPress, clearKeys }; const setKeymaps = (keymaps) => {
return {
type: actions.INPUT_SET_KEYMAPS,
keymaps: keymaps
};
};
export { keyPress, clearKeys, setKeymaps };

@ -6,15 +6,15 @@ import * as zooms from '../background/zooms';
const exec = (operation, tab) => { const exec = (operation, tab) => {
switch (operation.type) { switch (operation.type) {
case operations.TABS_CLOSE: case operations.TAB_CLOSE:
return tabs.closeTab(tab.id); return tabs.closeTab(tab.id);
case operations.TABS_REOPEN: case operations.TAB_REOPEN:
return tabs.reopenTab(); return tabs.reopenTab();
case operations.TABS_PREV: case operations.TAB_PREV:
return tabs.selectPrevTab(tab.index, operation.count); return tabs.selectPrevTab(tab.index, operation.count);
case operations.TABS_NEXT: case operations.TAB_NEXT:
return tabs.selectNextTab(tab.index, operation.count); return tabs.selectNextTab(tab.index, operation.count);
case operations.TABS_RELOAD: case operations.TAB_RELOAD:
return tabs.reload(tab, operation.cache); return tabs.reload(tab, operation.cache);
case operations.ZOOM_IN: case operations.ZOOM_IN:
return zooms.zoomIn(); return zooms.zoomIn();
@ -22,21 +22,21 @@ const exec = (operation, tab) => {
return zooms.zoomOut(); return zooms.zoomOut();
case operations.ZOOM_NEUTRAL: case operations.ZOOM_NEUTRAL:
return zooms.neutral(); return zooms.neutral();
case operations.COMMAND_OPEN: case operations.COMMAND_SHOW:
return consoleActions.showCommand(''); return consoleActions.showCommand('');
case operations.COMMAND_TABS_OPEN: case operations.COMMAND_SHOW_OPEN:
if (operation.alter) { if (operation.alter) {
// alter url // alter url
return consoleActions.showCommand('open ' + tab.url); return consoleActions.showCommand('open ' + tab.url);
} }
return consoleActions.showCommand('open '); return consoleActions.showCommand('open ');
case operations.COMMAND_TABS_NEW: case operations.COMMAND_SHOW_TABOPEN:
if (operation.alter) { if (operation.alter) {
// alter url // alter url
return consoleActions.showCommand('tabopen ' + tab.url); return consoleActions.showCommand('tabopen ' + tab.url);
} }
return consoleActions.showCommand('tabopen '); return consoleActions.showCommand('tabopen ');
case operations.COMMAND_BUFFER: case operations.COMMAND_SHOW_BUFFER:
return consoleActions.showCommand('buffer '); return consoleActions.showCommand('buffer ');
default: default:
return browser.tabs.sendMessage(tab.id, { return browser.tabs.sendMessage(tab.id, {

@ -0,0 +1,39 @@
export default `{
"keymaps": {
"0": { "type": "scroll.home" },
":": { "type": "command.show" },
"o": { "type": "command.show.open", "alter": false },
"O": { "type": "command.show.open", "alter": true },
"t": { "type": "command.show.tabopen", "alter": false },
"T": { "type": "command.show.tabopen", "alter": true },
"b": { "type": "command.show.buffer" },
"k": { "type": "scroll.lines", "count": -1 },
"j": { "type": "scroll.lines", "count": 1 },
"<C-E>": { "type": "scroll.lines", "count": -1 },
"<C-Y>": { "type": "scroll.lines", "count": 1 },
"<C-U>": { "type": "scroll.pages", "count": -0.5 },
"<C-D>": { "type": "scroll.pages", "count": 0.5 },
"<C-B>": { "type": "scroll.pages", "count": -1 },
"<C-F>": { "type": "scroll.pages", "count": 1 },
"gg": { "type": "scroll.top" },
"G": { "type": "scroll.bottom" },
"$": { "type": "scroll.end" },
"d": { "type": "tabs.close" },
"u": { "type": "tabs.reopen" },
"h": { "type": "tabs.prev", "count": 1 },
"l": { "type": "tabs.next", "count": 1 },
"r": { "type": "tabs.reload", "cache": false },
"R": { "type": "tabs.reload", "cache": true },
"zi": { "type": "zoom.in" },
"zo": { "type": "zoom.out" },
"zz": { "type": "zoom.neutral" },
"f": { "type": "follow.start", "newTab": false },
"F": { "type": "follow.start", "newTab": true },
"H": { "type": "navigate.history.prev" },
"L": { "type": "navigate.history.next" },
"[[": { "type": "navigate.link.prev" },
"]]": { "type": "navigate.link.next" },
"gu": { "type": "navigate.parent" },
"gU": { "type": "navigate.root" }
}
}`;

@ -41,7 +41,7 @@ backgroundStore.subscribe((sender) => {
const keyQueueChanged = (state, sender) => { const keyQueueChanged = (state, sender) => {
let prefix = keys.asKeymapChars(state.input.keys); let prefix = keys.asKeymapChars(state.input.keys);
let matched = Object.keys(keys.defaultKeymap).filter((keyStr) => { let matched = Object.keys(state.input.keymaps).filter((keyStr) => {
return keyStr.startsWith(prefix); return keyStr.startsWith(prefix);
}); });
if (matched.length === 0) { if (matched.length === 0) {
@ -51,11 +51,19 @@ const keyQueueChanged = (state, sender) => {
matched.length === 1 && prefix !== matched[0]) { matched.length === 1 && prefix !== matched[0]) {
return Promise.resolve(); return Promise.resolve();
} }
let action = keys.defaultKeymap[matched]; let action = state.input.keymaps[matched];
backgroundStore.dispatch(operationActions.exec(action, sender.tab), sender); backgroundStore.dispatch(operationActions.exec(action, sender.tab), sender);
backgroundStore.dispatch(inputActions.clearKeys(), sender); backgroundStore.dispatch(inputActions.clearKeys(), sender);
}; };
const reloadSettings = () => {
browser.storage.local.get('settings').then((value) => {
let settings = JSON.parse(value.settings.json);
let action = inputActions.setKeymaps(settings.keymaps);
backgroundStore.dispatch(action);
}, console.error);
};
const handleMessage = (message, sender) => { const handleMessage = (message, sender) => {
switch (message.type) { switch (message.type) {
case messages.KEYDOWN: case messages.KEYDOWN:
@ -77,6 +85,8 @@ const handleMessage = (message, sender) => {
case messages.CONSOLE_CHANGEED: case messages.CONSOLE_CHANGEED:
return backgroundStore.dispatch( return backgroundStore.dispatch(
commandActions.complete(message.text), sender); commandActions.complete(message.text), sender);
case messages.SETTINGS_RELOAD:
return reloadSettings();
} }
}; };
@ -87,3 +97,9 @@ browser.runtime.onMessage.addListener((message, sender) => {
backgroundStore.dispatch(consoleActions.showError(e.message), sender); backgroundStore.dispatch(consoleActions.showError(e.message), sender);
} }
}); });
const initializeSettings = () => {
reloadSettings();
};
initializeSettings();

@ -1,43 +1,3 @@
import operations from '../operations';
const defaultKeymap = {
':': { type: operations.COMMAND_OPEN },
'o': { type: operations.COMMAND_TABS_OPEN, alter: false },
'O': { type: operations.COMMAND_TABS_OPEN, alter: true },
't': { type: operations.COMMAND_TABS_NEW, alter: false },
'T': { type: operations.COMMAND_TABS_NEW, alter: true },
'b': { type: operations.COMMAND_BUFFER },
'k': { type: operations.SCROLL_LINES, count: -1 },
'j': { type: operations.SCROLL_LINES, count: 1 },
'<C-E>': { type: operations.SCROLL_LINES, count: -1 },
'<C-Y>': { type: operations.SCROLL_LINES, count: 1 },
'<C-U>': { type: operations.SCROLL_PAGES, count: -0.5 },
'<C-D>': { type: operations.SCROLL_PAGES, count: 0.5 },
'<C-B>': { type: operations.SCROLL_PAGES, count: -1 },
'<C-F>': { type: operations.SCROLL_PAGES, count: 1 },
'gg': { type: operations.SCROLL_TOP },
'G': { type: operations.SCROLL_BOTTOM },
'0': { type: operations.SCROLL_LEFT },
'$': { type: operations.SCROLL_RIGHT },
'd': { type: operations.TABS_CLOSE },
'u': { type: operations.TABS_REOPEN },
'h': { type: operations.TABS_PREV, count: 1 },
'l': { type: operations.TABS_NEXT, count: 1 },
'r': { type: operations.TABS_RELOAD, cache: false },
'R': { type: operations.TABS_RELOAD, cache: true },
'zi': { type: operations.ZOOM_IN },
'zo': { type: operations.ZOOM_OUT },
'zz': { type: operations.ZOOM_NEUTRAL },
'f': { type: operations.FOLLOW_START, newTab: false },
'F': { type: operations.FOLLOW_START, newTab: true },
'H': { type: operations.NAVIGATE_HISTORY_PREV },
'L': { type: operations.NAVIGATE_HISTORY_NEXT },
'[[': { type: operations.NAVIGATE_LINK_PREV },
']]': { type: operations.NAVIGATE_LINK_NEXT },
'gu': { type: operations.NAVIGATE_PARENT },
'gU': { type: operations.NAVIGATE_ROOT },
};
const asKeymapChars = (keys) => { const asKeymapChars = (keys) => {
return keys.map((k) => { return keys.map((k) => {
let c = String.fromCharCode(k.code); let c = String.fromCharCode(k.code);
@ -58,4 +18,4 @@ const asCaretChars = (keys) => {
}).join(''); }).join('');
}; };
export { defaultKeymap, asKeymapChars, asCaretChars }; export { asKeymapChars, asCaretChars };

@ -85,9 +85,9 @@ const execOperation = (operation) => {
return scrolls.scrollTop(window); return scrolls.scrollTop(window);
case operations.SCROLL_BOTTOM: case operations.SCROLL_BOTTOM:
return scrolls.scrollBottom(window); return scrolls.scrollBottom(window);
case operations.SCROLL_LEFT: case operations.SCROLL_HOME:
return scrolls.scrollLeft(window); return scrolls.scrollLeft(window);
case operations.SCROLL_RIGHT: case operations.SCROLL_END:
return scrolls.scrollRight(window); return scrolls.scrollRight(window);
case operations.FOLLOW_START: case operations.FOLLOW_START:
return startFollows(operation.newTab); return startFollows(operation.newTab);

@ -8,5 +8,7 @@ export default {
KEYDOWN: 'keydown', KEYDOWN: 'keydown',
OPEN_URL: 'open.url' OPEN_URL: 'open.url',
SETTINGS_RELOAD: 'settings.reload',
}; };

@ -1,17 +1,22 @@
export default { export default {
// Command // Command
COMMAND_OPEN: 'cmd.open', COMMAND_SHOW: 'command.show',
COMMAND_TABS_OPEN: 'cmd.tabs.open', COMMAND_SHOW_OPEN: 'command.show.open',
COMMAND_TABS_NEW: 'cmd.tabs.new', COMMAND_SHOW_TABOPEN: 'command.show.tabopen',
COMMAND_BUFFER: 'cmd.buffer', COMMAND_SHOW_BUFFER: 'command.show.buffer',
// Scrolls
SCROLL_LINES: 'scroll.lines', SCROLL_LINES: 'scroll.lines',
SCROLL_PAGES: 'scroll.pages', SCROLL_PAGES: 'scroll.pages',
SCROLL_TOP: 'scroll.top', SCROLL_TOP: 'scroll.top',
SCROLL_BOTTOM: 'scroll.bottom', SCROLL_BOTTOM: 'scroll.bottom',
SCROLL_LEFT: 'scroll.left', SCROLL_HOME: 'scroll.home',
SCROLL_RIGHT: 'scroll.right', SCROLL_END: 'scroll.end',
// Follows
FOLLOW_START: 'follow.start', FOLLOW_START: 'follow.start',
// Navigations
NAVIGATE_HISTORY_PREV: 'navigate.history.prev', NAVIGATE_HISTORY_PREV: 'navigate.history.prev',
NAVIGATE_HISTORY_NEXT: 'navigate.history.next', NAVIGATE_HISTORY_NEXT: 'navigate.history.next',
NAVIGATE_LINK_PREV: 'navigate.link.prev', NAVIGATE_LINK_PREV: 'navigate.link.prev',
@ -19,12 +24,14 @@ export default {
NAVIGATE_PARENT: 'navigate.parent', NAVIGATE_PARENT: 'navigate.parent',
NAVIGATE_ROOT: 'navigate.root', NAVIGATE_ROOT: 'navigate.root',
// Background // Tabs
TABS_CLOSE: 'tabs.close', TAB_CLOSE: 'tabs.close',
TABS_REOPEN: 'tabs.reopen', TAB_REOPEN: 'tabs.reopen',
TABS_PREV: 'tabs.prev', TAB_PREV: 'tabs.prev',
TABS_NEXT: 'tabs.next', TAB_NEXT: 'tabs.next',
TABS_RELOAD: 'tabs.reload', TAB_RELOAD: 'tabs.reload',
// Zooms
ZOOM_IN: 'zoom.in', ZOOM_IN: 'zoom.in',
ZOOM_OUT: 'zoom.out', ZOOM_OUT: 'zoom.out',
ZOOM_NEUTRAL: 'zoom.neutral', ZOOM_NEUTRAL: 'zoom.neutral',

@ -2,6 +2,7 @@ import actions from '../actions';
const defaultState = { const defaultState = {
keys: [], keys: [],
keymaps: {}
}; };
export default function reducer(state = defaultState, action = {}) { export default function reducer(state = defaultState, action = {}) {
@ -19,6 +20,11 @@ export default function reducer(state = defaultState, action = {}) {
return Object.assign({}, state, { return Object.assign({}, state, {
keys: [], keys: [],
}); });
case actions.INPUT_SET_KEYMAPS:
return Object.assign({}, state, {
keymaps: action.keymaps,
keys: [],
});
default: default:
return state; return state;
} }

@ -0,0 +1,22 @@
import './settings.scss';
import messages from '../messages';
document.addEventListener('DOMContentLoaded', () => {
let form = document.getElementById('vimvixen-settings-form');
form.addEventListener('submit', (e) => {
e.preventDefault();
browser.storage.local.set({
settings: {
json: e.target.elements['plain-json'].value
}
}).then(() => {
return browser.runtime.sendMessage({
type: messages.SETTINGS_RELOAD
});
});
});
browser.storage.local.get('settings').then((value) => {
form.elements['plain-json'].value = value.settings.json;
}, console.error);
});

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
</head>
<body>
<h1>Configure</h1>
<h2>Home page</h2>
<form id='vimvixen-settings-form' class='vimvixen-settings-form'>
<label for='load-from-json'>Load from JSON:</label>
<textarea name='plain-json' spellcheck='false'></textarea>
<button type='submit'>Save</button>
</form>
<script src='settings.js'></script>
</body>
</html>

@ -0,0 +1,8 @@
.vimvixen-settings-form {
textarea[name=plain-json] {
font-family: monospace;
width: 100%;
min-height: 64ex;
resize: vertical;
}
}

@ -7,6 +7,7 @@ const dist = path.resolve(__dirname, 'build');
module.exports = { module.exports = {
entry: { entry: {
index: path.join(src, 'content'), index: path.join(src, 'content'),
settings: path.join(src, 'settings'),
background: path.join(src, 'background'), background: path.join(src, 'background'),
console: path.join(src, 'console', 'console.js') console: path.join(src, 'console', 'console.js')
}, },
@ -46,6 +47,11 @@ module.exports = {
template: path.join(src, 'console', 'console.html'), template: path.join(src, 'console', 'console.html'),
filename: path.join(dist, 'console.html'), filename: path.join(dist, 'console.html'),
inject: false inject: false
}),
new HtmlWebpackPlugin({
template: path.join(src, 'settings', 'settings.html'),
filename: path.join(dist, 'settings.html'),
inject: false
}) })
] ]
}; };