diff options
| author | Shin'ya Ueoka <ueokande@i-beam.org> | 2017-10-08 15:19:25 +0900 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2017-10-08 15:19:25 +0900 | 
| commit | 0183161145d36cbafb7dbd86ca3a1aac6faca43f (patch) | |
| tree | aed757bfb5f8789156439d1e1fdff4e221376aaa /src/console | |
| parent | 0f54a203dba38acdd080a928cee95f875fe84706 (diff) | |
| parent | 57f798044d32ba7f9dc10a34ac31ad5dbdbf56ae (diff) | |
Merge pull request #22 from ueokande/separate-domains
Refactor: Separate domains
Diffstat (limited to 'src/console')
| -rw-r--r-- | src/console/actions/console.js | 44 | ||||
| -rw-r--r-- | src/console/actions/index.js | 9 | ||||
| -rw-r--r-- | src/console/components/completion.js | 61 | ||||
| -rw-r--r-- | src/console/components/console.js | 148 | ||||
| -rw-r--r-- | src/console/index.html | 20 | ||||
| -rw-r--r-- | src/console/index.js | 34 | ||||
| -rw-r--r-- | src/console/reducers/index.js | 94 | ||||
| -rw-r--r-- | src/console/site.scss | 92 | 
8 files changed, 502 insertions, 0 deletions
| diff --git a/src/console/actions/console.js b/src/console/actions/console.js new file mode 100644 index 0000000..01d9a9b --- /dev/null +++ b/src/console/actions/console.js @@ -0,0 +1,44 @@ +import actions from 'console/actions'; + +const showCommand = (text) => { +  return { +    type: actions.CONSOLE_SHOW_COMMAND, +    text: text +  }; +}; + +const showError = (text) => { +  return { +    type: actions.CONSOLE_SHOW_ERROR, +    text: text +  }; +}; + +const hide = () => { +  return { +    type: actions.CONSOLE_HIDE +  }; +}; + +const setCompletions = (completions) => { +  return { +    type: actions.CONSOLE_SET_COMPLETIONS, +    completions: completions +  }; +}; + +const completionNext = () => { +  return { +    type: actions.CONSOLE_COMPLETION_NEXT, +  }; +}; + +const completionPrev = () => { +  return { +    type: actions.CONSOLE_COMPLETION_PREV, +  }; +}; + +export { +  showCommand, showError, hide, setCompletions, completionNext, completionPrev +}; diff --git a/src/console/actions/index.js b/src/console/actions/index.js new file mode 100644 index 0000000..a5d03bc --- /dev/null +++ b/src/console/actions/index.js @@ -0,0 +1,9 @@ +export default { +  // console commands +  CONSOLE_SHOW_COMMAND: 'console.show.command', +  CONSOLE_SET_COMPLETIONS: 'console.set.completions', +  CONSOLE_SHOW_ERROR: 'console.show.error', +  CONSOLE_HIDE: 'console.hide', +  CONSOLE_COMPLETION_NEXT: 'console.completion.next', +  CONSOLE_COMPLETION_PREV: 'console.completion.prev', +}; diff --git a/src/console/components/completion.js b/src/console/components/completion.js new file mode 100644 index 0000000..5033b5c --- /dev/null +++ b/src/console/components/completion.js @@ -0,0 +1,61 @@ +export default class Completion { +  constructor(wrapper, store) { +    this.wrapper = wrapper; +    this.store = store; +    this.prevState = {}; +  } + +  update() { +    let state = this.store.getState(); +    if (JSON.stringify(this.prevState) === JSON.stringify(state)) { +      return; +    } + +    this.wrapper.innerHTML = ''; + +    for (let i = 0; i < state.completions.length; ++i) { +      let group = state.completions[i]; +      let title = this.createCompletionTitle(group.name); +      this.wrapper.append(title); + +      for (let j = 0; j < group.items.length; ++j) { +        let item = group.items[j]; +        let li = this.createCompletionItem(item.icon, item.caption, item.url); +        this.wrapper.append(li); + +        if (i === state.groupSelection && j === state.itemSelection) { +          li.classList.add('vimvixen-completion-selected'); +        } +      } +    } + +    this.prevState = state; +  } + +  createCompletionTitle(text) { +    let doc = this.wrapper.ownerDocument; +    let li = doc.createElement('li'); +    li.className = 'vimvixen-console-completion-title'; +    li.textContent = text; +    return li; +  } + +  createCompletionItem(icon, caption, url) { +    let doc = this.wrapper.ownerDocument; + +    let captionEle = doc.createElement('span'); +    captionEle.className = 'vimvixen-console-completion-item-caption'; +    captionEle.textContent = caption; + +    let urlEle = doc.createElement('span'); +    urlEle.className = 'vimvixen-console-completion-item-url'; +    urlEle.textContent = url; + +    let li = doc.createElement('li'); +    li.style.backgroundImage = 'url(' + icon + ')'; +    li.className = 'vimvixen-console-completion-item'; +    li.append(captionEle); +    li.append(urlEle); +    return li; +  } +} diff --git a/src/console/components/console.js b/src/console/components/console.js new file mode 100644 index 0000000..9023d91 --- /dev/null +++ b/src/console/components/console.js @@ -0,0 +1,148 @@ +import messages from 'shared/messages'; +import * as consoleActions from 'console/actions/console'; + +export default class ConsoleComponent { +  constructor(wrapper, store) { +    this.wrapper = wrapper; +    this.prevValue = ''; +    this.prevState = {}; +    this.completionOrigin = ''; +    this.store = store; + +    let doc = this.wrapper.ownerDocument; +    let input = doc.querySelector('#vimvixen-console-command-input'); +    input.addEventListener('blur', this.onBlur.bind(this)); +    input.addEventListener('keydown', this.onKeyDown.bind(this)); +    input.addEventListener('keyup', this.onKeyUp.bind(this)); + +    this.hideCommand(); +    this.hideError(); +  } + +  onBlur() { +    return browser.runtime.sendMessage({ +      type: messages.CONSOLE_BLURRED, +    }); +  } + +  onKeyDown(e) { +    let doc = this.wrapper.ownerDocument; +    let input = doc.querySelector('#vimvixen-console-command-input'); + +    switch (e.keyCode) { +    case KeyboardEvent.DOM_VK_ESCAPE: +      return input.blur(); +    case KeyboardEvent.DOM_VK_RETURN: +      return browser.runtime.sendMessage({ +        type: messages.CONSOLE_ENTERED, +        text: e.target.value +      }).then(this.onBlur); +    case KeyboardEvent.DOM_VK_TAB: +      if (e.shiftKey) { +        this.store.dispatch(consoleActions.completionPrev()); +      } else { +        this.store.dispatch(consoleActions.completionNext()); +      } +      e.stopPropagation(); +      e.preventDefault(); +      break; +    } +  } + +  onKeyUp(e) { +    if (e.keyCode === KeyboardEvent.DOM_VK_TAB) { +      return; +    } +    if (e.target.value === this.prevValue) { +      return; +    } + +    let doc = this.wrapper.ownerDocument; +    let input = doc.querySelector('#vimvixen-console-command-input'); +    this.completionOrigin = input.value; + +    this.prevValue = e.target.value; +    return browser.runtime.sendMessage({ +      type: messages.CONSOLE_QUERY_COMPLETIONS, +      text: e.target.value +    }).then((completions) => { +      this.store.dispatch(consoleActions.setCompletions(completions)); +    }); +  } + +  update() { +    let state = this.store.getState(); +    if (!this.prevState.commandShown && state.commandShown) { +      this.showCommand(state.commandText); +    } else if (!state.commandShown) { +      this.hideCommand(); +    } + +    if (state.errorShown) { +      this.setErrorText(state.errorText); +      this.showError(); +    } else { +      this.hideError(); +    } + +    if (state.groupSelection >= 0 && state.itemSelection >= 0) { +      let group = state.completions[state.groupSelection]; +      let item = group.items[state.itemSelection]; +      this.setCommandValue(item.content); +    } else if (state.completions.length > 0 && +      JSON.stringify(this.prevState.completions) === +        JSON.stringify(state.completions)) { +      // Reset input only completion groups not changed (unselected an item in +      // completion) in order to avoid to override previous input +      this.setCommandCompletionOrigin(); +    } + +    this.prevState = state; +  } + +  showCommand(text) { +    let doc = this.wrapper.ownerDocument; +    let command = doc.querySelector('#vimvixen-console-command'); +    let input = doc.querySelector('#vimvixen-console-command-input'); + +    command.style.display = 'block'; +    input.value = text; +    input.focus(); +  } + +  hideCommand() { +    let doc = this.wrapper.ownerDocument; +    let command = doc.querySelector('#vimvixen-console-command'); +    command.style.display = 'none'; +  } + +  setCommandValue(value) { +    let doc = this.wrapper.ownerDocument; +    let input = doc.querySelector('#vimvixen-console-command-input'); +    input.value = value; +  } + +  setCommandCompletionOrigin() { +    let doc = this.wrapper.ownerDocument; +    let input = doc.querySelector('#vimvixen-console-command-input'); +    input.value = this.completionOrigin; +  } + +  setErrorText(text) { +    let doc = this.wrapper.ownerDocument; +    let error = doc.querySelector('#vimvixen-console-error'); +    error.textContent = text; +  } + +  showError() { +    let doc = this.wrapper.ownerDocument; +    let error = doc.querySelector('#vimvixen-console-error'); +    error.style.display = 'block'; +  } + +  hideError() { +    let doc = this.wrapper.ownerDocument; +    let error = doc.querySelector('#vimvixen-console-error'); +    error.style.display = 'none'; +  } +} diff --git a/src/console/index.html b/src/console/index.html new file mode 100644 index 0000000..4222f12 --- /dev/null +++ b/src/console/index.html @@ -0,0 +1,20 @@ +<!doctype html> +<html> +  <head> +    <meta charset=utf-8 /> +    <title>VimVixen console</title> +    <script src='console.js'></script> +  </head> +  <body class='vimvixen-console'> +    <p id='vimvixen-console-error' +       class='vimvixen-console-error'></p> +    <div id='vimvixen-console-command'> +      <ul id='vimvixen-console-completion' class='vimvixen-console-completion'></ul> +      <div class='vimvixen-console-command'> +        <i class='vimvixen-console-command-prompt'></i><input +          id='vimvixen-console-command-input' +          class='vimvixen-console-command-input'></input> +      </div> +    </div> +  </body> +</html> diff --git a/src/console/index.js b/src/console/index.js new file mode 100644 index 0000000..7396a96 --- /dev/null +++ b/src/console/index.js @@ -0,0 +1,34 @@ +import './site.scss'; +import messages from 'shared/messages'; +import CompletionComponent from 'console/components/completion'; +import ConsoleComponent from 'console/components/console'; +import reducers from 'console/reducers'; +import { createStore } from 'shared/store'; +import * as consoleActions from 'console/actions/console'; + +const store = createStore(reducers); +let completionComponent = null; +let consoleComponent = null; + +window.addEventListener('load', () => { +  let wrapper = document.querySelector('#vimvixen-console-completion'); +  completionComponent = new CompletionComponent(wrapper, store); + +  consoleComponent = new ConsoleComponent(document.body, store); +}); + +store.subscribe(() => { +  completionComponent.update(); +  consoleComponent.update(); +}); + +browser.runtime.onMessage.addListener((action) => { +  switch (action.type) { +  case messages.CONSOLE_SHOW_COMMAND: +    return store.dispatch(consoleActions.showCommand(action.command)); +  case messages.CONSOLE_SHOW_ERROR: +    return store.dispatch(consoleActions.showError(action.text)); +  case messages.CONSOLE_HIDE: +    return store.dispatch(consoleActions.hide(action.command)); +  } +}); diff --git a/src/console/reducers/index.js b/src/console/reducers/index.js new file mode 100644 index 0000000..ee9c691 --- /dev/null +++ b/src/console/reducers/index.js @@ -0,0 +1,94 @@ +import actions from 'console/actions'; + +const defaultState = { +  errorShown: false, +  errorText: '', +  commandShown: false, +  commandText: '', +  completions: [], +  groupSelection: -1, +  itemSelection: -1, +}; + +const nextSelection = (state) => { +  if (state.groupSelection < 0) { +    return [0, 0]; +  } + +  let group = state.completions[state.groupSelection]; +  if (state.groupSelection + 1 >= state.completions.length && +    state.itemSelection + 1 >= group.items.length) { +    return [-1, -1]; +  } +  if (state.itemSelection + 1 >= group.items.length) { +    return [state.groupSelection + 1, 0]; +  } +  return [state.groupSelection, state.itemSelection + 1]; +}; + +const prevSelection = (state) => { +  if (state.groupSelection < 0) { +    return [ +      state.completions.length - 1, +      state.completions[state.completions.length - 1].items.length - 1 +    ]; +  } +  if (state.groupSelection === 0 && state.itemSelection === 0) { +    return [-1, -1]; +  } else if (state.itemSelection === 0) { +    return [ +      state.groupSelection - 1, +      state.completions[state.groupSelection - 1].items.length - 1 +    ]; +  } +  return [state.groupSelection, state.itemSelection - 1]; +}; + +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_SHOW_ERROR: +    return Object.assign({}, state, { +      errorText: action.text, +      errorShown: true, +      commandShown: false, +    }); +  case actions.CONSOLE_HIDE: +    if (state.errorShown) { +      // keep error message if shown +      return state; +    } +    return Object.assign({}, state, { +      errorShown: false, +      commandShown: false +    }); +  case actions.CONSOLE_SET_COMPLETIONS: +    return Object.assign({}, state, { +      completions: action.completions, +      groupSelection: -1, +      itemSelection: -1, +    }); +  case actions.CONSOLE_COMPLETION_NEXT: { +    let next = nextSelection(state); +    return Object.assign({}, state, { +      groupSelection: next[0], +      itemSelection: next[1], +    }); +  } +  case actions.CONSOLE_COMPLETION_PREV: { +    let next = prevSelection(state); +    return Object.assign({}, state, { +      groupSelection: next[0], +      itemSelection: next[1], +    }); +  } +  default: +    return state; +  } +} diff --git a/src/console/site.scss b/src/console/site.scss new file mode 100644 index 0000000..5823dce --- /dev/null +++ b/src/console/site.scss @@ -0,0 +1,92 @@ +html, body, * { +  margin: 0; +  padding: 0; +} + +body { +  position: absolute; +  bottom: 0; +  left: 0; +  right: 0; +  overflow: hidden; +} + +.vimvixen-console { +  border-top: 1px solid gray; +  bottom: 0; +  margin: 0; +  padding: 0; + +  @mixin consoole-font { +    font-style: normal; +    font-family: monospace; +    font-size: 12px; +    line-height: 16px; +  } + +  &-completion { +    background-color: white; + +    @include consoole-font; + +    &-title { +      background-color: lightgray; +      font-weight: bold; +      margin: 0; +      padding: 0; +    } + +    &-item { +      padding-left: 1.5rem; +      background-position: 0 center; +      background-size: contain; +      background-repeat: no-repeat; +      white-space: nowrap; + +      &.vimvixen-completion-selected { +        background-color: yellow; +      } + +      &-caption { +        display: inline-block; +        width: 40%; +        text-overflow: ellipsis; +        overflow: hidden; +      } + +      &-url { +        display: inline-block; +        color: green; +        width: 60%; +        text-overflow: ellipsis; +        overflow: hidden; +      } +    } +  } + +  &-error { +    background-color: red; +    font-weight: bold; +    color: white; + +    @include consoole-font; +  } + +  &-command { +    background-color: white; +    display: flex; + +    &-prompt:before { +      content: ':'; + +      @include consoole-font; +    } + +    &-input { +      border: none; +      flex-grow: 1; + +      @include consoole-font; +    } +  } +} | 
