Convert autocomplete stuff to TypeScript
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
parent
37bd0f3508
commit
fced4ea51e
14 changed files with 251 additions and 142 deletions
|
@ -15,30 +15,62 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import * as PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import flatMap from 'lodash/flatMap';
|
||||
import type {Completion} from '../../../autocomplete/Autocompleter';
|
||||
import { Room } from 'matrix-js-sdk';
|
||||
import {ICompletion, ISelectionRange, IProviderCompletions} from '../../../autocomplete/Autocompleter';
|
||||
import {Room} from 'matrix-js-sdk/src/models/room';
|
||||
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import Autocompleter from '../../../autocomplete/Autocompleter';
|
||||
import {sleep} from "../../../utils/promise";
|
||||
|
||||
const COMPOSER_SELECTED = 0;
|
||||
|
||||
export const generateCompletionDomId = (number) => `mx_Autocomplete_Completion_${number}`;
|
||||
|
||||
export default class Autocomplete extends React.Component {
|
||||
interface IProps {
|
||||
query: string;
|
||||
onConfirm: (ICompletion) => void;
|
||||
onSelectionChange?: (ICompletion, number) => void;
|
||||
selection: ISelectionRange;
|
||||
room: Room;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
completions: IProviderCompletions[];
|
||||
completionList: ICompletion[];
|
||||
selectionOffset: number;
|
||||
shouldShowCompletions: boolean;
|
||||
hide: boolean;
|
||||
forceComplete: boolean;
|
||||
}
|
||||
|
||||
export default class Autocomplete extends React.PureComponent<IProps, IState> {
|
||||
static propTypes = {
|
||||
// the query string for which to show autocomplete suggestions
|
||||
query: PropTypes.string.isRequired,
|
||||
|
||||
// method invoked with range and text content when completion is confirmed
|
||||
onConfirm: PropTypes.func.isRequired,
|
||||
|
||||
// method invoked when selected (if any) completion changes
|
||||
onSelectionChange: PropTypes.func,
|
||||
|
||||
// The room in which we're autocompleting
|
||||
room: PropTypes.instanceOf(Room),
|
||||
};
|
||||
|
||||
autocompleter: Autocompleter;
|
||||
queryRequested: string;
|
||||
debounceCompletionsRequest: NodeJS.Timeout;
|
||||
container: React.RefObject<HTMLDivElement>;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.autocompleter = new Autocompleter(props.room);
|
||||
this.completionPromise = null;
|
||||
this.hide = this.hide.bind(this);
|
||||
this.onCompletionClicked = this.onCompletionClicked.bind(this);
|
||||
|
||||
this.state = {
|
||||
// list of completionResults, each containing completions
|
||||
|
@ -57,13 +89,15 @@ export default class Autocomplete extends React.Component {
|
|||
|
||||
forceComplete: false,
|
||||
};
|
||||
|
||||
this.container = React.createRef();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._applyNewProps();
|
||||
}
|
||||
|
||||
_applyNewProps(oldQuery, oldRoom) {
|
||||
_applyNewProps(oldQuery?: string, oldRoom?: Room) {
|
||||
if (oldRoom && this.props.room.roomId !== oldRoom.roomId) {
|
||||
this.autocompleter.destroy();
|
||||
this.autocompleter = new Autocompleter(this.props.room);
|
||||
|
@ -159,7 +193,7 @@ export default class Autocomplete extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
hasSelection(): bool {
|
||||
hasSelection(): boolean {
|
||||
return this.countCompletions() > 0 && this.state.selectionOffset !== 0;
|
||||
}
|
||||
|
||||
|
@ -168,7 +202,7 @@ export default class Autocomplete extends React.Component {
|
|||
}
|
||||
|
||||
// called from MessageComposerInput
|
||||
moveSelection(delta): ?Completion {
|
||||
moveSelection(delta): ICompletion | undefined {
|
||||
const completionCount = this.countCompletions();
|
||||
if (completionCount === 0) return; // there are no items to move the selection through
|
||||
|
||||
|
@ -190,9 +224,14 @@ export default class Autocomplete extends React.Component {
|
|||
this.hide();
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.setState({hide: true, selectionOffset: 0, completions: [], completionList: []});
|
||||
}
|
||||
hide = () => {
|
||||
this.setState({
|
||||
hide: true,
|
||||
selectionOffset: 0,
|
||||
completions: [],
|
||||
completionList: [],
|
||||
});
|
||||
};
|
||||
|
||||
forceComplete() {
|
||||
return new Promise((resolve) => {
|
||||
|
@ -207,7 +246,7 @@ export default class Autocomplete extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
onCompletionClicked(selectionOffset: number): boolean {
|
||||
onCompletionClicked = (selectionOffset: number): boolean => {
|
||||
if (this.countCompletions() === 0 || selectionOffset === COMPOSER_SELECTED) {
|
||||
return false;
|
||||
}
|
||||
|
@ -216,7 +255,7 @@ export default class Autocomplete extends React.Component {
|
|||
this.hide();
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
setSelection(selectionOffset: number) {
|
||||
this.setState({selectionOffset, hide: false});
|
||||
|
@ -229,20 +268,16 @@ export default class Autocomplete extends React.Component {
|
|||
this._applyNewProps(prevProps.query, prevProps.room);
|
||||
// this is the selected completion, so scroll it into view if needed
|
||||
const selectedCompletion = this.refs[`completion${this.state.selectionOffset}`];
|
||||
if (selectedCompletion && this.container) {
|
||||
if (selectedCompletion && this.container.current) {
|
||||
const domNode = ReactDOM.findDOMNode(selectedCompletion);
|
||||
const offsetTop = domNode && domNode.offsetTop;
|
||||
if (offsetTop > this.container.scrollTop + this.container.offsetHeight ||
|
||||
offsetTop < this.container.scrollTop) {
|
||||
this.container.scrollTop = offsetTop - this.container.offsetTop;
|
||||
if (offsetTop > this.container.current.scrollTop + this.container.current.offsetHeight ||
|
||||
offsetTop < this.container.current.scrollTop) {
|
||||
this.container.current.scrollTop = offsetTop - this.container.current.offsetTop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setState(state, func) {
|
||||
super.setState(state, func);
|
||||
}
|
||||
|
||||
render() {
|
||||
let position = 1;
|
||||
const renderedCompletions = this.state.completions.map((completionResult, i) => {
|
||||
|
@ -276,23 +311,9 @@ export default class Autocomplete extends React.Component {
|
|||
}).filter((completion) => !!completion);
|
||||
|
||||
return !this.state.hide && renderedCompletions.length > 0 ? (
|
||||
<div className="mx_Autocomplete" ref={(e) => this.container = e}>
|
||||
<div className="mx_Autocomplete" ref={this.container}>
|
||||
{ renderedCompletions }
|
||||
</div>
|
||||
) : null;
|
||||
}
|
||||
}
|
||||
|
||||
Autocomplete.propTypes = {
|
||||
// the query string for which to show autocomplete suggestions
|
||||
query: PropTypes.string.isRequired,
|
||||
|
||||
// method invoked with range and text content when completion is confirmed
|
||||
onConfirm: PropTypes.func.isRequired,
|
||||
|
||||
// method invoked when selected (if any) completion changes
|
||||
onSelectionChange: PropTypes.func,
|
||||
|
||||
// The room in which we're autocompleting
|
||||
room: PropTypes.instanceOf(Room),
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue