Files
ThreadNet-Web/src/components/views/rooms/Autocomplete.js
T

168 lines
5.7 KiB
JavaScript
Raw Normal View History

2016-06-01 16:54:21 +05:30
import React from 'react';
import ReactDOM from 'react-dom';
2016-06-21 18:33:39 +05:30
import classNames from 'classnames';
2016-07-04 21:56:09 +05:30
import flatMap from 'lodash/flatMap';
import sdk from '../../../index';
2016-06-01 16:54:21 +05:30
import {getCompletions} from '../../../autocomplete/Autocompleter';
export default class Autocomplete extends React.Component {
constructor(props) {
super(props);
2016-07-03 22:15:13 +05:30
this.onConfirm = this.onConfirm.bind(this);
2016-06-01 16:54:21 +05:30
this.state = {
2016-07-03 22:15:13 +05:30
// list of completionResults, each containing completions
completions: [],
2016-07-03 22:15:13 +05:30
// array of completions, so we can look up current selection by offset quickly
completionList: [],
// how far down the completion list we are
2016-07-03 01:11:34 +05:30
selectionOffset: 0,
2016-06-01 16:54:21 +05:30
};
}
componentWillReceiveProps(props, state) {
2016-07-03 01:11:34 +05:30
if (props.query === this.props.query) {
return;
}
2016-07-03 01:11:34 +05:30
getCompletions(props.query, props.selection).forEach(completionResult => {
try {
completionResult.completions.then(completions => {
let i = this.state.completions.findIndex(
completion => completion.provider === completionResult.provider
);
2016-07-03 01:11:34 +05:30
i = i === -1 ? this.state.completions.length : i;
let newCompletions = Object.assign([], this.state.completions);
completionResult.completions = completions;
newCompletions[i] = completionResult;
2016-07-03 22:15:13 +05:30
this.setState({
2016-07-03 01:11:34 +05:30
completions: newCompletions,
2016-07-04 21:56:09 +05:30
completionList: flatMap(newCompletions, provider => provider.completions),
});
}, err => {
console.error(err);
});
} catch (e) {
// An error in one provider shouldn't mess up the rest.
console.error(e);
}
2016-06-01 16:54:21 +05:30
});
}
2016-07-03 01:11:34 +05:30
countCompletions(): number {
return this.state.completions.map(completionResult => {
return completionResult.completions.length;
}).reduce((l, r) => l + r);
}
// called from MessageComposerInput
onUpArrow(): boolean {
let completionCount = this.countCompletions(),
selectionOffset = (completionCount + this.state.selectionOffset - 1) % completionCount;
if (!completionCount) {
return false;
}
2016-07-03 22:15:13 +05:30
this.setSelection(selectionOffset);
2016-06-21 18:33:39 +05:30
return true;
}
2016-07-03 01:11:34 +05:30
// called from MessageComposerInput
onDownArrow(): boolean {
let completionCount = this.countCompletions(),
selectionOffset = (this.state.selectionOffset + 1) % completionCount;
if (!completionCount) {
return false;
}
2016-07-03 22:15:13 +05:30
this.setSelection(selectionOffset);
2016-06-21 18:33:39 +05:30
return true;
}
2016-07-03 22:15:13 +05:30
/** called from MessageComposerInput
* @returns {boolean} whether confirmation was handled
*/
onConfirm(): boolean {
2016-07-04 21:44:35 +05:30
if (this.countCompletions() === 0) {
2016-07-03 22:15:13 +05:30
return false;
2016-07-04 21:44:35 +05:30
}
2016-07-03 22:15:13 +05:30
let selectedCompletion = this.state.completionList[this.state.selectionOffset];
this.props.onConfirm(selectedCompletion.range, selectedCompletion.completion);
return true;
}
setSelection(selectionOffset: number) {
this.setState({selectionOffset});
}
componentDidUpdate() {
// this is the selected completion, so scroll it into view if needed
const selectedCompletion = this.refs[`completion${this.state.selectionOffset}`];
if (selectedCompletion && this.container) {
const {offsetTop} = ReactDOM.findDOMNode(selectedCompletion);
if (offsetTop > this.container.scrollTop + this.container.offsetHeight ||
offsetTop < this.container.scrollTop) {
this.container.scrollTop = offsetTop - this.container.offsetTop;
}
}
}
2016-06-01 16:54:21 +05:30
render() {
const EmojiText = sdk.getComponent('views.elements.EmojiText');
2016-06-21 18:33:39 +05:30
let position = 0;
let renderedCompletions = this.state.completions.map((completionResult, i) => {
let completions = completionResult.completions.map((completion, i) => {
const className = classNames('mx_Autocomplete_Completion', {
2016-07-03 01:11:34 +05:30
'selected': position === this.state.selectionOffset,
2016-06-21 18:33:39 +05:30
});
let componentPosition = position;
position++;
2016-07-03 01:11:34 +05:30
2016-07-04 21:44:35 +05:30
let onMouseOver = () => this.setSelection(componentPosition);
let onClick = () => {
this.setSelection(componentPosition);
this.onConfirm();
};
2016-07-03 22:15:13 +05:30
return React.cloneElement(completion.component, {
key: i,
ref: `completion${i}`,
className,
onMouseOver,
onClick,
});
});
return completions.length > 0 ? (
<div key={i} className="mx_Autocomplete_ProviderSection">
<EmojiText element="div" className="mx_Autocomplete_provider_name">{completionResult.provider.getName()}</EmojiText>
{completionResult.provider.renderCompletions(completions)}
2016-06-01 16:54:21 +05:30
</div>
) : null;
2016-06-01 16:54:21 +05:30
});
return (
<div className="mx_Autocomplete" ref={(e) => this.container = e}>
{renderedCompletions}
2016-06-01 16:54:21 +05:30
</div>
);
}
}
Autocomplete.propTypes = {
// the query string for which to show autocomplete suggestions
2016-07-03 01:11:34 +05:30
query: React.PropTypes.string.isRequired,
2016-07-04 21:44:35 +05:30
// method invoked with range and text content when completion is confirmed
onConfirm: React.PropTypes.func.isRequired,
2016-06-01 16:54:21 +05:30
};