commit
9c34b961c6
15 changed files with 298 additions and 307 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,133 @@ |
|||||||
|
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.focus(); |
||||||
|
} else if (prevProps.mode !== 'find' && this.props.mode === 'find') { |
||||||
|
this.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>; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
focus() { |
||||||
|
window.focus(); |
||||||
|
this.input.focus(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
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.children } |
||||||
|
</p> |
||||||
|
); |
||||||
|
case 'info': |
||||||
|
return ( |
||||||
|
<p className='vimvixen-console-message vimvixen-console-info'> |
||||||
|
{ props.children } |
||||||
|
</p> |
||||||
|
); |
||||||
|
} |
||||||
|
} |
Reference in new issue