mirror of
https://github.com/element-hq/element-web
synced 2024-11-27 03:36:07 +03:00
initial version of autocomplete
This commit is contained in:
parent
a145ab7e28
commit
b979a16199
7 changed files with 164 additions and 1 deletions
|
@ -26,6 +26,7 @@
|
|||
"favico.js": "^0.3.10",
|
||||
"filesize": "^3.1.2",
|
||||
"flux": "^2.0.3",
|
||||
"fuse.js": "^2.2.0",
|
||||
"glob": "^5.0.14",
|
||||
"highlight.js": "^8.9.1",
|
||||
"linkifyjs": "^2.0.0-beta.4",
|
||||
|
|
3
src/autocomplete/AutocompleteProvider.js
Normal file
3
src/autocomplete/AutocompleteProvider.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
export default class AutocompleteProvider {
|
||||
|
||||
}
|
7
src/autocomplete/Autocompleter.js
Normal file
7
src/autocomplete/Autocompleter.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
import CommandProvider from './CommandProvider';
|
||||
|
||||
const COMPLETERS = [CommandProvider].map(completer => new completer());
|
||||
|
||||
export function getCompletions(query: String) {
|
||||
return COMPLETERS.map(completer => completer.getCompletions(query));
|
||||
}
|
65
src/autocomplete/CommandProvider.js
Normal file
65
src/autocomplete/CommandProvider.js
Normal file
|
@ -0,0 +1,65 @@
|
|||
import AutocompleteProvider from './AutocompleteProvider';
|
||||
import Q from 'q';
|
||||
import Fuse from 'fuse.js';
|
||||
|
||||
const COMMANDS = [
|
||||
{
|
||||
command: '/me',
|
||||
args: '<message>',
|
||||
description: 'Displays action'
|
||||
},
|
||||
{
|
||||
command: '/ban',
|
||||
args: '<user-id> [reason]',
|
||||
description: 'Bans user with given id'
|
||||
},
|
||||
{
|
||||
command: '/deop'
|
||||
},
|
||||
{
|
||||
command: '/encrypt'
|
||||
},
|
||||
{
|
||||
command: '/invite'
|
||||
},
|
||||
{
|
||||
command: '/join',
|
||||
args: '<room-alias>',
|
||||
description: 'Joins room with given alias'
|
||||
},
|
||||
{
|
||||
command: '/kick',
|
||||
args: '<user-id> [reason]',
|
||||
description: 'Kicks user with given id'
|
||||
},
|
||||
{
|
||||
command: '/nick',
|
||||
args: '<display-name>',
|
||||
description: 'Changes your display nickname'
|
||||
}
|
||||
];
|
||||
|
||||
export default class CommandProvider extends AutocompleteProvider {
|
||||
constructor() {
|
||||
super();
|
||||
this.fuse = new Fuse(COMMANDS, {
|
||||
keys: ['command', 'args', 'description']
|
||||
});
|
||||
}
|
||||
|
||||
getCompletions(query: String) {
|
||||
let completions = [];
|
||||
const matches = query.match(/(^\/\w+)/);
|
||||
if(!!matches) {
|
||||
const command = matches[0];
|
||||
completions = this.fuse.search(command).map(result => {
|
||||
return {
|
||||
title: result.command,
|
||||
subtitle: result.args,
|
||||
description: result.description
|
||||
};
|
||||
});
|
||||
}
|
||||
return Q.when(completions);
|
||||
}
|
||||
}
|
67
src/components/views/rooms/Autocomplete.js
Normal file
67
src/components/views/rooms/Autocomplete.js
Normal file
|
@ -0,0 +1,67 @@
|
|||
import React from 'react';
|
||||
|
||||
import {getCompletions} from '../../../autocomplete/Autocompleter';
|
||||
|
||||
export default class Autocomplete extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
completions: []
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(props, state) {
|
||||
getCompletions(props.query)[0].then(completions => {
|
||||
console.log(completions);
|
||||
this.setState({
|
||||
completions
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const pinElement = document.querySelector(this.props.pinSelector);
|
||||
if(!pinElement) return null;
|
||||
|
||||
const position = pinElement.getBoundingClientRect();
|
||||
|
||||
const style = {
|
||||
position: 'fixed',
|
||||
border: '1px solid gray',
|
||||
background: 'white',
|
||||
borderRadius: '4px'
|
||||
};
|
||||
|
||||
this.props.pinTo.forEach(direction => {
|
||||
console.log(`${direction} = ${position[direction]}`);
|
||||
style[direction] = position[direction];
|
||||
});
|
||||
|
||||
const renderedCompletions = this.state.completions.map((completion, i) => {
|
||||
return (
|
||||
<div key={i} class="mx_Autocomplete_Completion">
|
||||
<strong>{completion.title}</strong>
|
||||
<em>{completion.subtitle}</em>
|
||||
<span style={{color: 'gray', float: 'right'}}>{completion.description}</span>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="mx_Autocomplete" style={style}>
|
||||
{renderedCompletions}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Autocomplete.propTypes = {
|
||||
// the query string for which to show autocomplete suggestions
|
||||
query: React.PropTypes.string.isRequired,
|
||||
|
||||
// CSS selector indicating which element to pin the autocomplete to
|
||||
pinSelector: React.PropTypes.string.isRequired,
|
||||
|
||||
// attributes on which the autocomplete should match the pinElement
|
||||
pinTo: React.PropTypes.array.isRequired
|
||||
};
|
|
@ -20,6 +20,7 @@ var MatrixClientPeg = require('../../../MatrixClientPeg');
|
|||
var Modal = require('../../../Modal');
|
||||
var sdk = require('../../../index');
|
||||
var dis = require('../../../dispatcher');
|
||||
import Autocomplete from './Autocomplete';
|
||||
|
||||
|
||||
module.exports = React.createClass({
|
||||
|
@ -45,6 +46,12 @@ module.exports = React.createClass({
|
|||
opacity: React.PropTypes.number,
|
||||
},
|
||||
|
||||
getInitialState: function () {
|
||||
return {
|
||||
autocompleteQuery: ''
|
||||
};
|
||||
},
|
||||
|
||||
onUploadClick: function(ev) {
|
||||
this.refs.uploadInput.click();
|
||||
},
|
||||
|
@ -117,6 +124,12 @@ module.exports = React.createClass({
|
|||
});
|
||||
},
|
||||
|
||||
onInputContentChanged(content: String) {
|
||||
this.setState({
|
||||
autocompleteQuery: content
|
||||
})
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var me = this.props.room.getMember(MatrixClientPeg.get().credentials.userId);
|
||||
var uploadInputStyle = {display: 'none'};
|
||||
|
@ -170,7 +183,8 @@ module.exports = React.createClass({
|
|||
|
||||
controls.push(
|
||||
<MessageComposerInput key="controls_input" tabComplete={this.props.tabComplete}
|
||||
onResize={this.props.onResize} room={this.props.room} />,
|
||||
onResize={this.props.onResize} room={this.props.room}
|
||||
onContentChanged={(content) => this.onInputContentChanged(content) } />,
|
||||
uploadButton,
|
||||
hangupButton,
|
||||
callButton,
|
||||
|
@ -191,6 +205,8 @@ module.exports = React.createClass({
|
|||
{controls}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Autocomplete query={this.state.autocompleteQuery} pinSelector=".mx_RoomView_statusArea" pinTo={['top', 'left', 'width']} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -73,6 +73,8 @@ module.exports = React.createClass({
|
|||
|
||||
// js-sdk Room object
|
||||
room: React.PropTypes.object.isRequired,
|
||||
|
||||
onContentChanged: React.PropTypes.func
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
|
@ -276,6 +278,8 @@ module.exports = React.createClass({
|
|||
{
|
||||
this.resizeInput();
|
||||
}
|
||||
|
||||
this.props.onContentChanged && this.props.onContentChanged(this.refs.textarea.value);
|
||||
},
|
||||
|
||||
onEnter: function(ev) {
|
||||
|
|
Loading…
Reference in a new issue