Merge branch 'more-redux'
This commit is contained in:
		
						commit
						c5529958d5
					
				
					 20 changed files with 481 additions and 358 deletions
				
			
		| 
						 | 
					@ -1,11 +0,0 @@
 | 
				
			||||||
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
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,5 @@
 | 
				
			||||||
import actions from '../actions';
 | 
					import * as tabs from '../background/tabs';
 | 
				
			||||||
 | 
					import * as consoleActions from './console';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const normalizeUrl = (string) => {
 | 
					const normalizeUrl = (string) => {
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
| 
						 | 
					@ -8,28 +9,76 @@ const normalizeUrl = (string) => {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function exec(line) {
 | 
					const openCommand = (url) => {
 | 
				
			||||||
  let name = line.split(' ')[0];
 | 
					  return browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => {
 | 
				
			||||||
  let remaining = line.replace(name + ' ', '');
 | 
					    if (tabs.length > 0) {
 | 
				
			||||||
 | 
					      return browser.tabs.update(tabs[0].id, { url: url });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const tabopenCommand = (url) => {
 | 
				
			||||||
 | 
					  return browser.tabs.create({ url: url });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const bufferCommand = (keywords) => {
 | 
				
			||||||
 | 
					  return browser.tabs.query({ active: true, currentWindow: true }).then((tabss) => {
 | 
				
			||||||
 | 
					    if (tabss.length > 0) {
 | 
				
			||||||
 | 
					      if (isNaN(keywords)) {
 | 
				
			||||||
 | 
					        return tabs.selectByKeyword(tabss[0], keywords);
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        let index = parseInt(keywords, 10) - 1;
 | 
				
			||||||
 | 
					        return tabs.selectAt(index);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const doCommand = (name, remaining) => {
 | 
				
			||||||
  switch (name) {
 | 
					  switch (name) {
 | 
				
			||||||
  case 'open':
 | 
					  case 'open':
 | 
				
			||||||
    // TODO use search engined and pass keywords to them
 | 
					    // TODO use search engined and pass keywords to them
 | 
				
			||||||
    return {
 | 
					    return openCommand(normalizeUrl(remaining));
 | 
				
			||||||
      type: actions.COMMAND_OPEN_URL,
 | 
					 | 
				
			||||||
      url: normalizeUrl(remaining)
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  case 'tabopen':
 | 
					  case 'tabopen':
 | 
				
			||||||
    return {
 | 
					    return tabopenCommand(normalizeUrl(remaining));
 | 
				
			||||||
      type: actions.COMMAND_TABOPEN_URL,
 | 
					 | 
				
			||||||
      url: normalizeUrl(remaining)
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  case 'b':
 | 
					  case 'b':
 | 
				
			||||||
  case 'buffer':
 | 
					  case 'buffer':
 | 
				
			||||||
    return {
 | 
					    return bufferCommand(remaining);
 | 
				
			||||||
      type: actions.COMMAND_BUFFER,
 | 
					 | 
				
			||||||
      keywords: remaining
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  throw new Error(name + ' command is not defined');
 | 
					  throw new Error(name + ' command is not defined');
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getCompletions = (command, keywords) => {
 | 
				
			||||||
 | 
					  switch (command) {
 | 
				
			||||||
 | 
					  case '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
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      return [{
 | 
				
			||||||
 | 
					        name: "Buffers",
 | 
				
			||||||
 | 
					        items: items
 | 
				
			||||||
 | 
					      }];
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return Promise.resolve([]);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function exec(line) {
 | 
				
			||||||
 | 
					  let name = line.split(' ')[0];
 | 
				
			||||||
 | 
					  let remaining = line.replace(name + ' ', '');
 | 
				
			||||||
 | 
					  return doCommand(name, remaining).then(() => {
 | 
				
			||||||
 | 
					    return consoleActions.hide();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function complete(line) {
 | 
				
			||||||
 | 
					  let command = line.split(' ', 1)[0];
 | 
				
			||||||
 | 
					  let keywords = line.replace(command + ' ', '');
 | 
				
			||||||
 | 
					  return getCompletions(command, keywords).then(consoleActions.setCompletions);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,36 +5,7 @@ export default {
 | 
				
			||||||
  CONSOLE_SHOW_ERROR: 'vimvixen.console.show.error',
 | 
					  CONSOLE_SHOW_ERROR: 'vimvixen.console.show.error',
 | 
				
			||||||
  CONSOLE_HIDE: 'vimvixen.console.hide',
 | 
					  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
 | 
					  // 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',
 | 
				
			||||||
 | 
					 | 
				
			||||||
  COMMAND_OPEN_URL: 'command.open.url',
 | 
					 | 
				
			||||||
  COMMAND_TABOPEN_URL: 'command.tabopen.url',
 | 
					 | 
				
			||||||
  COMMAND_BUFFER: 'command.buffer',
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										43
									
								
								src/actions/operation.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/actions/operation.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,43 @@
 | 
				
			||||||
 | 
					import operations from '../operations';
 | 
				
			||||||
 | 
					import messages  from '../messages';
 | 
				
			||||||
 | 
					import * as consoleActions from './console';
 | 
				
			||||||
 | 
					import * as tabs from '../background/tabs';
 | 
				
			||||||
 | 
					import * as zooms from '../background/zooms';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function exec(operation, tab) {
 | 
				
			||||||
 | 
					  switch (operation.type) {
 | 
				
			||||||
 | 
					  case operations.TABS_CLOSE:
 | 
				
			||||||
 | 
					    return tabs.closeTab(tab.id);
 | 
				
			||||||
 | 
					  case operations.TABS_REOPEN:
 | 
				
			||||||
 | 
					    return tabs.reopenTab();
 | 
				
			||||||
 | 
					  case operations.TABS_PREV:
 | 
				
			||||||
 | 
					    return tabs.selectPrevTab(tab.index, operation.count);
 | 
				
			||||||
 | 
					  case operations.TABS_NEXT:
 | 
				
			||||||
 | 
					    return tabs.selectNextTab(tab.index, operation.count);
 | 
				
			||||||
 | 
					  case operations.TABS_RELOAD:
 | 
				
			||||||
 | 
					    return tabs.reload(tab, operation.cache);
 | 
				
			||||||
 | 
					  case operations.ZOOM_IN:
 | 
				
			||||||
 | 
					    return zooms.zoomIn();
 | 
				
			||||||
 | 
					  case operations.ZOOM_OUT:
 | 
				
			||||||
 | 
					    return zooms.zoomOut();
 | 
				
			||||||
 | 
					  case operations.ZOOM_NEUTRAL:
 | 
				
			||||||
 | 
					    return zooms.neutral();
 | 
				
			||||||
 | 
					  case operations.COMMAND_OPEN:
 | 
				
			||||||
 | 
					    return consoleActions.showCommand('');
 | 
				
			||||||
 | 
					  case operations.COMMAND_TABS_OPEN:
 | 
				
			||||||
 | 
					    if (operations.alter) {
 | 
				
			||||||
 | 
					      // alter url
 | 
				
			||||||
 | 
					      return consoleActions.showCommand('open ' + tab.url);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      return consoleActions.showCommand('open ');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  case operations.COMMAND_BUFFER:
 | 
				
			||||||
 | 
					    return consoleActions.showCommand('buffer ');
 | 
				
			||||||
 | 
					  default:
 | 
				
			||||||
 | 
					    return browser.tabs.sendMessage(tab.id, {
 | 
				
			||||||
 | 
					      type: messages.CONTENT_OPERATION,
 | 
				
			||||||
 | 
					      operation
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,45 +1,75 @@
 | 
				
			||||||
import * as keys from './keys';
 | 
					import * as keys from './keys';
 | 
				
			||||||
import * as inputActions from '../actions/input';
 | 
					import * as inputActions from '../actions/input';
 | 
				
			||||||
import backgroundReducers from '../reducers/background';
 | 
					import * as operationActions from '../actions/operation';
 | 
				
			||||||
import commandReducer from '../reducers/command';
 | 
					import * as commandActions from '../actions/command';
 | 
				
			||||||
import inputReducers from '../reducers/input';
 | 
					import * as consoleActions from '../actions/console';
 | 
				
			||||||
 | 
					import reducers from '../reducers';
 | 
				
			||||||
 | 
					import messages from '../messages';
 | 
				
			||||||
 | 
					import * as store from '../store'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let inputState = inputReducers(undefined, {});
 | 
					let prevInput = [];
 | 
				
			||||||
 | 
					const backgroundStore = store.createStore(reducers, (e, sender) => {
 | 
				
			||||||
const keyQueueChanged = (sender, prevState, state) => {
 | 
					  console.error('Vim-Vixen:', e);
 | 
				
			||||||
  if (state.keys.length === 0) {
 | 
					  if (sender) {
 | 
				
			||||||
    return Promise.resolve();
 | 
					    backgroundStore.dispatch(consoleActions.showError(e.message), sender);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					backgroundStore.subscribe((sender) => {
 | 
				
			||||||
 | 
					  let currentInput = backgroundStore.getState().input
 | 
				
			||||||
 | 
					  if (JSON.stringify(prevInput) === JSON.stringify(currentInput)) {
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  prevInput = currentInput;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let prefix = keys.asKeymapChars(state.keys);
 | 
					  if (currentInput.keys.length === 0) {
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (sender) {
 | 
				
			||||||
 | 
					    return keyQueueChanged(backgroundStore.getState(), sender);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					backgroundStore.subscribe((sender) => {
 | 
				
			||||||
 | 
					  if (sender) {
 | 
				
			||||||
 | 
					    return browser.tabs.sendMessage(sender.tab.id, {
 | 
				
			||||||
 | 
					      type: messages.STATE_UPDATE,
 | 
				
			||||||
 | 
					      state: backgroundStore.getState()
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const keyQueueChanged = (state, sender) => {
 | 
				
			||||||
 | 
					  let prefix = keys.asKeymapChars(state.input.keys);
 | 
				
			||||||
  let matched = Object.keys(keys.defaultKeymap).filter((keys) => {
 | 
					  let matched = Object.keys(keys.defaultKeymap).filter((keys) => {
 | 
				
			||||||
    return keys.startsWith(prefix);
 | 
					    return keys.startsWith(prefix);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
  if (matched.length == 0) {
 | 
					  if (matched.length == 0) {
 | 
				
			||||||
    return handleMessage(inputActions.clearKeys(), sender);
 | 
					    backgroundStore.dispatch(inputActions.clearKeys(), sender);
 | 
				
			||||||
 | 
					    return Promise.resolve();
 | 
				
			||||||
  } else if (matched.length > 1 || matched.length === 1 && prefix !== matched[0]) {
 | 
					  } else if (matched.length > 1 || matched.length === 1 && prefix !== matched[0]) {
 | 
				
			||||||
    return Promise.resolve();
 | 
					    return Promise.resolve();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  let action = keys.defaultKeymap[matched];
 | 
					  let action = keys.defaultKeymap[matched];
 | 
				
			||||||
  return handleMessage(inputActions.clearKeys(), sender).then(() => {
 | 
					  backgroundStore.dispatch(operationActions.exec(action, sender.tab), sender);
 | 
				
			||||||
    return backgroundReducers(undefined, action, sender).then(() => {
 | 
					  backgroundStore.dispatch(inputActions.clearKeys(), sender);
 | 
				
			||||||
      return browser.tabs.sendMessage(sender.tab.id, action);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const handleMessage = (action, sender) => {
 | 
					const handleMessage = (message, sender) => {
 | 
				
			||||||
  let nextInputState = inputReducers(inputState, action);
 | 
					  switch (message.type) {
 | 
				
			||||||
  if (JSON.stringify(nextInputState) !== JSON.stringify(inputState)) {
 | 
					  case messages.KEYDOWN:
 | 
				
			||||||
    let prevState = inputState;
 | 
					    return backgroundStore.dispatch(inputActions.keyPress(message.code, message.ctrl), sender);
 | 
				
			||||||
    inputState = nextInputState;
 | 
					  case messages.CONSOLE_BLURRED:
 | 
				
			||||||
    return keyQueueChanged(sender, prevState, inputState);
 | 
					    return backgroundStore.dispatch(consoleActions.hide(), sender);
 | 
				
			||||||
 | 
					  case messages.CONSOLE_ENTERED:
 | 
				
			||||||
 | 
					    return backgroundStore.dispatch(commandActions.exec(message.text), sender);
 | 
				
			||||||
 | 
					  case messages.CONSOLE_CHANGEED:
 | 
				
			||||||
 | 
					    return backgroundStore.dispatch(commandActions.complete(message.text), sender);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return backgroundReducers(undefined, action, sender).then(() => {
 | 
					}
 | 
				
			||||||
    return commandReducer(undefined, action, sender).then(() => {
 | 
					 | 
				
			||||||
      return browser.tabs.sendMessage(sender.tab.id, action);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
browser.runtime.onMessage.addListener(handleMessage);
 | 
					browser.runtime.onMessage.addListener((message, sender) => {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    handleMessage(message, sender);
 | 
				
			||||||
 | 
					  } catch (e) {
 | 
				
			||||||
 | 
					    backgroundStore.dispatch(consoleActions.showError(e.message), sender);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,35 +1,35 @@
 | 
				
			||||||
import actions from '../actions';
 | 
					import operations from '../operations';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const defaultKeymap = {
 | 
					const defaultKeymap = {
 | 
				
			||||||
  ':': { type: actions.CMD_OPEN },
 | 
					  ':': { type: operations.COMMAND_OPEN },
 | 
				
			||||||
  'o': { type: actions.CMD_TABS_OPEN, alter: false },
 | 
					  'o': { type: operations.COMMAND_TABS_OPEN, alter: false },
 | 
				
			||||||
  'O': { type: actions.CMD_TABS_OPEN, alter: true },
 | 
					  'O': { type: operations.COMMAND_TABS_OPEN, alter: true },
 | 
				
			||||||
  'b': { type: actions.CMD_BUFFER },
 | 
					  'b': { type: operations.COMMAND_BUFFER },
 | 
				
			||||||
  'k': { type: actions.SCROLL_LINES, count: -1 },
 | 
					  'k': { type: operations.SCROLL_LINES, count: -1 },
 | 
				
			||||||
  'j': { type: actions.SCROLL_LINES, count: 1 },
 | 
					  'j': { type: operations.SCROLL_LINES, count: 1 },
 | 
				
			||||||
  '<C-E>': { type: actions.SCROLL_LINES, count: -1 },
 | 
					  '<C-E>': { type: operations.SCROLL_LINES, count: -1 },
 | 
				
			||||||
  '<C-Y>': { type: actions.SCROLL_LINES, count: 1 },
 | 
					  '<C-Y>': { type: operations.SCROLL_LINES, count: 1 },
 | 
				
			||||||
  '<C-U>': { type: actions.SCROLL_PAGES, count: -0.5 },
 | 
					  '<C-U>': { type: operations.SCROLL_PAGES, count: -0.5 },
 | 
				
			||||||
  '<C-D>': { type: actions.SCROLL_PAGES, count: 0.5 },
 | 
					  '<C-D>': { type: operations.SCROLL_PAGES, count: 0.5 },
 | 
				
			||||||
  '<C-B>': { type: actions.SCROLL_PAGES, count: -1 },
 | 
					  '<C-B>': { type: operations.SCROLL_PAGES, count: -1 },
 | 
				
			||||||
  '<C-F>': { type: actions.SCROLL_PAGES, count: 1 },
 | 
					  '<C-F>': { type: operations.SCROLL_PAGES, count: 1 },
 | 
				
			||||||
  'gg': { type: actions.SCROLL_TOP },
 | 
					  'gg': { type: operations.SCROLL_TOP },
 | 
				
			||||||
  'G': { type: actions.SCROLL_BOTTOM },
 | 
					  'G': { type: operations.SCROLL_BOTTOM },
 | 
				
			||||||
  '0': { type: actions.SCROLL_LEFT },
 | 
					  '0': { type: operations.SCROLL_LEFT },
 | 
				
			||||||
  '$': { type: actions.SCROLL_RIGHT },
 | 
					  '$': { type: operations.SCROLL_RIGHT },
 | 
				
			||||||
  'd': { type: actions.TABS_CLOSE },
 | 
					  'd': { type: operations.TABS_CLOSE },
 | 
				
			||||||
  'u': { type: actions.TABS_REOPEN },
 | 
					  'u': { type: operations.TABS_REOPEN },
 | 
				
			||||||
  'h': { type: actions.TABS_PREV, count: 1 },
 | 
					  'h': { type: operations.TABS_PREV, count: 1 },
 | 
				
			||||||
  'l': { type: actions.TABS_NEXT, count: 1 },
 | 
					  'l': { type: operations.TABS_NEXT, count: 1 },
 | 
				
			||||||
  'r': { type: actions.TABS_RELOAD, cache: false },
 | 
					  'r': { type: operations.TABS_RELOAD, cache: false },
 | 
				
			||||||
  'R': { type: actions.TABS_RELOAD, cache: true },
 | 
					  'R': { type: operations.TABS_RELOAD, cache: true },
 | 
				
			||||||
  'zi': { type: actions.ZOOM_IN },
 | 
					  'zi': { type: operations.ZOOM_IN },
 | 
				
			||||||
  'zo': { type: actions.ZOOM_OUT },
 | 
					  'zo': { type: operations.ZOOM_OUT },
 | 
				
			||||||
  'zz': { type: actions.ZOOM_NEUTRAL },
 | 
					  'zz': { type: operations.ZOOM_NEUTRAL },
 | 
				
			||||||
  'f': { type: actions.FOLLOW_START, newTab: false },
 | 
					  'f': { type: operations.FOLLOW_START, newTab: false },
 | 
				
			||||||
  'F': { type: actions.FOLLOW_START, newTab: true },
 | 
					  'F': { type: operations.FOLLOW_START, newTab: true },
 | 
				
			||||||
  'H': { type: actions.HISTORY_PREV },
 | 
					  'H': { type: operations.HISTORY_PREV },
 | 
				
			||||||
  'L': { type: actions.HISTORY_NEXT },
 | 
					  'L': { type: operations.HISTORY_NEXT },
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const asKeymapChars = (keys) => {
 | 
					const asKeymapChars = (keys) => {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,18 +1,17 @@
 | 
				
			||||||
import './console.scss';
 | 
					import './console.scss';
 | 
				
			||||||
import * as backgroundActions from '../actions/background';
 | 
					 | 
				
			||||||
import * as consoleActions from '../actions/console';
 | 
					 | 
				
			||||||
import * as commandActions from '../actions/command';
 | 
					 | 
				
			||||||
import Completion from './completion';
 | 
					import Completion from './completion';
 | 
				
			||||||
import consoleReducer from '../reducers/console';
 | 
					import messages from '../messages';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO consider object-oriented
 | 
					// TODO consider object-oriented
 | 
				
			||||||
var prevValue = "";
 | 
					var prevValue = "";
 | 
				
			||||||
var completion = null;
 | 
					var completion = null;
 | 
				
			||||||
var completionOrigin = "";
 | 
					var completionOrigin = "";
 | 
				
			||||||
let state = consoleReducer(undefined, {});
 | 
					var prevState = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const handleBlur = () => {
 | 
					const handleBlur = () => {
 | 
				
			||||||
  return browser.runtime.sendMessage(consoleActions.hide());
 | 
					  return browser.runtime.sendMessage({
 | 
				
			||||||
 | 
					    type: messages.CONSOLE_BLURRED,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const completeNext = () => {
 | 
					const completeNext = () => {
 | 
				
			||||||
| 
						 | 
					@ -52,7 +51,10 @@ const handleKeydown = (e) => {
 | 
				
			||||||
  case KeyboardEvent.DOM_VK_ESCAPE:
 | 
					  case KeyboardEvent.DOM_VK_ESCAPE:
 | 
				
			||||||
    return input.blur();
 | 
					    return input.blur();
 | 
				
			||||||
  case KeyboardEvent.DOM_VK_RETURN:
 | 
					  case KeyboardEvent.DOM_VK_RETURN:
 | 
				
			||||||
    return browser.runtime.sendMessage(commandActions.exec(e.target.value));
 | 
					    return browser.runtime.sendMessage({
 | 
				
			||||||
 | 
					      type: messages.CONSOLE_ENTERED,
 | 
				
			||||||
 | 
					      text: e.target.value
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
  case KeyboardEvent.DOM_VK_TAB:
 | 
					  case KeyboardEvent.DOM_VK_TAB:
 | 
				
			||||||
    if (e.shiftKey) {
 | 
					    if (e.shiftKey) {
 | 
				
			||||||
      completePrev();
 | 
					      completePrev();
 | 
				
			||||||
| 
						 | 
					@ -73,9 +75,10 @@ const handleKeyup = (e) => {
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  prevValue = e.target.value;
 | 
					  prevValue = e.target.value;
 | 
				
			||||||
  return browser.runtime.sendMessage(
 | 
					  return browser.runtime.sendMessage({
 | 
				
			||||||
    backgroundActions.requestCompletions(e.target.value)
 | 
					    type: messages.CONSOLE_CHANGEED,
 | 
				
			||||||
  );
 | 
					    text: e.target.value
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
window.addEventListener('load', () => {
 | 
					window.addEventListener('load', () => {
 | 
				
			||||||
| 
						 | 
					@ -147,7 +150,7 @@ const updateCompletions = (completions) => {
 | 
				
			||||||
  completionOrigin = input.value.split(' ')[0];
 | 
					  completionOrigin = input.value.split(' ')[0];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const update = (prevState, state) => {
 | 
					const update = (state) => {
 | 
				
			||||||
  let error = window.document.querySelector('#vimvixen-console-error');
 | 
					  let error = window.document.querySelector('#vimvixen-console-error');
 | 
				
			||||||
  let command = window.document.querySelector('#vimvixen-console-command');
 | 
					  let command = window.document.querySelector('#vimvixen-console-command');
 | 
				
			||||||
  let input = window.document.querySelector('#vimvixen-console-command-input');
 | 
					  let input = window.document.querySelector('#vimvixen-console-command-input');
 | 
				
			||||||
| 
						 | 
					@ -156,25 +159,26 @@ const update = (prevState, state) => {
 | 
				
			||||||
  error.textContent = state.errorText;
 | 
					  error.textContent = state.errorText;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  command.style.display = state.commandShown ? 'block' : 'none';
 | 
					  command.style.display = state.commandShown ? 'block' : 'none';
 | 
				
			||||||
  if (!prevState.commandShown && state.commandShown) {
 | 
					  if (state.commandShown && !prevState.commandShown) {
 | 
				
			||||||
    // setup input on firstly shown
 | 
					 | 
				
			||||||
    input.value = state.commandText;
 | 
					    input.value = state.commandText;
 | 
				
			||||||
    input.focus();
 | 
					    input.focus();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (JSON.stringify(state.completions) !== JSON.stringify(prevState.completions)) {
 | 
					  if (JSON.stringify(state.completions) !== JSON.stringify(prevState.completions)) {
 | 
				
			||||||
    updateCompletions(state.completions);
 | 
					    updateCompletions(state.completions);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  prevState = state;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
browser.runtime.onMessage.addListener((action) => {
 | 
					browser.runtime.onMessage.addListener((action) => {
 | 
				
			||||||
  let nextState = consoleReducer(state, action);
 | 
					  if (action.type === messages.STATE_UPDATE) {
 | 
				
			||||||
  if (JSON.stringify(nextState) !== JSON.stringify(state)) {
 | 
					    return update(action.state.console);
 | 
				
			||||||
    update(state, nextState);
 | 
					 | 
				
			||||||
    state = nextState;
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
window.addEventListener('load', () => {
 | 
					window.addEventListener('load', () => {
 | 
				
			||||||
  update({}, state);
 | 
					  let error = window.document.querySelector('#vimvixen-console-error');
 | 
				
			||||||
 | 
					  let command = window.document.querySelector('#vimvixen-console-command');
 | 
				
			||||||
 | 
					  error.style.display = 'none';
 | 
				
			||||||
 | 
					  command.style.display = 'none';
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,4 @@
 | 
				
			||||||
import './console-frame.scss';
 | 
					import './console-frame.scss';
 | 
				
			||||||
import * as consoleActions from '../actions/console';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const initialize = (doc) => {
 | 
					const initialize = (doc) => {
 | 
				
			||||||
  let iframe = doc.createElement('iframe');
 | 
					  let iframe = doc.createElement('iframe');
 | 
				
			||||||
| 
						 | 
					@ -11,17 +10,9 @@ const initialize = (doc) => {
 | 
				
			||||||
  return 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) => {
 | 
					const blur = (doc) => {
 | 
				
			||||||
  let iframe = doc.getElementById('vimvixen-console-frame');
 | 
					  let iframe = doc.getElementById('vimvixen-console-frame');
 | 
				
			||||||
  iframe.blur();
 | 
					  iframe.blur();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export { initialize, showCommand, showError, blur };
 | 
					export { initialize, blur };
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,40 +1,61 @@
 | 
				
			||||||
import '../console/console-frame.scss';
 | 
					import '../console/console-frame.scss';
 | 
				
			||||||
import * as inputActions from '../actions/input';
 | 
					 | 
				
			||||||
import * as consoleFrames from '../console/frames';
 | 
					import * as consoleFrames from '../console/frames';
 | 
				
			||||||
import actions from '../actions';
 | 
					import * as scrolls from '../content/scrolls';
 | 
				
			||||||
import contentReducer from '../reducers/content';
 | 
					import * as histories from '../content/histories';
 | 
				
			||||||
 | 
					import Follow from '../content/follow';
 | 
				
			||||||
 | 
					import operations from '../operations';
 | 
				
			||||||
 | 
					import messages  from '../messages';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
consoleFrames.initialize(window.document);
 | 
					consoleFrames.initialize(window.document);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
browser.runtime.onMessage.addListener((action) => {
 | 
					 | 
				
			||||||
  contentReducer(undefined, action);
 | 
					 | 
				
			||||||
  return Promise.resolve();
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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))
 | 
					  browser.runtime.sendMessage({
 | 
				
			||||||
    .catch((err) => {
 | 
					    type: messages.KEYDOWN,
 | 
				
			||||||
      console.error("Vim Vixen:", err);
 | 
					    code: e.which,
 | 
				
			||||||
      return consoleFrames.showError(err.message);
 | 
					    ctrl: e.ctrl
 | 
				
			||||||
    });
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const execOperation = (operation) => {
 | 
				
			||||||
 | 
					  switch (operation.type) {
 | 
				
			||||||
 | 
					  case operations.SCROLL_LINES:
 | 
				
			||||||
 | 
					    return scrolls.scrollLines(window, operation.count);
 | 
				
			||||||
 | 
					  case operations.SCROLL_PAGES:
 | 
				
			||||||
 | 
					    return scrolls.scrollPages(window, operation.count);
 | 
				
			||||||
 | 
					  case operations.SCROLL_TOP:
 | 
				
			||||||
 | 
					    return scrolls.scrollTop(window);
 | 
				
			||||||
 | 
					  case operations.SCROLL_BOTTOM:
 | 
				
			||||||
 | 
					    return scrolls.scrollBottom(window);
 | 
				
			||||||
 | 
					  case operations.SCROLL_LEFT:
 | 
				
			||||||
 | 
					    return scrolls.scrollLeft(window);
 | 
				
			||||||
 | 
					  case operations.SCROLL_RIGHT:
 | 
				
			||||||
 | 
					    return scrolls.scrollRight(window);
 | 
				
			||||||
 | 
					  case operations.FOLLOW_START:
 | 
				
			||||||
 | 
					    return new Follow(window.document, operation.newTab);
 | 
				
			||||||
 | 
					  case operations.HISTORY_PREV:
 | 
				
			||||||
 | 
					    return histories.prev(window);
 | 
				
			||||||
 | 
					  case operations.HISTORY_NEXT:
 | 
				
			||||||
 | 
					    return histories.next(window);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const update = (state) => {
 | 
				
			||||||
 | 
					  if (!state.console.commandShown) {
 | 
				
			||||||
 | 
					    window.focus();
 | 
				
			||||||
 | 
					    consoleFrames.blur(window.document);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
browser.runtime.onMessage.addListener((action) => {
 | 
					browser.runtime.onMessage.addListener((action) => {
 | 
				
			||||||
  switch (action.type) {
 | 
					  switch (action.type) {
 | 
				
			||||||
  case actions.CONSOLE_HIDE:
 | 
					  case messages.STATE_UPDATE:
 | 
				
			||||||
    window.focus();
 | 
					    return update(action.state);
 | 
				
			||||||
    return consoleFrames.blur(window.document);
 | 
					  case messages.CONTENT_OPERATION:
 | 
				
			||||||
  case 'vimvixen.command.enter':
 | 
					    execOperation(action.operation);
 | 
				
			||||||
    return browser.runtime.sendMessage({
 | 
					    return Promise.resolve();
 | 
				
			||||||
      type: 'event.cmd.enter',
 | 
					 | 
				
			||||||
      text: action.value
 | 
					 | 
				
			||||||
    }).catch((err) => {
 | 
					 | 
				
			||||||
      console.error("Vim Vixen:", err);
 | 
					 | 
				
			||||||
      return consoleFrames.showError(err.message);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  default:
 | 
					  default:
 | 
				
			||||||
    return Promise.resolve();
 | 
					    return Promise.resolve();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										10
									
								
								src/messages/index.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/messages/index.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,10 @@
 | 
				
			||||||
 | 
					export default {
 | 
				
			||||||
 | 
					  STATE_UPDATE: 'state.update',
 | 
				
			||||||
 | 
					  CONTENT_OPERATION: 'content.operation',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  CONSOLE_BLURRED: 'console.blured',
 | 
				
			||||||
 | 
					  CONSOLE_ENTERED: 'console.entered',
 | 
				
			||||||
 | 
					  CONSOLE_CHANGEED: 'console.changed',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  KEYDOWN: 'keydown'
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										26
									
								
								src/operations/index.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/operations/index.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,26 @@
 | 
				
			||||||
 | 
					export default {
 | 
				
			||||||
 | 
					  // command
 | 
				
			||||||
 | 
					  COMMAND_OPEN: 'cmd.open',
 | 
				
			||||||
 | 
					  COMMAND_TABS_OPEN: 'cmd.tabs.open',
 | 
				
			||||||
 | 
					  COMMAND_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',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // background
 | 
				
			||||||
 | 
					  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',
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,53 +0,0 @@
 | 
				
			||||||
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();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,24 +0,0 @@
 | 
				
			||||||
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();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -28,10 +28,13 @@ export default function reducer(state = defaultState, action = {}) {
 | 
				
			||||||
      commandShown: false,
 | 
					      commandShown: false,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  case actions.CONSOLE_HIDE:
 | 
					  case actions.CONSOLE_HIDE:
 | 
				
			||||||
 | 
					    if (state.errorShown) {
 | 
				
			||||||
 | 
					      // keep error message if shown
 | 
				
			||||||
 | 
					      return state;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    return Object.assign({}, state, {
 | 
					    return Object.assign({}, state, {
 | 
				
			||||||
      errorShown: false,
 | 
					      errorShown: false,
 | 
				
			||||||
      commandShown: false
 | 
					      commandShown: false
 | 
				
			||||||
      
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  default:
 | 
					  default:
 | 
				
			||||||
    return state;
 | 
					    return state;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,48 +0,0 @@
 | 
				
			||||||
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;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										14
									
								
								src/reducers/index.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/reducers/index.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,14 @@
 | 
				
			||||||
 | 
					import inputReducer from '../reducers/input';
 | 
				
			||||||
 | 
					import consoleReducer from '../reducers/console';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const defaultState = {
 | 
				
			||||||
 | 
					  input: inputReducer(undefined, {}),
 | 
				
			||||||
 | 
					  console: consoleReducer(undefined, {})
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function reducer(state = defaultState, action = {}) {
 | 
				
			||||||
 | 
					  return Object.assign({}, state, {
 | 
				
			||||||
 | 
					    input: inputReducer(state.input, action),
 | 
				
			||||||
 | 
					    console: consoleReducer(state.console, action)
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										51
									
								
								src/store/index.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/store/index.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,51 @@
 | 
				
			||||||
 | 
					class Store {
 | 
				
			||||||
 | 
					  constructor(reducer, catcher) {
 | 
				
			||||||
 | 
					    this.reducer = reducer;
 | 
				
			||||||
 | 
					    this.catcher = catcher;
 | 
				
			||||||
 | 
					    this.subscribers = [];
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      this.state = this.reducer(undefined, {});
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      catcher(e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  dispatch(action, sender) {
 | 
				
			||||||
 | 
					    if (action instanceof Promise) {
 | 
				
			||||||
 | 
					      action.then((a) => {
 | 
				
			||||||
 | 
					        this.transitNext(a, sender);
 | 
				
			||||||
 | 
					      }).catch((e) => {
 | 
				
			||||||
 | 
					        this.catcher(e, sender);
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        this.transitNext(action, sender);
 | 
				
			||||||
 | 
					      } catch (e) {
 | 
				
			||||||
 | 
					        this.catcher(e, sender);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return action
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getState() {
 | 
				
			||||||
 | 
					    return this.state;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  subscribe(callback) {
 | 
				
			||||||
 | 
					    this.subscribers.push(callback);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  transitNext(action, sender) {
 | 
				
			||||||
 | 
					    let newState = this.reducer(this.state, action);
 | 
				
			||||||
 | 
					    if (JSON.stringify(this.state) !== JSON.stringify(newState)) {
 | 
				
			||||||
 | 
					      this.state = newState;
 | 
				
			||||||
 | 
					      this.subscribers.forEach(f => f(sender));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const empty = () => {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function createStore(reducer, catcher = empty) {
 | 
				
			||||||
 | 
					  return new Store(reducer, catcher);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,14 +0,0 @@
 | 
				
			||||||
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');
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,51 +0,0 @@
 | 
				
			||||||
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');
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
							
								
								
									
										111
									
								
								test/store/index.test.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								test/store/index.test.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,111 @@
 | 
				
			||||||
 | 
					import { expect } from "chai";
 | 
				
			||||||
 | 
					import { createStore } from '../../src/store';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe("Store class", () => {
 | 
				
			||||||
 | 
					  const reducer = (state, action) => {
 | 
				
			||||||
 | 
					    if (state == undefined) {
 | 
				
			||||||
 | 
					      return 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return state + action;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe("#dispatch", () => {
 | 
				
			||||||
 | 
					    it('transit status by immediate action', () => {
 | 
				
			||||||
 | 
					      let store = createStore(reducer);
 | 
				
			||||||
 | 
					      store.dispatch(10);
 | 
				
			||||||
 | 
					      expect(store.getState()).to.equal(10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      store.dispatch(-20);
 | 
				
			||||||
 | 
					      expect(store.getState()).to.equal(-10);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('returns next state by immediate action', () => {
 | 
				
			||||||
 | 
					      let store = createStore(reducer);
 | 
				
			||||||
 | 
					      let dispatchedAction = store.dispatch(11);
 | 
				
			||||||
 | 
					      expect(dispatchedAction).to.equal(11);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('transit status by Promise action', () => {
 | 
				
			||||||
 | 
					      let store = createStore(reducer);
 | 
				
			||||||
 | 
					      let p1 = Promise.resolve(10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return store.dispatch(p1).then(() => {
 | 
				
			||||||
 | 
					        expect(store.getState()).to.equal(10);
 | 
				
			||||||
 | 
					      }).then(() => {
 | 
				
			||||||
 | 
					        store.dispatch(Promise.resolve(-20));
 | 
				
			||||||
 | 
					      }).then(() => {
 | 
				
			||||||
 | 
					        expect(store.getState()).to.equal(-10);
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('returns next state by promise action', () => {
 | 
				
			||||||
 | 
					      let store = createStore(reducer);
 | 
				
			||||||
 | 
					      let dispatchedAction = store.dispatch(Promise.resolve(11));
 | 
				
			||||||
 | 
					      return dispatchedAction.then((value) => {
 | 
				
			||||||
 | 
					        expect(value).to.equal(11);
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe("#subscribe", () => {
 | 
				
			||||||
 | 
					    it('invoke callback', (done) => {
 | 
				
			||||||
 | 
					      let store = createStore(reducer);
 | 
				
			||||||
 | 
					      store.subscribe(() => {
 | 
				
			||||||
 | 
					        expect(store.getState()).to.equal(15);
 | 
				
			||||||
 | 
					        done();
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      store.dispatch(15);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('propagate sender object', (done) => {
 | 
				
			||||||
 | 
					      let store = createStore(reducer);
 | 
				
			||||||
 | 
					      store.subscribe((sender) => {
 | 
				
			||||||
 | 
					        expect(sender).to.equal('sender');
 | 
				
			||||||
 | 
					        done();
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      store.dispatch(15, 'sender');
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe("catcher", () => {
 | 
				
			||||||
 | 
					    it('catch an error in reducer on initializing by immediate action', (done) => {
 | 
				
			||||||
 | 
					      let store = createStore(() => {
 | 
				
			||||||
 | 
					        throw new Error();
 | 
				
			||||||
 | 
					      }, (e) => {
 | 
				
			||||||
 | 
					        expect(e).to.be.an('error');
 | 
				
			||||||
 | 
					        done();
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('catch an error in reducer on initializing by immediate action', (done) => {
 | 
				
			||||||
 | 
					      let store = createStore((state, action) => {
 | 
				
			||||||
 | 
					        if (state === undefined) return 0;
 | 
				
			||||||
 | 
					        throw new Error();
 | 
				
			||||||
 | 
					      }, (e) => {
 | 
				
			||||||
 | 
					        expect(e).to.be.an('error');
 | 
				
			||||||
 | 
					        done();
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      store.dispatch(20);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('catch an error in reducer on initializing by promise action', (done) => {
 | 
				
			||||||
 | 
					      let store = createStore((state, action) => {
 | 
				
			||||||
 | 
					        if (state === undefined) return 0;
 | 
				
			||||||
 | 
					        throw new Error();
 | 
				
			||||||
 | 
					      }, (e) => {
 | 
				
			||||||
 | 
					        expect(e).to.be.an('error');
 | 
				
			||||||
 | 
					        done();
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      store.dispatch(Promise.resolve(20));
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('catch an error in promise action', (done) => {
 | 
				
			||||||
 | 
					      let store = createStore((state, action) => 0, (e) => {
 | 
				
			||||||
 | 
					        expect(e).to.be.an('error');
 | 
				
			||||||
 | 
					        done();
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      store.dispatch(new Promise(() => { throw new Error() }));
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Reference in a new issue