commit
b2cddcd69b
29 changed files with 735 additions and 652 deletions
@ -0,0 +1,11 @@ |
|||||||
|
import actions from '../actions'; |
||||||
|
|
||||||
|
export function requestCompletions(line) { |
||||||
|
let command = line.split(' ', 1)[0]; |
||||||
|
let keywords = line.replace(command + ' ', ''); |
||||||
|
return { |
||||||
|
type: actions.BACKGROUND_REQUEST_COMPLETIONS, |
||||||
|
command, |
||||||
|
keywords |
||||||
|
}; |
||||||
|
} |
@ -0,0 +1,35 @@ |
|||||||
|
import actions from '../actions'; |
||||||
|
|
||||||
|
const normalizeUrl = (string) => { |
||||||
|
try { |
||||||
|
return new URL(string).href |
||||||
|
} catch (e) { |
||||||
|
return 'http://' + string; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export function exec(line) { |
||||||
|
let name = line.split(' ')[0]; |
||||||
|
let remaining = line.replace(name + ' ', ''); |
||||||
|
|
||||||
|
switch (name) { |
||||||
|
case 'open': |
||||||
|
// TODO use search engined and pass keywords to them
|
||||||
|
return { |
||||||
|
type: actions.COMMAND_OPEN_URL, |
||||||
|
url: normalizeUrl(remaining) |
||||||
|
}; |
||||||
|
case 'tabopen': |
||||||
|
return { |
||||||
|
type: actions.COMMAND_TABOPEN_URL, |
||||||
|
url: normalizeUrl(remaining) |
||||||
|
}; |
||||||
|
case 'b': |
||||||
|
case 'buffer': |
||||||
|
return { |
||||||
|
type: actions.COMMAND_BUFFER, |
||||||
|
keywords: remaining |
||||||
|
}; |
||||||
|
} |
||||||
|
throw new Error(name + ' command is not defined'); |
||||||
|
} |
@ -0,0 +1,28 @@ |
|||||||
|
import actions from '../actions'; |
||||||
|
|
||||||
|
export function showCommand(text) { |
||||||
|
return { |
||||||
|
type: actions.CONSOLE_SHOW_COMMAND, |
||||||
|
text: text |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
export function setCompletions(completions) { |
||||||
|
return {
|
||||||
|
type: actions.CONSOLE_SET_COMPLETIONS, |
||||||
|
completions: completions |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
export function showError(text) { |
||||||
|
return { |
||||||
|
type: actions.CONSOLE_SHOW_ERROR, |
||||||
|
text: text |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
export function hide() { |
||||||
|
return {
|
||||||
|
type: actions.CONSOLE_HIDE |
||||||
|
}; |
||||||
|
} |
@ -0,0 +1,40 @@ |
|||||||
|
export default { |
||||||
|
// console commands
|
||||||
|
CONSOLE_SHOW_COMMAND: 'vimvixen.console.show.command', |
||||||
|
CONSOLE_SET_COMPLETIONS: 'vimvixen.console.set.completions', |
||||||
|
CONSOLE_SHOW_ERROR: 'vimvixen.console.show.error', |
||||||
|
CONSOLE_HIDE: 'vimvixen.console.hide', |
||||||
|
|
||||||
|
// Background commands
|
||||||
|
BACKGROUND_REQUEST_COMPLETIONS: 'vimvixen.background.request.completions', |
||||||
|
TABS_CLOSE: 'tabs.close', |
||||||
|
TABS_REOPEN: 'tabs.reopen', |
||||||
|
TABS_PREV: 'tabs.prev', |
||||||
|
TABS_NEXT: 'tabs.next', |
||||||
|
TABS_RELOAD: 'tabs.reload', |
||||||
|
ZOOM_IN: 'zoom.in', |
||||||
|
ZOOM_OUT: 'zoom.out', |
||||||
|
ZOOM_NEUTRAL: 'zoom.neutral', |
||||||
|
|
||||||
|
// content commands
|
||||||
|
CMD_OPEN: 'cmd.open', |
||||||
|
CMD_TABS_OPEN: 'cmd.tabs.open', |
||||||
|
CMD_BUFFER: 'cmd.buffer', |
||||||
|
SCROLL_LINES: 'scroll.lines', |
||||||
|
SCROLL_PAGES: 'scroll.pages', |
||||||
|
SCROLL_TOP: 'scroll.top', |
||||||
|
SCROLL_BOTTOM: 'scroll.bottom', |
||||||
|
SCROLL_LEFT: 'scroll.left', |
||||||
|
SCROLL_RIGHT: 'scroll.right', |
||||||
|
FOLLOW_START: 'follow.start', |
||||||
|
HISTORY_PREV: 'history.prev', |
||||||
|
HISTORY_NEXT: 'history.next', |
||||||
|
|
||||||
|
// User input
|
||||||
|
INPUT_KEY_PRESS: 'input.key,press', |
||||||
|
INPUT_CLEAR_KEYS: 'input.clear.keys', |
||||||
|
|
||||||
|
COMMAND_OPEN_URL: 'command.open.url', |
||||||
|
COMMAND_TABOPEN_URL: 'command.tabopen.url', |
||||||
|
COMMAND_BUFFER: 'command.buffer', |
||||||
|
}; |
@ -0,0 +1,15 @@ |
|||||||
|
import actions from '../actions'; |
||||||
|
|
||||||
|
export function keyPress(code, ctrl) { |
||||||
|
return { |
||||||
|
type: actions.INPUT_KEY_PRESS, |
||||||
|
code, |
||||||
|
ctrl |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
export function clearKeys() { |
||||||
|
return { |
||||||
|
type: actions.INPUT_CLEAR_KEYS |
||||||
|
} |
||||||
|
} |
@ -1,103 +1,45 @@ |
|||||||
import * as actions from '../shared/actions'; |
import * as keys from './keys'; |
||||||
import * as tabs from './tabs'; |
import * as inputActions from '../actions/input'; |
||||||
import * as zooms from './zooms'; |
import backgroundReducers from '../reducers/background'; |
||||||
import KeyQueue from './key-queue'; |
import commandReducer from '../reducers/command'; |
||||||
|
import inputReducers from '../reducers/input'; |
||||||
|
|
||||||
const queue = new KeyQueue(); |
let inputState = inputReducers(undefined, {}); |
||||||
|
|
||||||
const keyPressHandle = (request, sender) => { |
const keyQueueChanged = (sender, prevState, state) => { |
||||||
let action = queue.push({ |
if (state.keys.length === 0) { |
||||||
code: request.code, |
|
||||||
ctrl: request.ctrl |
|
||||||
}); |
|
||||||
if (!action) { |
|
||||||
return Promise.resolve(); |
return Promise.resolve(); |
||||||
} |
} |
||||||
|
|
||||||
if (actions.isBackgroundAction(action[0])) { |
let prefix = keys.asKeymapChars(state.keys); |
||||||
return doBackgroundAction(sender, action); |
let matched = Object.keys(keys.defaultKeymap).filter((keys) => { |
||||||
} else if (actions.isContentAction(action[0])) { |
return keys.startsWith(prefix); |
||||||
return Promise.resolve({ |
|
||||||
type: 'response.action', |
|
||||||
action: action |
|
||||||
}); |
}); |
||||||
} |
if (matched.length == 0) { |
||||||
return Promise.resolve(); |
return handleMessage(inputActions.clearKeys(), sender); |
||||||
}; |
} else if (matched.length > 1 || matched.length === 1 && prefix !== matched[0]) { |
||||||
|
|
||||||
const doBackgroundAction = (sender, action) => { |
|
||||||
switch(action[0]) { |
|
||||||
case actions.TABS_CLOSE: |
|
||||||
return tabs.closeTab(sender.tab.id); |
|
||||||
case actions.TABS_REOPEN: |
|
||||||
return tabs.reopenTab(); |
|
||||||
case actions.TABS_PREV: |
|
||||||
return tabs.selectPrevTab(sender.tab.index, actions[1] || 1); |
|
||||||
case actions.TABS_NEXT: |
|
||||||
return tabs.selectNextTab(sender.tab.index, actions[1] || 1); |
|
||||||
case actions.TABS_RELOAD: |
|
||||||
return tabs.reload(sender.tab, actions[1] || false); |
|
||||||
case actions.ZOOM_IN: |
|
||||||
return zooms.zoomIn(); |
|
||||||
case actions.ZOOM_OUT: |
|
||||||
return zooms.zoomOut(); |
|
||||||
case actions.ZOOM_NEUTRAL: |
|
||||||
return zooms.neutral(); |
|
||||||
} |
|
||||||
return Promise.resolve(); |
return Promise.resolve(); |
||||||
} |
} |
||||||
|
let action = keys.defaultKeymap[matched]; |
||||||
const normalizeUrl = (string) => { |
return handleMessage(inputActions.clearKeys(), sender).then(() => { |
||||||
try { |
return backgroundReducers(undefined, action, sender).then(() => { |
||||||
return new URL(string).href |
return browser.tabs.sendMessage(sender.tab.id, action); |
||||||
} catch (e) { |
}); |
||||||
return 'http://' + string; |
}); |
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
const cmdBuffer = (sender, arg) => { |
|
||||||
if (isNaN(arg)) { |
|
||||||
return tabs.selectByKeyword(sender.tab, arg); |
|
||||||
} else { |
|
||||||
let index = parseInt(arg, 10) - 1; |
|
||||||
return tabs.selectAt(index); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
const cmdEnterHandle = (request, sender) => { |
|
||||||
let words = request.text.split(' ').filter((s) => s.length > 0); |
|
||||||
switch (words[0]) { |
|
||||||
case 'open': |
|
||||||
return browser.tabs.update(sender.tab.id, { url: normalizeUrl(words[1]) }); |
|
||||||
case 'tabopen': |
|
||||||
return browser.tabs.create({ url: normalizeUrl(words[1]) }); |
|
||||||
case 'b': |
|
||||||
case 'buffer': |
|
||||||
return cmdBuffer(sender, words[1]); |
|
||||||
} |
|
||||||
throw new Error(words[0] + ' command is not defined'); |
|
||||||
}; |
}; |
||||||
|
|
||||||
browser.runtime.onMessage.addListener((request, sender) => { |
const handleMessage = (action, sender) => { |
||||||
switch (request.type) { |
let nextInputState = inputReducers(inputState, action); |
||||||
case 'event.keypress': |
if (JSON.stringify(nextInputState) !== JSON.stringify(inputState)) { |
||||||
return keyPressHandle(request, sender); |
let prevState = inputState; |
||||||
case 'event.cmd.enter': |
inputState = nextInputState; |
||||||
return cmdEnterHandle(request, sender); |
return keyQueueChanged(sender, prevState, inputState); |
||||||
case 'event.cmd.tabs.completion': |
|
||||||
return tabs.getCompletions(request.text).then((tabs) => { |
|
||||||
let items = tabs.map((tab) => { |
|
||||||
return { |
|
||||||
caption: tab.title, |
|
||||||
content: tab.title, |
|
||||||
url: tab.url, |
|
||||||
icon: tab.favIconUrl |
|
||||||
} |
} |
||||||
|
return backgroundReducers(undefined, action, sender).then(() => { |
||||||
|
return commandReducer(undefined, action, sender).then(() => { |
||||||
|
return browser.tabs.sendMessage(sender.tab.id, action); |
||||||
}); |
}); |
||||||
return { |
|
||||||
name: "Buffers", |
|
||||||
items: items |
|
||||||
}; |
|
||||||
}); |
|
||||||
} |
|
||||||
}); |
}); |
||||||
|
}; |
||||||
|
|
||||||
|
browser.runtime.onMessage.addListener(handleMessage); |
||||||
|
@ -1,82 +0,0 @@ |
|||||||
import * as actions from '../shared/actions'; |
|
||||||
|
|
||||||
const DEFAULT_KEYMAP = { |
|
||||||
':': [ actions.CMD_OPEN ], |
|
||||||
'o': [ actions.CMD_TABS_OPEN, false ], |
|
||||||
'O': [ actions.CMD_TABS_OPEN, true ], |
|
||||||
'b': [ actions.CMD_BUFFER ], |
|
||||||
'k': [ actions.SCROLL_LINES, -1 ], |
|
||||||
'j': [ actions.SCROLL_LINES, 1 ], |
|
||||||
'<C-E>': [ actions.SCROLL_LINES, -1 ], |
|
||||||
'<C-Y>': [ actions.SCROLL_LINES, 1 ], |
|
||||||
'<C-U>': [ actions.SCROLL_PAGES, -0.5 ], |
|
||||||
'<C-D>': [ actions.SCROLL_PAGES, 0.5 ], |
|
||||||
'<C-B>': [ actions.SCROLL_PAGES, -1 ], |
|
||||||
'<C-F>': [ actions.SCROLL_PAGES, 1 ], |
|
||||||
'gg': [ actions.SCROLL_TOP ], |
|
||||||
'G': [ actions.SCROLL_BOTTOM ], |
|
||||||
'0': [ actions.SCROLL_LEFT ], |
|
||||||
'$': [ actions.SCROLL_RIGHT ], |
|
||||||
'd': [ actions.TABS_CLOSE ], |
|
||||||
'u': [ actions.TABS_REOPEN], |
|
||||||
'h': [ actions.TABS_PREV, 1 ], |
|
||||||
'l': [ actions.TABS_NEXT, 1 ], |
|
||||||
'r': [ actions.TABS_RELOAD, false ], |
|
||||||
'R': [ actions.TABS_RELOAD, true ], |
|
||||||
'zi': [ actions.ZOOM_IN ], |
|
||||||
'zo': [ actions.ZOOM_OUT ], |
|
||||||
'zz': [ actions.ZOOM_NEUTRAL], |
|
||||||
'f': [ actions.FOLLOW_START, false ], |
|
||||||
'F': [ actions.FOLLOW_START, true ], |
|
||||||
'H': [ actions.HISTORY_PREV ], |
|
||||||
'L': [ actions.HISTORY_NEXT ], |
|
||||||
} |
|
||||||
|
|
||||||
export default class KeyQueue { |
|
||||||
|
|
||||||
constructor(keymap = DEFAULT_KEYMAP) { |
|
||||||
this.data = []; |
|
||||||
this.keymap = keymap; |
|
||||||
} |
|
||||||
|
|
||||||
push(key) { |
|
||||||
this.data.push(key); |
|
||||||
|
|
||||||
let current = this.asKeymapChars(); |
|
||||||
let filtered = Object.keys(this.keymap).filter((keys) => { |
|
||||||
return keys.startsWith(current); |
|
||||||
}); |
|
||||||
|
|
||||||
if (filtered.length == 0) { |
|
||||||
this.data = []; |
|
||||||
return null; |
|
||||||
} else if (filtered.length === 1 && current === filtered[0]) { |
|
||||||
let action = this.keymap[filtered[0]]; |
|
||||||
this.data = []; |
|
||||||
return action; |
|
||||||
} |
|
||||||
return null; |
|
||||||
} |
|
||||||
|
|
||||||
asKeymapChars() { |
|
||||||
return this.data.map((k) => { |
|
||||||
let c = String.fromCharCode(k.code); |
|
||||||
if (k.ctrl) { |
|
||||||
return '<C-' + c.toUpperCase() + '>'; |
|
||||||
} else { |
|
||||||
return c |
|
||||||
} |
|
||||||
}).join(''); |
|
||||||
} |
|
||||||
|
|
||||||
asCaretChars() { |
|
||||||
return this.data.map((k) => { |
|
||||||
let c = String.fromCharCode(k.code); |
|
||||||
if (k.ctrl) { |
|
||||||
return '^' + c.toUpperCase(); |
|
||||||
} else { |
|
||||||
return c; |
|
||||||
} |
|
||||||
}).join(''); |
|
||||||
} |
|
||||||
} |
|
@ -1,28 +1,57 @@ |
|||||||
const identifyKey = (key1, key2) => { |
import actions from '../actions'; |
||||||
return (key1.code === key2.code) && |
|
||||||
((key1.shift || false) === (key2.shift || false)) && |
|
||||||
((key1.ctrl || false) === (key2.ctrl || false)) && |
|
||||||
((key1.alt || false) === (key2.alt || false)) && |
|
||||||
((key1.meta || false) === (key2.meta || false)); |
|
||||||
}; |
|
||||||
|
|
||||||
const hasPrefix = (keys, prefix) => { |
const defaultKeymap = { |
||||||
if (keys.length < prefix.length) { |
':': { type: actions.CMD_OPEN }, |
||||||
return false; |
'o': { type: actions.CMD_TABS_OPEN, alter: false }, |
||||||
} |
'O': { type: actions.CMD_TABS_OPEN, alter: true }, |
||||||
for (let i = 0; i < prefix.length; ++i) { |
'b': { type: actions.CMD_BUFFER }, |
||||||
if (!identifyKey(keys[i], prefix[i])) { |
'k': { type: actions.SCROLL_LINES, count: -1 }, |
||||||
return false; |
'j': { type: actions.SCROLL_LINES, count: 1 }, |
||||||
|
'<C-E>': { type: actions.SCROLL_LINES, count: -1 }, |
||||||
|
'<C-Y>': { type: actions.SCROLL_LINES, count: 1 }, |
||||||
|
'<C-U>': { type: actions.SCROLL_PAGES, count: -0.5 }, |
||||||
|
'<C-D>': { type: actions.SCROLL_PAGES, count: 0.5 }, |
||||||
|
'<C-B>': { type: actions.SCROLL_PAGES, count: -1 }, |
||||||
|
'<C-F>': { type: actions.SCROLL_PAGES, count: 1 }, |
||||||
|
'gg': { type: actions.SCROLL_TOP }, |
||||||
|
'G': { type: actions.SCROLL_BOTTOM }, |
||||||
|
'0': { type: actions.SCROLL_LEFT }, |
||||||
|
'$': { type: actions.SCROLL_RIGHT }, |
||||||
|
'd': { type: actions.TABS_CLOSE }, |
||||||
|
'u': { type: actions.TABS_REOPEN }, |
||||||
|
'h': { type: actions.TABS_PREV, count: 1 }, |
||||||
|
'l': { type: actions.TABS_NEXT, count: 1 }, |
||||||
|
'r': { type: actions.TABS_RELOAD, cache: false }, |
||||||
|
'R': { type: actions.TABS_RELOAD, cache: true }, |
||||||
|
'zi': { type: actions.ZOOM_IN }, |
||||||
|
'zo': { type: actions.ZOOM_OUT }, |
||||||
|
'zz': { type: actions.ZOOM_NEUTRAL }, |
||||||
|
'f': { type: actions.FOLLOW_START, newTab: false }, |
||||||
|
'F': { type: actions.FOLLOW_START, newTab: true }, |
||||||
|
'H': { type: actions.HISTORY_PREV }, |
||||||
|
'L': { type: actions.HISTORY_NEXT }, |
||||||
} |
} |
||||||
|
|
||||||
|
const asKeymapChars = (keys) => { |
||||||
|
return keys.map((k) => { |
||||||
|
let c = String.fromCharCode(k.code); |
||||||
|
if (k.ctrl) { |
||||||
|
return '<C-' + c.toUpperCase() + '>'; |
||||||
|
} else { |
||||||
|
return c |
||||||
} |
} |
||||||
return true; |
}).join(''); |
||||||
} |
} |
||||||
|
|
||||||
const identifyKeys = (keys1, keys2) => { |
const asCaretChars = (keys) => { |
||||||
if (keys1.length !== keys2.length) { |
return keys.map((k) => { |
||||||
return false; |
let c = String.fromCharCode(k.code); |
||||||
|
if (k.ctrl) { |
||||||
|
return '^' + c.toUpperCase(); |
||||||
|
} else { |
||||||
|
return c; |
||||||
} |
} |
||||||
return hasPrefix(keys1, keys2); |
}).join(''); |
||||||
} |
} |
||||||
|
|
||||||
export { identifyKey, identifyKeys, hasPrefix }; |
export { defaultKeymap, asKeymapChars, asCaretChars }; |
||||||
|
@ -1,61 +0,0 @@ |
|||||||
import './console-frame.scss'; |
|
||||||
import * as messages from '../shared/messages'; |
|
||||||
|
|
||||||
export default class ConsoleFrame { |
|
||||||
constructor(win) { |
|
||||||
let element = window.document.createElement('iframe'); |
|
||||||
element.src = browser.runtime.getURL('build/console.html'); |
|
||||||
element.className = 'vimvixen-console-frame'; |
|
||||||
win.document.body.append(element); |
|
||||||
|
|
||||||
this.element = element; |
|
||||||
|
|
||||||
this.errorShown = true; |
|
||||||
|
|
||||||
this.hide(); |
|
||||||
} |
|
||||||
|
|
||||||
showCommand(text) { |
|
||||||
this.showFrame(); |
|
||||||
|
|
||||||
let message = { |
|
||||||
type: 'vimvixen.console.show.command', |
|
||||||
text: text |
|
||||||
}; |
|
||||||
messages.send(this.element.contentWindow, message); |
|
||||||
this.errorShown = false; |
|
||||||
} |
|
||||||
|
|
||||||
showError(text) { |
|
||||||
this.showFrame(); |
|
||||||
|
|
||||||
let message = { |
|
||||||
type: 'vimvixen.console.show.error', |
|
||||||
text: text |
|
||||||
}; |
|
||||||
messages.send(this.element.contentWindow, message); |
|
||||||
this.errorShown = true; |
|
||||||
this.element.blur(); |
|
||||||
} |
|
||||||
|
|
||||||
showFrame() { |
|
||||||
this.element.style.display = 'block'; |
|
||||||
} |
|
||||||
|
|
||||||
hide() { |
|
||||||
this.element.style.display = 'none'; |
|
||||||
this.element.blur(); |
|
||||||
this.errorShown = false; |
|
||||||
} |
|
||||||
|
|
||||||
isErrorShown() { |
|
||||||
return this.element.style.display === 'block' && this.errorShown; |
|
||||||
} |
|
||||||
|
|
||||||
setCompletions(completions) { |
|
||||||
messages.send(this.element.contentWindow, { |
|
||||||
type: 'vimvixen.console.set.completions', |
|
||||||
completions: completions |
|
||||||
}); |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,27 @@ |
|||||||
|
import './console-frame.scss'; |
||||||
|
import * as consoleActions from '../actions/console'; |
||||||
|
|
||||||
|
const initialize = (doc) => { |
||||||
|
let iframe = doc.createElement('iframe'); |
||||||
|
iframe.src = browser.runtime.getURL('build/console.html'); |
||||||
|
iframe.id = 'vimvixen-console-frame'; |
||||||
|
iframe.className = 'vimvixen-console-frame'; |
||||||
|
doc.body.append(iframe); |
||||||
|
|
||||||
|
return iframe; |
||||||
|
} |
||||||
|
|
||||||
|
const showCommand = (text) => { |
||||||
|
return browser.runtime.sendMessage(consoleActions.showCommand(text)); |
||||||
|
}; |
||||||
|
|
||||||
|
const showError = (text) => { |
||||||
|
return browser.runtime.sendMessage(consoleActions.showError(text)); |
||||||
|
} |
||||||
|
|
||||||
|
const blur = (doc) => { |
||||||
|
let iframe = doc.getElementById('vimvixen-console-frame'); |
||||||
|
iframe.blur(); |
||||||
|
} |
||||||
|
|
||||||
|
export { initialize, showCommand, showError, blur }; |
@ -1,129 +1,41 @@ |
|||||||
import * as scrolls from './scrolls'; |
import '../console/console-frame.scss'; |
||||||
import * as histories from './histories'; |
import * as inputActions from '../actions/input'; |
||||||
import * as actions from '../shared/actions'; |
import * as consoleFrames from '../console/frames'; |
||||||
import * as messages from '../shared/messages'; |
import actions from '../actions'; |
||||||
import ConsoleFrame from '../console/console-frame'; |
import contentReducer from '../reducers/content'; |
||||||
import Follow from './follow'; |
|
||||||
|
|
||||||
let vvConsole = new ConsoleFrame(window); |
consoleFrames.initialize(window.document); |
||||||
|
|
||||||
const doAction = (action) => { |
browser.runtime.onMessage.addListener((action) => { |
||||||
if (typeof action === 'undefined' || action === null) { |
contentReducer(undefined, action); |
||||||
return; |
return Promise.resolve(); |
||||||
} |
}); |
||||||
|
|
||||||
switch (action[0]) { |
|
||||||
case actions.CMD_OPEN: |
|
||||||
vvConsole.showCommand(''); |
|
||||||
break; |
|
||||||
case actions.CMD_TABS_OPEN: |
|
||||||
if (action[1] || false) { |
|
||||||
// alter url
|
|
||||||
vvConsole.showCommand('open ' + window.location.href); |
|
||||||
} else { |
|
||||||
vvConsole.showCommand('open '); |
|
||||||
} |
|
||||||
break; |
|
||||||
case actions.CMD_BUFFER: |
|
||||||
vvConsole.showCommand('buffer '); |
|
||||||
break; |
|
||||||
case actions.SCROLL_LINES: |
|
||||||
scrolls.scrollLines(window, action[1]); |
|
||||||
break; |
|
||||||
case actions.SCROLL_PAGES: |
|
||||||
scrolls.scrollPages(window, action[1]); |
|
||||||
break; |
|
||||||
case actions.SCROLL_TOP: |
|
||||||
scrolls.scrollTop(window); |
|
||||||
break; |
|
||||||
case actions.SCROLL_BOTTOM: |
|
||||||
scrolls.scrollBottom(window); |
|
||||||
break; |
|
||||||
case actions.SCROLL_LEFT: |
|
||||||
scrolls.scrollLeft(window); |
|
||||||
break; |
|
||||||
case actions.SCROLL_RIGHT: |
|
||||||
scrolls.scrollRight(window); |
|
||||||
break; |
|
||||||
case actions.FOLLOW_START: |
|
||||||
new Follow(window.document, action[1] || false); |
|
||||||
break; |
|
||||||
case actions.HISTORY_PREV: |
|
||||||
histories.prev(window); |
|
||||||
break; |
|
||||||
case actions.HISTORY_NEXT: |
|
||||||
histories.next(window); |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
const handleResponse = (response) => { |
|
||||||
if (!response) { |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
switch(response.type) { |
|
||||||
case 'response.action': |
|
||||||
doAction(response.action); |
|
||||||
break; |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
window.addEventListener("keypress", (e) => { |
window.addEventListener("keypress", (e) => { |
||||||
if (e.target instanceof HTMLInputElement) { |
if (e.target instanceof HTMLInputElement) { |
||||||
return; |
return; |
||||||
} |
} |
||||||
|
browser.runtime.sendMessage(inputActions.keyPress(e.which, e.ctrlKey)) |
||||||
let request = { |
|
||||||
type: 'event.keypress', |
|
||||||
code: e.which, |
|
||||||
ctrl: e.ctrlKey, |
|
||||||
} |
|
||||||
|
|
||||||
browser.runtime.sendMessage(request) |
|
||||||
.then(handleResponse) |
|
||||||
.catch((err) => { |
.catch((err) => { |
||||||
console.error("Vim Vixen:", err); |
console.error("Vim Vixen:", err); |
||||||
vvConsole.showError(err.message); |
return consoleFrames.showError(err.message); |
||||||
}); |
}); |
||||||
}); |
}); |
||||||
|
|
||||||
const doCompletion = (line) => { |
browser.runtime.onMessage.addListener((action) => { |
||||||
if (line.startsWith('buffer ')) { |
switch (action.type) { |
||||||
let keyword = line.replace('buffer ', ''); |
case actions.CONSOLE_HIDE: |
||||||
|
window.focus(); |
||||||
browser.runtime.sendMessage({ |
return consoleFrames.blur(window.document); |
||||||
type: 'event.cmd.tabs.completion', |
|
||||||
text: keyword |
|
||||||
}).then((completions) => { |
|
||||||
vvConsole.setCompletions([completions]); |
|
||||||
}).catch((err) => { |
|
||||||
console.error("Vim Vixen:", err); |
|
||||||
vvConsole.showError(err.message); |
|
||||||
}); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
messages.receive(window, (message) => { |
|
||||||
switch (message.type) { |
|
||||||
case 'vimvixen.command.blur': |
|
||||||
if (!vvConsole.isErrorShown()) { |
|
||||||
vvConsole.hide(); |
|
||||||
} |
|
||||||
break; |
|
||||||
case 'vimvixen.command.enter': |
case 'vimvixen.command.enter': |
||||||
browser.runtime.sendMessage({ |
return browser.runtime.sendMessage({ |
||||||
type: 'event.cmd.enter', |
type: 'event.cmd.enter', |
||||||
text: message.value |
text: action.value |
||||||
}).catch((err) => { |
}).catch((err) => { |
||||||
console.error("Vim Vixen:", err); |
console.error("Vim Vixen:", err); |
||||||
vvConsole.showError(err.message); |
return consoleFrames.showError(err.message); |
||||||
}); |
}); |
||||||
break; |
|
||||||
case 'vimvixen.command.change': |
|
||||||
doCompletion(message.value); |
|
||||||
break; |
|
||||||
default: |
default: |
||||||
return; |
return Promise.resolve(); |
||||||
} |
} |
||||||
}); |
}); |
||||||
|
@ -0,0 +1,53 @@ |
|||||||
|
import * as tabs from '../background/tabs'; |
||||||
|
import * as zooms from '../background/zooms'; |
||||||
|
import * as consoleActions from '../actions/console'; |
||||||
|
import actions from '../actions'; |
||||||
|
|
||||||
|
const doCompletion = (command, keywords, sender) => { |
||||||
|
if (command === 'buffer') { |
||||||
|
return tabs.getCompletions(keywords).then((tabs) => { |
||||||
|
let items = tabs.map((tab) => { |
||||||
|
return { |
||||||
|
caption: tab.title, |
||||||
|
content: tab.title, |
||||||
|
url: tab.url, |
||||||
|
icon: tab.favIconUrl |
||||||
|
} |
||||||
|
}); |
||||||
|
let completions = { |
||||||
|
name: "Buffers", |
||||||
|
items: items |
||||||
|
}; |
||||||
|
return browser.tabs.sendMessage( |
||||||
|
sender, |
||||||
|
consoleActions.setCompletions([completions])); |
||||||
|
}); |
||||||
|
} |
||||||
|
return Promise.resolve(); |
||||||
|
}; |
||||||
|
|
||||||
|
export default function reducer(state, action = {}, sender) { |
||||||
|
// TODO hide sender object
|
||||||
|
switch (action.type) { |
||||||
|
case actions.BACKGROUND_REQUEST_COMPLETIONS: |
||||||
|
return doCompletion(action.command, action.keywords, sender.tab.id); |
||||||
|
case actions.TABS_CLOSE: |
||||||
|
return tabs.closeTab(sender.tab.id); |
||||||
|
case actions.TABS_REOPEN: |
||||||
|
return tabs.reopenTab(); |
||||||
|
case actions.TABS_PREV: |
||||||
|
return tabs.selectPrevTab(sender.tab.index, action.count); |
||||||
|
case actions.TABS_NEXT: |
||||||
|
return tabs.selectNextTab(sender.tab.index, action.count); |
||||||
|
case actions.TABS_RELOAD: |
||||||
|
return tabs.reload(sender.tab, action.cache); |
||||||
|
case actions.ZOOM_IN: |
||||||
|
return zooms.zoomIn(); |
||||||
|
case actions.ZOOM_OUT: |
||||||
|
return zooms.zoomOut(); |
||||||
|
case actions.ZOOM_NEUTRAL: |
||||||
|
return zooms.neutral(); |
||||||
|
default: |
||||||
|
return Promise.resolve(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,24 @@ |
|||||||
|
import * as tabs from '../background/tabs'; |
||||||
|
import actions from '../actions'; |
||||||
|
|
||||||
|
const cmdBuffer = (sender, arg) => { |
||||||
|
if (isNaN(arg)) { |
||||||
|
return tabs.selectByKeyword(sender.tab, arg); |
||||||
|
} else { |
||||||
|
let index = parseInt(arg, 10) - 1; |
||||||
|
return tabs.selectAt(index); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default function reducer(state, action, sender) { |
||||||
|
switch (action.type) { |
||||||
|
case actions.COMMAND_OPEN_URL: |
||||||
|
return browser.tabs.update(sender.tab.id, { url: action.url }); |
||||||
|
case actions.COMMAND_TABOPEN_URL: |
||||||
|
return browser.tabs.create({ url: action.url }); |
||||||
|
case actions.COMMAND_BUFFER: |
||||||
|
return cmdBuffer(sender, action.keywords); |
||||||
|
default: |
||||||
|
return Promise.resolve(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
import actions from '../actions'; |
||||||
|
|
||||||
|
const defaultState = { |
||||||
|
errorShown: false, |
||||||
|
errorText: '', |
||||||
|
commandShown: false, |
||||||
|
commandText: '', |
||||||
|
completions: [], |
||||||
|
}; |
||||||
|
|
||||||
|
export default function reducer(state = defaultState, action = {}) { |
||||||
|
switch (action.type) { |
||||||
|
case actions.CONSOLE_SHOW_COMMAND: |
||||||
|
return Object.assign({}, state, { |
||||||
|
commandShown: true,
|
||||||
|
commandText: action.text, |
||||||
|
errorShown: false, |
||||||
|
completions: [] |
||||||
|
}); |
||||||
|
case actions.CONSOLE_SET_COMPLETIONS: |
||||||
|
return Object.assign({}, state, { |
||||||
|
completions: action.completions |
||||||
|
}); |
||||||
|
case actions.CONSOLE_SHOW_ERROR: |
||||||
|
return Object.assign({}, state, { |
||||||
|
errorText: action.text, |
||||||
|
errorShown: true, |
||||||
|
commandShown: false, |
||||||
|
}); |
||||||
|
case actions.CONSOLE_HIDE: |
||||||
|
return Object.assign({}, state, { |
||||||
|
errorShown: false, |
||||||
|
commandShown: false |
||||||
|
|
||||||
|
}); |
||||||
|
default: |
||||||
|
return state; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,48 @@ |
|||||||
|
import * as consoleFrames from '../console/frames'; |
||||||
|
import * as histories from '../content/histories'; |
||||||
|
import * as scrolls from '../content/scrolls'; |
||||||
|
import Follow from '../content/follow'; |
||||||
|
import actions from '../actions'; |
||||||
|
|
||||||
|
export default function reducer(state, action = {}) { |
||||||
|
switch (action.type) { |
||||||
|
case actions.CMD_OPEN: |
||||||
|
return consoleFrames.showCommand(''); |
||||||
|
case actions.CMD_TABS_OPEN: |
||||||
|
if (action.alter) { |
||||||
|
// alter url
|
||||||
|
return consoleFrames.showCommand('open ' + window.location.href); |
||||||
|
} else { |
||||||
|
return consoleFrames.showCommand('open '); |
||||||
|
} |
||||||
|
case actions.CMD_BUFFER: |
||||||
|
return consoleFrames.showCommand('buffer '); |
||||||
|
case actions.SCROLL_LINES: |
||||||
|
scrolls.scrollLines(window, action.count); |
||||||
|
break; |
||||||
|
case actions.SCROLL_PAGES: |
||||||
|
scrolls.scrollPages(window, action.count); |
||||||
|
break; |
||||||
|
case actions.SCROLL_TOP: |
||||||
|
scrolls.scrollTop(window); |
||||||
|
break; |
||||||
|
case actions.SCROLL_BOTTOM: |
||||||
|
scrolls.scrollBottom(window); |
||||||
|
break; |
||||||
|
case actions.SCROLL_LEFT: |
||||||
|
scrolls.scrollLeft(window); |
||||||
|
break; |
||||||
|
case actions.SCROLL_RIGHT: |
||||||
|
scrolls.scrollRight(window); |
||||||
|
break; |
||||||
|
case actions.FOLLOW_START: |
||||||
|
new Follow(window.document, action.newTab); |
||||||
|
break; |
||||||
|
case actions.HISTORY_PREV: |
||||||
|
histories.prev(window); |
||||||
|
break; |
||||||
|
case actions.HISTORY_NEXT: |
||||||
|
histories.next(window); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,23 @@ |
|||||||
|
import actions from '../actions'; |
||||||
|
|
||||||
|
const defaultState = { |
||||||
|
keys: [], |
||||||
|
}; |
||||||
|
|
||||||
|
export default function reducer(state = defaultState, action = {}) { |
||||||
|
switch (action.type) { |
||||||
|
case actions.INPUT_KEY_PRESS: |
||||||
|
return Object.assign({}, state, { |
||||||
|
keys: state.keys.concat([{ |
||||||
|
code: action.code, |
||||||
|
ctrl: action.ctrl |
||||||
|
}]) |
||||||
|
}); |
||||||
|
case actions.INPUT_CLEAR_KEYS: |
||||||
|
return Object.assign({}, state, { |
||||||
|
keys: [], |
||||||
|
}); |
||||||
|
default: |
||||||
|
return state; |
||||||
|
} |
||||||
|
} |
@ -1,54 +0,0 @@ |
|||||||
export const CMD_OPEN = 'cmd.open'; |
|
||||||
export const CMD_TABS_OPEN = 'cmd.tabs.open'; |
|
||||||
export const CMD_BUFFER = 'cmd.buffer'; |
|
||||||
export const TABS_CLOSE = 'tabs.close'; |
|
||||||
export const TABS_REOPEN = 'tabs.reopen'; |
|
||||||
export const TABS_PREV = 'tabs.prev'; |
|
||||||
export const TABS_NEXT = 'tabs.next'; |
|
||||||
export const TABS_RELOAD = 'tabs.reload'; |
|
||||||
export const SCROLL_LINES = 'scroll.lines'; |
|
||||||
export const SCROLL_PAGES = 'scroll.pages'; |
|
||||||
export const SCROLL_TOP = 'scroll.top'; |
|
||||||
export const SCROLL_BOTTOM = 'scroll.bottom'; |
|
||||||
export const SCROLL_LEFT= 'scroll.left'; |
|
||||||
export const SCROLL_RIGHT= 'scroll.right'; |
|
||||||
export const FOLLOW_START = 'follow.start'; |
|
||||||
export const HISTORY_PREV = 'history.prev'; |
|
||||||
export const HISTORY_NEXT = 'history.next'; |
|
||||||
export const ZOOM_IN = 'zoom.in'; |
|
||||||
export const ZOOM_OUT = 'zoom.out'; |
|
||||||
export const ZOOM_NEUTRAL = 'zoom.neutral'; |
|
||||||
|
|
||||||
const BACKGROUND_ACTION_SET = new Set([ |
|
||||||
TABS_CLOSE, |
|
||||||
TABS_REOPEN, |
|
||||||
TABS_PREV, |
|
||||||
TABS_NEXT, |
|
||||||
TABS_RELOAD, |
|
||||||
ZOOM_IN, |
|
||||||
ZOOM_OUT, |
|
||||||
ZOOM_NEUTRAL |
|
||||||
]); |
|
||||||
|
|
||||||
const CONTENT_ACTION_SET = new Set([ |
|
||||||
CMD_OPEN, |
|
||||||
CMD_TABS_OPEN, |
|
||||||
CMD_BUFFER, |
|
||||||
SCROLL_LINES, |
|
||||||
SCROLL_PAGES, |
|
||||||
SCROLL_TOP, |
|
||||||
SCROLL_BOTTOM, |
|
||||||
SCROLL_LEFT, |
|
||||||
SCROLL_RIGHT, |
|
||||||
FOLLOW_START, |
|
||||||
HISTORY_PREV, |
|
||||||
HISTORY_NEXT |
|
||||||
]); |
|
||||||
|
|
||||||
export const isBackgroundAction = (action) => { |
|
||||||
return BACKGROUND_ACTION_SET.has(action); |
|
||||||
}; |
|
||||||
|
|
||||||
export const isContentAction = (action) => { |
|
||||||
return CONTENT_ACTION_SET.has(action); |
|
||||||
}; |
|
@ -1,19 +0,0 @@ |
|||||||
const receive = (win, callback) => { |
|
||||||
win.addEventListener('message', (e) => { |
|
||||||
let message; |
|
||||||
try { |
|
||||||
message = JSON.parse(e.data); |
|
||||||
} catch (e) { |
|
||||||
// ignore message posted by author of web page
|
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
callback(message); |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
const send = (win, message) => { |
|
||||||
win.postMessage(JSON.stringify(message), '*'); |
|
||||||
} |
|
||||||
|
|
||||||
export { receive, send }; |
|
@ -0,0 +1,14 @@ |
|||||||
|
import { expect } from "chai"; |
||||||
|
import actions from '../../src/actions'; |
||||||
|
import * as backgroundActions from '../../src/actions/background'; |
||||||
|
|
||||||
|
describe("background actions", () => { |
||||||
|
describe("requestCompletions", () => { |
||||||
|
it('create BACKGROUND_REQUEST_COMPLETIONS action', () => { |
||||||
|
let action = backgroundActions.requestCompletions('buffer hoge fuga'); |
||||||
|
expect(action.type).to.equal(actions.BACKGROUND_REQUEST_COMPLETIONS); |
||||||
|
expect(action.command).to.equal('buffer'); |
||||||
|
expect(action.keywords).to.equal('hoge fuga'); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,51 @@ |
|||||||
|
import { expect } from "chai"; |
||||||
|
import actions from '../../src/actions'; |
||||||
|
import * as commandActions from '../../src/actions/command'; |
||||||
|
|
||||||
|
describe("command actions", () => { |
||||||
|
describe("exec", () => { |
||||||
|
context("open command", () => { |
||||||
|
it('create COMMAND_OPEN_URL acion with a full url', () => { |
||||||
|
let action = commandActions.exec("open https://github.com/") |
||||||
|
expect(action.type).to.equal(actions.COMMAND_OPEN_URL); |
||||||
|
expect(action.url).to.equal('https://github.com/'); |
||||||
|
}); |
||||||
|
|
||||||
|
it('create COMMAND_OPEN_URL acion with a domain name', () => { |
||||||
|
let action = commandActions.exec("open github.com") |
||||||
|
expect(action.type).to.equal(actions.COMMAND_OPEN_URL); |
||||||
|
expect(action.url).to.equal('http://github.com'); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
context("tabopen command", () => { |
||||||
|
it('create COMMAND_TABOPEN_URL acion with a full url', () => { |
||||||
|
let action = commandActions.exec("tabopen https://github.com/") |
||||||
|
expect(action.type).to.equal(actions.COMMAND_TABOPEN_URL); |
||||||
|
expect(action.url).to.equal('https://github.com/'); |
||||||
|
}); |
||||||
|
|
||||||
|
it('create COMMAND_TABOPEN_URL acion with a domain name', () => { |
||||||
|
let action = commandActions.exec("tabopen github.com") |
||||||
|
expect(action.type).to.equal(actions.COMMAND_TABOPEN_URL); |
||||||
|
expect(action.url).to.equal('http://github.com'); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
context("buffer command", () => { |
||||||
|
it('create COMMAND_BUFFER acion with a keywords', () => { |
||||||
|
let action = commandActions.exec("buffer foo bar") |
||||||
|
expect(action.type).to.equal(actions.COMMAND_BUFFER); |
||||||
|
expect(action.keywords).to.equal('foo bar'); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
context("b command", () => { |
||||||
|
it('create COMMAND_BUFFER acion with a keywords', () => { |
||||||
|
let action = commandActions.exec("b foo bar") |
||||||
|
expect(action.type).to.equal(actions.COMMAND_BUFFER); |
||||||
|
expect(action.keywords).to.equal('foo bar'); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,37 @@ |
|||||||
|
import { expect } from "chai"; |
||||||
|
import actions from '../../src/actions'; |
||||||
|
import * as consoleActions from '../../src/actions/console'; |
||||||
|
|
||||||
|
describe("console actions", () => { |
||||||
|
describe("showCommand", () => { |
||||||
|
it('create CONSOLE_SHOW_COMMAND action', () => { |
||||||
|
let action = consoleActions.showCommand('hello'); |
||||||
|
expect(action.type).to.equal(actions.CONSOLE_SHOW_COMMAND); |
||||||
|
expect(action.text).to.equal('hello'); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe("setCompletions", () => { |
||||||
|
it('create CONSOLE_SET_COMPLETIONS action', () => { |
||||||
|
let action = consoleActions.setCompletions([1,2,3]); |
||||||
|
expect(action.type).to.equal(actions.CONSOLE_SET_COMPLETIONS); |
||||||
|
expect(action.completions).to.deep.equal([1, 2, 3]); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe("showError", () => { |
||||||
|
it('create CONSOLE_SHOW_ERROR action', () => { |
||||||
|
let action = consoleActions.showError('an error'); |
||||||
|
expect(action.type).to.equal(actions.CONSOLE_SHOW_ERROR); |
||||||
|
expect(action.text).to.equal('an error'); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe("hide", () => { |
||||||
|
it('create CONSOLE_HIDE action', () => { |
||||||
|
let action = consoleActions.hide(); |
||||||
|
expect(action.type).to.equal(actions.CONSOLE_HIDE); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
@ -0,0 +1,21 @@ |
|||||||
|
import { expect } from "chai"; |
||||||
|
import actions from '../../src/actions'; |
||||||
|
import * as inputActions from '../../src/actions/input'; |
||||||
|
|
||||||
|
describe("input actions", () => { |
||||||
|
describe("keyPress", () => { |
||||||
|
it('create INPUT_KEY_PRESS action', () => { |
||||||
|
let action = inputActions.keyPress(123, true); |
||||||
|
expect(action.type).to.equal(actions.INPUT_KEY_PRESS); |
||||||
|
expect(action.code).to.equal(123); |
||||||
|
expect(action.ctrl).to.be.true; |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe("clearKeys", () => { |
||||||
|
it('create INPUT_CLEAR_KEYSaction', () => { |
||||||
|
let action = inputActions.clearKeys(); |
||||||
|
expect(action.type).to.equal(actions.INPUT_CLEAR_KEYS); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
@ -1,50 +0,0 @@ |
|||||||
import { expect } from "chai"; |
|
||||||
import KeyQueue from '../../src/background/key-queue'; |
|
||||||
|
|
||||||
describe("keyQueue class", () => { |
|
||||||
const KEYMAP = { |
|
||||||
'g<C-X>GG': [], |
|
||||||
'gg': [ 'scroll.top' ], |
|
||||||
}; |
|
||||||
|
|
||||||
const g = 'g'.charCodeAt(0); |
|
||||||
const G = 'G'.charCodeAt(0); |
|
||||||
const x = 'x'.charCodeAt(0); |
|
||||||
|
|
||||||
describe("#push", () => { |
|
||||||
it("returns matched action", () => { |
|
||||||
let queue = new KeyQueue(KEYMAP); |
|
||||||
queue.push({ code: g }); |
|
||||||
let action = queue.push({ code: g }); |
|
||||||
|
|
||||||
expect(action).to.deep.equal([ 'scroll.top' ]); |
|
||||||
}); |
|
||||||
|
|
||||||
it("returns null on no actions matched", () => { |
|
||||||
let queue = new KeyQueue(KEYMAP); |
|
||||||
queue.push({ code: g }); |
|
||||||
let action = queue.push({ code: G }); |
|
||||||
|
|
||||||
expect(action).to.be.null; |
|
||||||
expect(queue.asKeymapChars()).to.be.empty; |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('#asKeymapChars', () => { |
|
||||||
let queue = new KeyQueue(KEYMAP); |
|
||||||
queue.push({ code: g }); |
|
||||||
queue.push({ code: x, ctrl: true }); |
|
||||||
queue.push({ code: G }); |
|
||||||
|
|
||||||
expect(queue.asKeymapChars()).to.equal('g<C-X>G'); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('#asCaretChars', () => { |
|
||||||
let queue = new KeyQueue(KEYMAP); |
|
||||||
queue.push({ code: g }); |
|
||||||
queue.push({ code: x, ctrl: true }); |
|
||||||
queue.push({ code: G }); |
|
||||||
|
|
||||||
expect(queue.asCaretChars()).to.equal('g^XG'); |
|
||||||
}); |
|
||||||
}); |
|
@ -1,55 +1,31 @@ |
|||||||
import { expect } from "chai"; |
import { expect } from "chai"; |
||||||
import { identifyKey, identifyKeys, hasPrefix } from '../../src/background/keys'; |
import * as keys from '../../src/background/keys'; |
||||||
|
|
||||||
describe('keys', () => { |
describe("keys", () => { |
||||||
describe('#identifyKey', () => { |
const KEYMAP = { |
||||||
it('return true if key matched', () => { |
'g<C-X>GG': [], |
||||||
expect(identifyKey( |
'gg': { type: 'scroll.top' }, |
||||||
{ code: 100 }, |
}; |
||||||
{ code: 100 })).to.be.true; |
|
||||||
expect(identifyKey( |
|
||||||
{ code: 100, shift: true, ctrl: true }, |
|
||||||
{ code: 100, shift: true, ctrl: true })).to.be.true; |
|
||||||
expect(identifyKey( |
|
||||||
{ code: 100, shift: false, ctrl: false }, |
|
||||||
{ code: 100 })).to.be.true; |
|
||||||
}); |
|
||||||
|
|
||||||
it('return false if key not matched', () => { |
const g = 'g'.charCodeAt(0); |
||||||
expect(identifyKey( |
const G = 'G'.charCodeAt(0); |
||||||
{ code: 100 }, |
const x = 'x'.charCodeAt(0); |
||||||
{ code: 101 })).to.be.false; |
|
||||||
expect(identifyKey( |
|
||||||
{ code: 100, shift: true, ctrl: true }, |
|
||||||
{ code: 100, shift: true })).to.be.false; |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('#identifyKeys', () => { |
describe('#asKeymapChars', () => { |
||||||
it ('return true if keys matched', () => { |
let keySequence = [ |
||||||
let keys = [{ code: 100 }, { code: 101, ctrl: false}]; |
{ code: g }, |
||||||
let prefix = [{ code: 100, ctrl: false }, { code: 101 }]; |
{ code: x, ctrl: true }, |
||||||
expect(hasPrefix(keys, prefix)).to.be.true; |
{ code: G } |
||||||
|
]; |
||||||
|
expect(keys.asKeymapChars(keySequence)).to.equal('g<C-X>G'); |
||||||
}); |
}); |
||||||
|
|
||||||
it ('return false if keys matched', () => { |
describe('#asCaretChars', () => { |
||||||
let keys = [{ code: 100 }, { code: 101, ctrl: true }]; |
let keySequence = [ |
||||||
let prefix = [{ code: 100 }, { code: 101 }]; |
{ code: g }, |
||||||
expect(hasPrefix(keys, prefix)).to.be.false; |
{ code: x, ctrl: true }, |
||||||
}); |
{ code: G } |
||||||
}); |
]; |
||||||
|
expect(keys.asCaretChars(keySequence)).to.equal('g^XG'); |
||||||
describe('#hasPrefix', () => { |
|
||||||
it ('return true if prefix matched', () => { |
|
||||||
let keys = [{ code: 100 }, { code: 101 }, { code: 102 }]; |
|
||||||
let prefix = [{ code: 100 }, { code: 101 }]; |
|
||||||
expect(hasPrefix(keys, prefix)).to.be.true; |
|
||||||
}); |
|
||||||
|
|
||||||
it ('return false if prefix not matched', () => { |
|
||||||
let keys = [{ code: 100 }, { code: 101 }, { code: 102 }]; |
|
||||||
let prefix = [{ code: 102 }]; |
|
||||||
expect(hasPrefix(keys, prefix)).to.be.false; |
|
||||||
}); |
|
||||||
}); |
}); |
||||||
}); |
}); |
||||||
|
@ -0,0 +1,43 @@ |
|||||||
|
import { expect } from "chai"; |
||||||
|
import actions from '../../src/actions'; |
||||||
|
import consoleReducer from '../../src/reducers/console'; |
||||||
|
|
||||||
|
describe("console reducer", () => { |
||||||
|
it('return the initial state', () => { |
||||||
|
let state = consoleReducer(undefined, {}); |
||||||
|
expect(state).to.have.property('errorShown', false); |
||||||
|
expect(state).to.have.property('errorText', ''); |
||||||
|
expect(state).to.have.property('commandShown', false); |
||||||
|
expect(state).to.have.property('commandText', ''); |
||||||
|
expect(state).to.have.deep.property('completions', []); |
||||||
|
}); |
||||||
|
|
||||||
|
it('return next state for CONSOLE_SHOW_COMMAND', () => { |
||||||
|
let action = { type: actions.CONSOLE_SHOW_COMMAND, text: 'open ' }; |
||||||
|
let state = consoleReducer({}, action); |
||||||
|
expect(state).to.have.property('commandShown', true); |
||||||
|
expect(state).to.have.property('commandText', 'open '); |
||||||
|
expect(state).to.have.property('errorShown', false); |
||||||
|
}); |
||||||
|
|
||||||
|
it('return next state for CONSOLE_SET_COMPLETIONS', () => { |
||||||
|
let action = { type: actions.CONSOLE_SET_COMPLETIONS, completions: [1, 2, 3] }; |
||||||
|
let state = consoleReducer({}, action); |
||||||
|
expect(state).to.have.deep.property('completions', [1, 2, 3]); |
||||||
|
}); |
||||||
|
|
||||||
|
it('return next state for CONSOLE_SHOW_ERROR', () => { |
||||||
|
let action = { type: actions.CONSOLE_SHOW_ERROR, text: 'an error' }; |
||||||
|
let state = consoleReducer({}, action); |
||||||
|
expect(state).to.have.property('errorShown', true); |
||||||
|
expect(state).to.have.property('errorText', 'an error'); |
||||||
|
expect(state).to.have.property('commandShown', false); |
||||||
|
}); |
||||||
|
|
||||||
|
it('return next state for CONSOLE_HIDE', () => { |
||||||
|
let action = { type: actions.CONSOLE_HIDE }; |
||||||
|
let state = consoleReducer({}, action); |
||||||
|
expect(state).to.have.property('errorShown', false); |
||||||
|
expect(state).to.have.property('commandShown', false); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,34 @@ |
|||||||
|
import { expect } from "chai"; |
||||||
|
import actions from '../../src/actions'; |
||||||
|
import inputReducer from '../../src/reducers/input'; |
||||||
|
|
||||||
|
describe("input reducer", () => { |
||||||
|
it('return the initial state', () => { |
||||||
|
let state = inputReducer(undefined, {}); |
||||||
|
expect(state).to.have.deep.property('keys', []); |
||||||
|
}); |
||||||
|
|
||||||
|
it('return next state for INPUT_KEY_PRESS', () => { |
||||||
|
let action = { type: actions.INPUT_KEY_PRESS, code: 123, ctrl: true }; |
||||||
|
let state = inputReducer(undefined, action); |
||||||
|
expect(state).to.have.deep.property('keys', [{ code: 123, ctrl: true }]); |
||||||
|
|
||||||
|
action = { type: actions.INPUT_KEY_PRESS, code: 456, ctrl: false }; |
||||||
|
state = inputReducer(state, action); |
||||||
|
expect(state).to.have.deep.property('keys', [ |
||||||
|
{ code: 123, ctrl: true }, |
||||||
|
{ code: 456, ctrl: false } |
||||||
|
]); |
||||||
|
}); |
||||||
|
|
||||||
|
it('return next state for INPUT_CLEAR_KEYS', () => { |
||||||
|
let action = { type: actions.INPUT_CLEAR_KEYS }; |
||||||
|
let state = inputReducer({ |
||||||
|
keys: [ |
||||||
|
{ code: 123, ctrl: true }, |
||||||
|
{ code: 456, ctrl: false } |
||||||
|
] |
||||||
|
}, action); |
||||||
|
expect(state).to.have.deep.property('keys', []); |
||||||
|
}); |
||||||
|
}); |
@ -1,25 +0,0 @@ |
|||||||
import { expect } from "chai"; |
|
||||||
import * as messages from '../../src/shared/messages'; |
|
||||||
|
|
||||||
describe('messages', () => { |
|
||||||
describe('#receive', () => { |
|
||||||
it('received a message', (done) => { |
|
||||||
messages.receive(window, (message) => { |
|
||||||
expect(message).to.deep.equal({ type: 'vimvixen.test' }); |
|
||||||
done(); |
|
||||||
}); |
|
||||||
window.postMessage(JSON.stringify({ type: 'vimvixen.test' }), '*'); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('#send', () => { |
|
||||||
it('sends a message', (done) => { |
|
||||||
window.addEventListener('message', (e) => { |
|
||||||
let json = JSON.parse(e.data); |
|
||||||
expect(json).to.deep.equal({ type: 'vimvixen.test' }); |
|
||||||
done(); |
|
||||||
}); |
|
||||||
messages.send(window, { type: 'vimvixen.test' }); |
|
||||||
}); |
|
||||||
}); |
|
||||||
}); |
|
Reference in new issue