diff --git a/src/console/components/completion.js b/src/console/components/completion.js deleted file mode 100644 index a49a221..0000000 --- a/src/console/components/completion.js +++ /dev/null @@ -1,65 +0,0 @@ -export default class Completion { - constructor(wrapper, store) { - this.wrapper = wrapper; - this.store = store; - this.prevState = {}; - - store.subscribe(() => { - this.update(); - }); - } - - 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 deleted file mode 100644 index 4fc8a53..0000000 --- a/src/console/components/console.js +++ /dev/null @@ -1,175 +0,0 @@ -import * as consoleActions from 'console/actions/console'; - -const inputShownMode = (state) => { - return ['command', 'find'].includes(state.mode); -}; - -export default class ConsoleComponent { - constructor(wrapper, store) { - this.wrapper = wrapper; - this.store = store; - this.prevMode = ''; - - 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('input', this.onInput.bind(this)); - - store.subscribe(() => { - this.update(); - }); - this.update(); - } - - onBlur() { - let state = this.store.getState(); - if (state.mode === 'command' || state.mode === 'find') { - return this.store.dispatch(consoleActions.hideCommand()); - } - } - - doEnter(e) { - e.stopPropagation(); - e.preventDefault(); - - let state = this.store.getState(); - let value = e.target.value; - if (state.mode === 'command') { - return this.store.dispatch(consoleActions.enterCommand(value)); - } else if (state.mode === 'find') { - return this.store.dispatch(consoleActions.enterFind(value)); - } - } - - selectNext(e) { - this.store.dispatch(consoleActions.completionNext()); - e.stopPropagation(); - e.preventDefault(); - } - - selectPrev(e) { - this.store.dispatch(consoleActions.completionPrev()); - e.stopPropagation(); - e.preventDefault(); - } - - onKeyDown(e) { - if (e.keyCode === KeyboardEvent.DOM_VK_ESCAPE && e.ctrlKey) { - this.store.dispatch(consoleActions.hideCommand()); - } - switch (e.keyCode) { - case KeyboardEvent.DOM_VK_ESCAPE: - return this.store.dispatch(consoleActions.hideCommand()); - case KeyboardEvent.DOM_VK_RETURN: - return this.doEnter(e); - case KeyboardEvent.DOM_VK_TAB: - if (e.shiftKey) { - this.store.dispatch(consoleActions.completionPrev()); - } else { - this.store.dispatch(consoleActions.completionNext()); - } - e.stopPropagation(); - e.preventDefault(); - break; - case KeyboardEvent.DOM_VK_OPEN_BRACKET: - if (e.ctrlKey) { - return this.store.dispatch(consoleActions.hideCommand()); - } - break; - case KeyboardEvent.DOM_VK_M: - if (e.ctrlKey) { - return this.doEnter(e); - } - break; - case KeyboardEvent.DOM_VK_N: - if (e.ctrlKey) { - this.selectNext(e); - } - break; - case KeyboardEvent.DOM_VK_P: - if (e.ctrlKey) { - this.selectPrev(e); - } - break; - } - } - - onInput(e) { - let state = this.store.getState(); - let text = e.target.value; - this.store.dispatch(consoleActions.setConsoleText(text)); - if (state.mode === 'command') { - this.store.dispatch(consoleActions.getCompletions(text)); - } - } - - onInputShown(state) { - let doc = this.wrapper.ownerDocument; - let input = doc.querySelector('#vimvixen-console-command-input'); - - input.focus(); - window.focus(); - - if (state.mode === 'command') { - let text = state.consoleText; - input.value = text; - this.store.dispatch(consoleActions.getCompletions(text)); - } - } - - update() { - let state = this.store.getState(); - - this.updateMessage(state); - this.updateCommand(state); - this.updatePrompt(state); - - if (this.prevMode !== state.mode && inputShownMode(state)) { - this.onInputShown(state); - } - this.prevMode = state.mode; - } - - updateMessage(state) { - let doc = this.wrapper.ownerDocument; - let box = doc.querySelector('.vimvixen-console-message'); - let display = 'none'; - let classList = ['vimvixen-console-message']; - - if (state.mode === 'error' || state.mode === 'info') { - display = 'block'; - classList.push('vimvixen-console-' + state.mode); - } - - box.className = classList.join(' '); - box.style.display = display; - box.textContent = state.messageText; - } - - updateCommand(state) { - let doc = this.wrapper.ownerDocument; - let command = doc.querySelector('#vimvixen-console-command'); - let input = doc.querySelector('#vimvixen-console-command-input'); - - let display = 'none'; - if (inputShownMode(state)) { - display = 'block'; - } - - command.style.display = display; - input.value = state.consoleText; - } - - updatePrompt(state) { - let classList = ['vimvixen-console-command-prompt']; - if (inputShownMode(state)) { - classList.push('prompt-' + state.mode); - } - - let doc = this.wrapper.ownerDocument; - let ele = doc.querySelector('.vimvixen-console-command-prompt'); - ele.className = classList.join(' '); - } -} diff --git a/src/console/components/console.jsx b/src/console/components/console.jsx new file mode 100644 index 0000000..70cbb4e --- /dev/null +++ b/src/console/components/console.jsx @@ -0,0 +1,129 @@ +import './console.scss'; +import { connect } from 'preact-redux'; +import { Component, h } from 'preact'; +import Input from './console/input'; +import Completion from './console/completion'; +import Message from './console/message'; +import * as consoleActions from '../../console/actions/console'; + +class ConsoleComponent extends Component { + onBlur() { + if (this.props.mode === 'command' || this.props.mode === 'find') { + return this.context.store.dispatch(consoleActions.hideCommand()); + } + } + + doEnter(e) { + e.stopPropagation(); + e.preventDefault(); + + let value = e.target.value; + if (this.props.mode === 'command') { + return this.context.store.dispatch(consoleActions.enterCommand(value)); + } else if (this.props.mode === 'find') { + return this.context.store.dispatch(consoleActions.enterFind(value)); + } + } + + selectNext(e) { + this.context.store.dispatch(consoleActions.completionNext()); + e.stopPropagation(); + e.preventDefault(); + } + + selectPrev(e) { + this.context.store.dispatch(consoleActions.completionPrev()); + e.stopPropagation(); + e.preventDefault(); + } + + onKeyDown(e) { + if (e.keyCode === KeyboardEvent.DOM_VK_ESCAPE && e.ctrlKey) { + this.context.store.dispatch(consoleActions.hideCommand()); + } + switch (e.keyCode) { + case KeyboardEvent.DOM_VK_ESCAPE: + return this.context.store.dispatch(consoleActions.hideCommand()); + case KeyboardEvent.DOM_VK_RETURN: + return this.doEnter(e); + case KeyboardEvent.DOM_VK_TAB: + if (e.shiftKey) { + this.context.store.dispatch(consoleActions.completionPrev()); + } else { + this.context.store.dispatch(consoleActions.completionNext()); + } + e.stopPropagation(); + e.preventDefault(); + break; + case KeyboardEvent.DOM_VK_OPEN_BRACKET: + if (e.ctrlKey) { + return this.context.store.dispatch(consoleActions.hideCommand()); + } + break; + case KeyboardEvent.DOM_VK_M: + if (e.ctrlKey) { + return this.doEnter(e); + } + break; + case KeyboardEvent.DOM_VK_N: + if (e.ctrlKey) { + this.selectNext(e); + } + break; + case KeyboardEvent.DOM_VK_P: + if (e.ctrlKey) { + this.selectPrev(e); + } + break; + } + } + + onInput(e) { + let text = e.target.value; + this.context.store.dispatch(consoleActions.setConsoleText(text)); + if (this.props.mode === 'command') { + this.context.store.dispatch(consoleActions.getCompletions(text)); + } + } + + + componentDidUpdate(prevProps) { + if (!this.input) { + return; + } + if (prevProps.mode !== 'command' && this.props.mode === 'command') { + this.context.store.dispatch( + consoleActions.getCompletions(this.props.consoleText)); + this.input.focus(); + } else if (prevProps.mode !== 'find' && this.props.mode === 'find') { + this.input.focus(); + } + } + + + render() { + switch (this.props.mode) { + case 'command': + case 'find': + return
+ + { this.input = c; }} + mode={this.props.mode} + onBlur={this.onBlur.bind(this)} + onKeyDown={this.onKeyDown.bind(this)} + onInput={this.onInput.bind(this)} + value={this.props.consoleText} + /> +
; + case 'info': + case 'error': + return + { this.props.messageText } + ; + } + } +} + +const mapStateToProps = state => state; +export default connect(mapStateToProps)(ConsoleComponent); diff --git a/src/console/site.scss b/src/console/components/console.scss similarity index 91% rename from src/console/site.scss rename to src/console/components/console.scss index b9b1016..c0b9b12 100644 --- a/src/console/site.scss +++ b/src/console/components/console.scss @@ -89,18 +89,10 @@ body { background-color: white; display: flex; - &-prompt:before { + &-prompt { @include consoole-font; } - &-prompt.prompt-command:before { - content: ':'; - } - - &-prompt.prompt-find:before { - content: '/'; - } - &-input { border: none; flex-grow: 1; diff --git a/src/console/components/console/completion.jsx b/src/console/components/console/completion.jsx new file mode 100644 index 0000000..c60543b --- /dev/null +++ b/src/console/components/console/completion.jsx @@ -0,0 +1,56 @@ +import { Component, h } from 'preact'; +import { connect } from 'preact-redux'; + +const CompletionTitle = (props) => { + return
  • {props.title}
  • ; +}; + +const CompletionItem = (props) => { + let className = 'vimvixen-console-completion-item'; + if (props.highlight) { + className += ' vimvixen-completion-selected'; + } + return
  • + {props.caption} + {props.url} +
  • ; +}; + + +class CompletionComponent extends Component { + render() { + let eles = []; + for (let i = 0; i < this.props.completions.length; ++i) { + let group = this.props.completions[i]; + eles.push(); + for (let j = 0; j < group.items.length; ++j) { + let item = group.items[j]; + let selected = + i === this.props.groupSelection && + j === this.props.itemSelection; + eles.push(); + } + } + + return ( + + ); + } +} + +const mapStateToProps = state => state; +export default connect(mapStateToProps)(CompletionComponent); diff --git a/src/console/components/console/input.jsx b/src/console/components/console/input.jsx new file mode 100644 index 0000000..d59e6e7 --- /dev/null +++ b/src/console/components/console/input.jsx @@ -0,0 +1,32 @@ +import { Component, h } from 'preact'; + +export default class InputComponent extends Component { + focus() { + this.input.focus(); + } + + render() { + let prompt = ''; + if (this.props.mode === 'command') { + prompt = ':'; + } else if (this.props.mode === 'find') { + prompt = '/'; + } + + return ( +
    + + { prompt } + + { this.input = c; }} + onBlur={this.props.onBlur} + onKeyDown={this.props.onKeyDown} + onInput={this.props.onInput} + value={this.props.value} + /> +
    + ); + } +} diff --git a/src/console/components/console/message.jsx b/src/console/components/console/message.jsx new file mode 100644 index 0000000..126687e --- /dev/null +++ b/src/console/components/console/message.jsx @@ -0,0 +1,18 @@ +import { h } from 'preact'; + +export default function Message(props) { + switch (props.mode) { + case 'error': + return ( +

    + { props.text } +

    + ); + case 'info': + return ( +

    + { props.children } +

    + ); + } +} diff --git a/src/console/index.html b/src/console/index.html index e049b5e..5c1e99c 100644 --- a/src/console/index.html +++ b/src/console/index.html @@ -5,15 +5,5 @@ VimVixen console - -

    -
    -
      -
      - -
      -
      - + diff --git a/src/console/index.js b/src/console/index.jsx similarity index 74% rename from src/console/index.js rename to src/console/index.jsx index 8724a44..c0d1807 100644 --- a/src/console/index.js +++ b/src/console/index.jsx @@ -1,21 +1,25 @@ -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, applyMiddleware } from 'redux'; import promise from 'redux-promise'; import * as consoleActions from 'console/actions/console'; +import { Provider } from 'preact-redux'; +import Console from './components/console'; + +import { render, h } from 'preact'; + const store = createStore( reducers, applyMiddleware(promise), ); window.addEventListener('load', () => { - let wrapper = document.querySelector('#vimvixen-console-completion'); - new CompletionComponent(wrapper, store); // eslint-disable-line no-new - new ConsoleComponent(document.body, store); // eslint-disable-line no-new + render( + + + , + document.body); }); const onMessage = (message) => {