parent
97787c773f
commit
1fb5f43305
9 changed files with 247 additions and 266 deletions
@ -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; |
||||
} |
||||
} |
@ -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(' '); |
||||
} |
||||
} |
@ -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 <div className='vimvixen-console-command-wrapper'> |
||||
<Completion /> |
||||
<Input |
||||
ref={(c) => { 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} |
||||
/> |
||||
</div>; |
||||
case 'info': |
||||
case 'error': |
||||
return <Message mode={ this.props.mode } > |
||||
{ this.props.messageText } |
||||
</Message>; |
||||
} |
||||
} |
||||
} |
||||
|
||||
const mapStateToProps = state => state; |
||||
export default connect(mapStateToProps)(ConsoleComponent); |
@ -0,0 +1,56 @@ |
||||
import { Component, h } from 'preact'; |
||||
import { connect } from 'preact-redux'; |
||||
|
||||
const CompletionTitle = (props) => { |
||||
return <li className='vimvixen-console-completion-title' >{props.title}</li>; |
||||
}; |
||||
|
||||
const CompletionItem = (props) => { |
||||
let className = 'vimvixen-console-completion-item'; |
||||
if (props.highlight) { |
||||
className += ' vimvixen-completion-selected'; |
||||
} |
||||
return <li |
||||
className={className} |
||||
style={{ backgroundImage: 'url(' + props.icon + ')' }} |
||||
> |
||||
<span |
||||
className='vimvixen-console-completion-item-caption' |
||||
>{props.caption}</span> |
||||
<span |
||||
className='vimvixen-console-completion-item-url' |
||||
>{props.url}</span> |
||||
</li>; |
||||
}; |
||||
|
||||
|
||||
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(<CompletionTitle title={ group.name }/>); |
||||
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(<CompletionItem |
||||
icon={item.icon} |
||||
caption={item.caption} |
||||
url={item.url} |
||||
highlight={selected} |
||||
/ >); |
||||
} |
||||
} |
||||
|
||||
return ( |
||||
<ul className='vimvixen-console-completion'> |
||||
{ eles } |
||||
</ul> |
||||
); |
||||
} |
||||
} |
||||
|
||||
const mapStateToProps = state => state; |
||||
export default connect(mapStateToProps)(CompletionComponent); |
@ -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 ( |
||||
<div className='vimvixen-console-command'> |
||||
<i className='vimvixen-console-command-prompt'> |
||||
{ prompt } |
||||
</i> |
||||
<input |
||||
className='vimvixen-console-command-input' |
||||
ref={(c) => { this.input = c; }} |
||||
onBlur={this.props.onBlur} |
||||
onKeyDown={this.props.onKeyDown} |
||||
onInput={this.props.onInput} |
||||
value={this.props.value} |
||||
/> |
||||
</div> |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,18 @@ |
||||
import { h } from 'preact'; |
||||
|
||||
export default function Message(props) { |
||||
switch (props.mode) { |
||||
case 'error': |
||||
return ( |
||||
<p className='vimvixen-console-message vimvixen-console-error'> |
||||
{ props.text } |
||||
</p> |
||||
); |
||||
case 'info': |
||||
return ( |
||||
<p className='vimvixen-console-message vimvixen-console-info'> |
||||
{ props.children } |
||||
</p> |
||||
); |
||||
} |
||||
} |
@ -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( |
||||
<Provider store={store} > |
||||
<Console></Console> |
||||
</Provider>, |
||||
document.body); |
||||
}); |
||||
|
||||
const onMessage = (message) => { |
Reference in new issue