You've already forked matrix-react-sdk
							
							
				mirror of
				https://github.com/matrix-org/matrix-react-sdk.git
				synced 2025-11-03 00:33:22 +03:00 
			
		
		
		
	Convert autocomplete stuff to TypeScript
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
		@@ -84,7 +84,7 @@ interface ICommandOpts {
 | 
			
		||||
    hideCompletionAfterSpace?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Command {
 | 
			
		||||
export class Command {
 | 
			
		||||
    command: string;
 | 
			
		||||
    aliases: string[];
 | 
			
		||||
    args: undefined | string;
 | 
			
		||||
 
 | 
			
		||||
@@ -16,10 +16,21 @@ See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import type {Completion, SelectionRange} from './Autocompleter';
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import type {ICompletion, ISelectionRange} from './Autocompleter';
 | 
			
		||||
 | 
			
		||||
export interface ICommand {
 | 
			
		||||
    command: string | null;
 | 
			
		||||
    range: {
 | 
			
		||||
        start: number;
 | 
			
		||||
        end: number;
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default class AutocompleteProvider {
 | 
			
		||||
    commandRegex: RegExp;
 | 
			
		||||
    forcedCommandRegex: RegExp;
 | 
			
		||||
 | 
			
		||||
    constructor(commandRegex?: RegExp, forcedCommandRegex?: RegExp) {
 | 
			
		||||
        if (commandRegex) {
 | 
			
		||||
            if (!commandRegex.global) {
 | 
			
		||||
@@ -42,11 +53,11 @@ export default class AutocompleteProvider {
 | 
			
		||||
    /**
 | 
			
		||||
     * Of the matched commands in the query, returns the first that contains or is contained by the selection, or null.
 | 
			
		||||
     * @param {string} query The query string
 | 
			
		||||
     * @param {SelectionRange} selection Selection to search
 | 
			
		||||
     * @param {ISelectionRange} selection Selection to search
 | 
			
		||||
     * @param {boolean} force True if the user is forcing completion
 | 
			
		||||
     * @return {object} { command, range } where both objects fields are null if no match
 | 
			
		||||
     */
 | 
			
		||||
    getCurrentCommand(query: string, selection: SelectionRange, force: boolean = false) {
 | 
			
		||||
    getCurrentCommand(query: string, selection: ISelectionRange, force = false) {
 | 
			
		||||
        let commandRegex = this.commandRegex;
 | 
			
		||||
 | 
			
		||||
        if (force && this.shouldForceComplete()) {
 | 
			
		||||
@@ -82,7 +93,7 @@ export default class AutocompleteProvider {
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async getCompletions(query: string, selection: SelectionRange, force: boolean = false): Array<Completion> {
 | 
			
		||||
    async getCompletions(query: string, selection: ISelectionRange, force = false): Promise<ICompletion[]> {
 | 
			
		||||
        return [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -90,7 +101,7 @@ export default class AutocompleteProvider {
 | 
			
		||||
        return 'Default Provider';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    renderCompletions(completions: [React.Component]): ?React.Component {
 | 
			
		||||
    renderCompletions(completions: React.ReactNode[]): React.ReactNode | null {
 | 
			
		||||
        console.error('stub; should be implemented in subclasses');
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
@@ -15,10 +15,8 @@ See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
// @flow
 | 
			
		||||
 | 
			
		||||
import type {Component} from 'react';
 | 
			
		||||
import {Room} from 'matrix-js-sdk';
 | 
			
		||||
import {ReactElement} from 'react';
 | 
			
		||||
import Room from 'matrix-js-sdk/src/models/room';
 | 
			
		||||
import CommandProvider from './CommandProvider';
 | 
			
		||||
import CommunityProvider from './CommunityProvider';
 | 
			
		||||
import DuckDuckGoProvider from './DuckDuckGoProvider';
 | 
			
		||||
@@ -27,22 +25,26 @@ import UserProvider from './UserProvider';
 | 
			
		||||
import EmojiProvider from './EmojiProvider';
 | 
			
		||||
import NotifProvider from './NotifProvider';
 | 
			
		||||
import {timeout} from "../utils/promise";
 | 
			
		||||
import AutocompleteProvider, {ICommand} from "./AutocompleteProvider";
 | 
			
		||||
 | 
			
		||||
export type SelectionRange = {
 | 
			
		||||
    beginning: boolean, // whether the selection is in the first block of the editor or not
 | 
			
		||||
    start: number, // byte offset relative to the start anchor of the current editor selection.
 | 
			
		||||
    end: number, // byte offset relative to the end anchor of the current editor selection.
 | 
			
		||||
};
 | 
			
		||||
export interface ISelectionRange {
 | 
			
		||||
    beginning?: boolean; // whether the selection is in the first block of the editor or not
 | 
			
		||||
    start: number; // byte offset relative to the start anchor of the current editor selection.
 | 
			
		||||
    end: number; // byte offset relative to the end anchor of the current editor selection.
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type Completion = {
 | 
			
		||||
export interface ICompletion {
 | 
			
		||||
    type: "at-room" | "command" | "community" | "room" | "user";
 | 
			
		||||
    completion: string,
 | 
			
		||||
    component: ?Component,
 | 
			
		||||
    range: SelectionRange,
 | 
			
		||||
    command: ?string,
 | 
			
		||||
    completionId?: string;
 | 
			
		||||
    component?: ReactElement,
 | 
			
		||||
    range: ISelectionRange,
 | 
			
		||||
    command?: string,
 | 
			
		||||
    suffix?: string;
 | 
			
		||||
    // If provided, apply a LINK entity to the completion with the
 | 
			
		||||
    // data = { url: href }.
 | 
			
		||||
    href: ?string,
 | 
			
		||||
};
 | 
			
		||||
    href?: string,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const PROVIDERS = [
 | 
			
		||||
    UserProvider,
 | 
			
		||||
@@ -57,7 +59,16 @@ const PROVIDERS = [
 | 
			
		||||
// Providers will get rejected if they take longer than this.
 | 
			
		||||
const PROVIDER_COMPLETION_TIMEOUT = 3000;
 | 
			
		||||
 | 
			
		||||
export interface IProviderCompletions {
 | 
			
		||||
    completions: ICompletion[];
 | 
			
		||||
    provider: AutocompleteProvider;
 | 
			
		||||
    command: ICommand;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default class Autocompleter {
 | 
			
		||||
    room: Room;
 | 
			
		||||
    providers: AutocompleteProvider[];
 | 
			
		||||
 | 
			
		||||
    constructor(room: Room) {
 | 
			
		||||
        this.room = room;
 | 
			
		||||
        this.providers = PROVIDERS.map((Prov) => {
 | 
			
		||||
@@ -71,13 +82,14 @@ export default class Autocompleter {
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async getCompletions(query: string, selection: SelectionRange, force: boolean = false): Array<Completion> {
 | 
			
		||||
    async getCompletions(query: string, selection: ISelectionRange, force = false): Promise<IProviderCompletions[]> {
 | 
			
		||||
        /* Note: This intentionally waits for all providers to return,
 | 
			
		||||
         otherwise, we run into a condition where new completions are displayed
 | 
			
		||||
         while the user is interacting with the list, which makes it difficult
 | 
			
		||||
         to predict whether an action will actually do what is intended
 | 
			
		||||
        */
 | 
			
		||||
        const completionsList = await Promise.all(this.providers.map(provider => {
 | 
			
		||||
        // list of results from each provider, each being a list of completions or null if it times out
 | 
			
		||||
        const completionsList: ICompletion[][] = await Promise.all(this.providers.map(provider => {
 | 
			
		||||
            return timeout(provider.getCompletions(query, selection, force), null, PROVIDER_COMPLETION_TIMEOUT);
 | 
			
		||||
        }));
 | 
			
		||||
 | 
			
		||||
@@ -17,17 +17,19 @@ See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import {_t} from '../languageHandler';
 | 
			
		||||
import AutocompleteProvider from './AutocompleteProvider';
 | 
			
		||||
import QueryMatcher from './QueryMatcher';
 | 
			
		||||
import {TextualCompletion} from './Components';
 | 
			
		||||
import type {Completion, SelectionRange} from "./Autocompleter";
 | 
			
		||||
import {Commands, CommandMap} from '../SlashCommands';
 | 
			
		||||
import {ICompletion, ISelectionRange} from "./Autocompleter";
 | 
			
		||||
import {Command, Commands, CommandMap} from '../SlashCommands';
 | 
			
		||||
 | 
			
		||||
const COMMAND_RE = /(^\/\w*)(?: .*)?/g;
 | 
			
		||||
 | 
			
		||||
export default class CommandProvider extends AutocompleteProvider {
 | 
			
		||||
    matcher: QueryMatcher<Command>;
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super(COMMAND_RE);
 | 
			
		||||
        this.matcher = new QueryMatcher(Commands, {
 | 
			
		||||
@@ -36,7 +38,7 @@ export default class CommandProvider extends AutocompleteProvider {
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async getCompletions(query: string, selection: SelectionRange, force?: boolean): Array<Completion> {
 | 
			
		||||
    async getCompletions(query: string, selection: ISelectionRange, force?: boolean): Promise<ICompletion[]> {
 | 
			
		||||
        const {command, range} = this.getCurrentCommand(query, selection);
 | 
			
		||||
        if (!command) return [];
 | 
			
		||||
 | 
			
		||||
@@ -85,7 +87,7 @@ export default class CommandProvider extends AutocompleteProvider {
 | 
			
		||||
        return '*️⃣ ' + _t('Commands');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    renderCompletions(completions: [React.Component]): ?React.Component {
 | 
			
		||||
    renderCompletions(completions: React.ReactNode[]): React.ReactNode {
 | 
			
		||||
        return (
 | 
			
		||||
            <div className="mx_Autocomplete_Completion_container_block" role="listbox" aria-label={_t("Command Autocomplete")}>
 | 
			
		||||
                { completions }
 | 
			
		||||
@@ -15,7 +15,8 @@ See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import Group from "matrix-js-sdk/src/models/group";
 | 
			
		||||
import { _t } from '../languageHandler';
 | 
			
		||||
import AutocompleteProvider from './AutocompleteProvider';
 | 
			
		||||
import {MatrixClientPeg} from '../MatrixClientPeg';
 | 
			
		||||
@@ -24,7 +25,7 @@ import {PillCompletion} from './Components';
 | 
			
		||||
import * as sdk from '../index';
 | 
			
		||||
import _sortBy from 'lodash/sortBy';
 | 
			
		||||
import {makeGroupPermalink} from "../utils/permalinks/Permalinks";
 | 
			
		||||
import type {Completion, SelectionRange} from "./Autocompleter";
 | 
			
		||||
import {ICompletion, ISelectionRange} from "./Autocompleter";
 | 
			
		||||
import FlairStore from "../stores/FlairStore";
 | 
			
		||||
 | 
			
		||||
const COMMUNITY_REGEX = /\B\+\S*/g;
 | 
			
		||||
@@ -39,6 +40,8 @@ function score(query, space) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default class CommunityProvider extends AutocompleteProvider {
 | 
			
		||||
    matcher: QueryMatcher<Group>;
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super(COMMUNITY_REGEX);
 | 
			
		||||
        this.matcher = new QueryMatcher([], {
 | 
			
		||||
@@ -46,7 +49,7 @@ export default class CommunityProvider extends AutocompleteProvider {
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async getCompletions(query: string, selection: SelectionRange, force: boolean = false): Array<Completion> {
 | 
			
		||||
    async getCompletions(query: string, selection: ISelectionRange, force: boolean = false): Promise<ICompletion[]> {
 | 
			
		||||
        const BaseAvatar = sdk.getComponent('views.avatars.BaseAvatar');
 | 
			
		||||
 | 
			
		||||
        // Disable autocompletions when composing commands because of various issues
 | 
			
		||||
@@ -104,7 +107,7 @@ export default class CommunityProvider extends AutocompleteProvider {
 | 
			
		||||
        return '💬 ' + _t('Communities');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    renderCompletions(completions: [React.Component]): ?React.Component {
 | 
			
		||||
    renderCompletions(completions: React.ReactNode[]): React.ReactNode {
 | 
			
		||||
        return (
 | 
			
		||||
            <div
 | 
			
		||||
                className="mx_Autocomplete_Completion_container_pill mx_Autocomplete_Completion_container_truncate"
 | 
			
		||||
@@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import * as PropTypes from 'prop-types';
 | 
			
		||||
import classNames from 'classnames';
 | 
			
		||||
 | 
			
		||||
/* These were earlier stateless functional components but had to be converted
 | 
			
		||||
@@ -24,7 +24,21 @@ something that is not entirely possible with stateless functional components. On
 | 
			
		||||
presumably wrap them in a <div> before rendering but I think this is the better way to do it.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
export class TextualCompletion extends React.Component {
 | 
			
		||||
interface ITextualCompletionProps {
 | 
			
		||||
    title?: string;
 | 
			
		||||
    subtitle?: string;
 | 
			
		||||
    description?: string;
 | 
			
		||||
    className?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class TextualCompletion extends React.PureComponent<ITextualCompletionProps> {
 | 
			
		||||
    static propTypes = {
 | 
			
		||||
        title: PropTypes.string,
 | 
			
		||||
        subtitle: PropTypes.string,
 | 
			
		||||
        description: PropTypes.string,
 | 
			
		||||
        className: PropTypes.string,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
        const {
 | 
			
		||||
            title,
 | 
			
		||||
@@ -42,14 +56,24 @@ export class TextualCompletion extends React.Component {
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
TextualCompletion.propTypes = {
 | 
			
		||||
    title: PropTypes.string,
 | 
			
		||||
    subtitle: PropTypes.string,
 | 
			
		||||
    description: PropTypes.string,
 | 
			
		||||
    className: PropTypes.string,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export class PillCompletion extends React.Component {
 | 
			
		||||
interface IPillCompletionProps {
 | 
			
		||||
    title?: string;
 | 
			
		||||
    subtitle?: string;
 | 
			
		||||
    description?: string;
 | 
			
		||||
    initialComponent?: React.ReactNode,
 | 
			
		||||
    className?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class PillCompletion extends React.PureComponent<IPillCompletionProps> {
 | 
			
		||||
    static propTypes = {
 | 
			
		||||
        title: PropTypes.string,
 | 
			
		||||
        subtitle: PropTypes.string,
 | 
			
		||||
        description: PropTypes.string,
 | 
			
		||||
        initialComponent: PropTypes.element,
 | 
			
		||||
        className: PropTypes.string,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
        const {
 | 
			
		||||
            title,
 | 
			
		||||
@@ -69,10 +93,3 @@ export class PillCompletion extends React.Component {
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
PillCompletion.propTypes = {
 | 
			
		||||
    title: PropTypes.string,
 | 
			
		||||
    subtitle: PropTypes.string,
 | 
			
		||||
    description: PropTypes.string,
 | 
			
		||||
    initialComponent: PropTypes.element,
 | 
			
		||||
    className: PropTypes.string,
 | 
			
		||||
};
 | 
			
		||||
@@ -16,12 +16,12 @@ See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import { _t } from '../languageHandler';
 | 
			
		||||
import AutocompleteProvider from './AutocompleteProvider';
 | 
			
		||||
 | 
			
		||||
import {TextualCompletion} from './Components';
 | 
			
		||||
import type {SelectionRange} from "./Autocompleter";
 | 
			
		||||
import {ICompletion, ISelectionRange} from "./Autocompleter";
 | 
			
		||||
 | 
			
		||||
const DDG_REGEX = /\/ddg\s+(.+)$/g;
 | 
			
		||||
const REFERRER = 'vector';
 | 
			
		||||
@@ -31,12 +31,12 @@ export default class DuckDuckGoProvider extends AutocompleteProvider {
 | 
			
		||||
        super(DDG_REGEX);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static getQueryUri(query: String) {
 | 
			
		||||
    static getQueryUri(query: string) {
 | 
			
		||||
        return `https://api.duckduckgo.com/?q=${encodeURIComponent(query)}`
 | 
			
		||||
         + `&format=json&no_redirect=1&no_html=1&t=${encodeURIComponent(REFERRER)}`;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async getCompletions(query: string, selection: SelectionRange, force: boolean = false) {
 | 
			
		||||
    async getCompletions(query: string, selection: ISelectionRange, force= false): Promise<ICompletion[]> {
 | 
			
		||||
        const {command, range} = this.getCurrentCommand(query, selection);
 | 
			
		||||
        if (!query || !command) {
 | 
			
		||||
            return [];
 | 
			
		||||
@@ -95,7 +95,7 @@ export default class DuckDuckGoProvider extends AutocompleteProvider {
 | 
			
		||||
        return '🔍 ' + _t('Results from DuckDuckGo');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    renderCompletions(completions: [React.Component]): ?React.Component {
 | 
			
		||||
    renderCompletions(completions: React.ReactNode[]): React.ReactNode {
 | 
			
		||||
        return (
 | 
			
		||||
            <div
 | 
			
		||||
                className="mx_Autocomplete_Completion_container_block"
 | 
			
		||||
@@ -17,41 +17,56 @@ See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import { _t } from '../languageHandler';
 | 
			
		||||
import AutocompleteProvider from './AutocompleteProvider';
 | 
			
		||||
import QueryMatcher from './QueryMatcher';
 | 
			
		||||
import {PillCompletion} from './Components';
 | 
			
		||||
import type {Completion, SelectionRange} from './Autocompleter';
 | 
			
		||||
import {ICompletion, ISelectionRange} from './Autocompleter';
 | 
			
		||||
import _uniq from 'lodash/uniq';
 | 
			
		||||
import _sortBy from 'lodash/sortBy';
 | 
			
		||||
import SettingsStore from "../settings/SettingsStore";
 | 
			
		||||
import { shortcodeToUnicode } from '../HtmlUtils';
 | 
			
		||||
 | 
			
		||||
import EMOTICON_REGEX from 'emojibase-regex/emoticon';
 | 
			
		||||
import EMOJIBASE from 'emojibase-data/en/compact.json';
 | 
			
		||||
import * as EMOJIBASE from 'emojibase-data/en/compact.json';
 | 
			
		||||
 | 
			
		||||
const LIMIT = 20;
 | 
			
		||||
 | 
			
		||||
// Match for ascii-style ";-)" emoticons or ":wink:" shortcodes provided by emojibase
 | 
			
		||||
const EMOJI_REGEX = new RegExp('(' + EMOTICON_REGEX.source + '|:[+-\\w]*:?)$', 'g');
 | 
			
		||||
 | 
			
		||||
interface IEmoji {
 | 
			
		||||
    annotation: string;
 | 
			
		||||
    group: number;
 | 
			
		||||
    hexcode: string;
 | 
			
		||||
    order: number;
 | 
			
		||||
    shortcodes: string[];
 | 
			
		||||
    tags: string[];
 | 
			
		||||
    unicode: string;
 | 
			
		||||
    emoticon: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface IEmojiShort {
 | 
			
		||||
    emoji: IEmoji;
 | 
			
		||||
    shortname: string;
 | 
			
		||||
    _orderBy: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// XXX: it's very unclear why we bother with this generated emojidata file.
 | 
			
		||||
// all it means is that we end up bloating the bundle with precomputed stuff
 | 
			
		||||
// which would be trivial to calculate and cache on demand.
 | 
			
		||||
const EMOJI_SHORTNAMES = EMOJIBASE.sort((a, b) => {
 | 
			
		||||
const EMOJI_SHORTNAMES: IEmojiShort[] = (EMOJIBASE as IEmoji[]).sort((a, b) => {
 | 
			
		||||
    if (a.group === b.group) {
 | 
			
		||||
        return a.order - b.order;
 | 
			
		||||
    }
 | 
			
		||||
    return a.group - b.group;
 | 
			
		||||
}).map((emoji, index) => {
 | 
			
		||||
    return {
 | 
			
		||||
        emoji,
 | 
			
		||||
        shortname: `:${emoji.shortcodes[0]}:`,
 | 
			
		||||
        // Include the index so that we can preserve the original order
 | 
			
		||||
        _orderBy: index,
 | 
			
		||||
    };
 | 
			
		||||
});
 | 
			
		||||
}).map((emoji, index) => ({
 | 
			
		||||
    emoji,
 | 
			
		||||
    shortname: `:${emoji.shortcodes[0]}:`,
 | 
			
		||||
    // Include the index so that we can preserve the original order
 | 
			
		||||
    _orderBy: index,
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
function score(query, space) {
 | 
			
		||||
    const index = space.indexOf(query);
 | 
			
		||||
@@ -63,6 +78,9 @@ function score(query, space) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default class EmojiProvider extends AutocompleteProvider {
 | 
			
		||||
    matcher: QueryMatcher<IEmojiShort>;
 | 
			
		||||
    nameMatcher: QueryMatcher<IEmojiShort>;
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super(EMOJI_REGEX);
 | 
			
		||||
        this.matcher = new QueryMatcher(EMOJI_SHORTNAMES, {
 | 
			
		||||
@@ -80,7 +98,7 @@ export default class EmojiProvider extends AutocompleteProvider {
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async getCompletions(query: string, selection: SelectionRange, force?: boolean): Array<Completion> {
 | 
			
		||||
    async getCompletions(query: string, selection: ISelectionRange, force?: boolean): Promise<ICompletion[]> {
 | 
			
		||||
        if (!SettingsStore.getValue("MessageComposerInput.suggestEmoji")) {
 | 
			
		||||
            return []; // don't give any suggestions if the user doesn't want them
 | 
			
		||||
        }
 | 
			
		||||
@@ -132,7 +150,7 @@ export default class EmojiProvider extends AutocompleteProvider {
 | 
			
		||||
        return '😃 ' + _t('Emoji');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    renderCompletions(completions: [React.Component]): ?React.Component {
 | 
			
		||||
    renderCompletions(completions: React.ReactNode[]): React.ReactNode {
 | 
			
		||||
        return (
 | 
			
		||||
            <div className="mx_Autocomplete_Completion_container_pill" role="listbox" aria-label={_t("Emoji Autocomplete")}>
 | 
			
		||||
                { completions }
 | 
			
		||||
@@ -14,23 +14,26 @@ See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import Room from "matrix-js-sdk/src/models/room";
 | 
			
		||||
import AutocompleteProvider from './AutocompleteProvider';
 | 
			
		||||
import { _t } from '../languageHandler';
 | 
			
		||||
import {MatrixClientPeg} from '../MatrixClientPeg';
 | 
			
		||||
import {PillCompletion} from './Components';
 | 
			
		||||
import * as sdk from '../index';
 | 
			
		||||
import type {Completion, SelectionRange} from "./Autocompleter";
 | 
			
		||||
import {ICompletion, ISelectionRange} from "./Autocompleter";
 | 
			
		||||
 | 
			
		||||
const AT_ROOM_REGEX = /@\S*/g;
 | 
			
		||||
 | 
			
		||||
export default class NotifProvider extends AutocompleteProvider {
 | 
			
		||||
    room: Room;
 | 
			
		||||
 | 
			
		||||
    constructor(room) {
 | 
			
		||||
        super(AT_ROOM_REGEX);
 | 
			
		||||
        this.room = room;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async getCompletions(query: string, selection: SelectionRange, force:boolean = false): Array<Completion> {
 | 
			
		||||
    async getCompletions(query: string, selection: ISelectionRange, force= false): Promise<ICompletion[]> {
 | 
			
		||||
        const RoomAvatar = sdk.getComponent('views.avatars.RoomAvatar');
 | 
			
		||||
 | 
			
		||||
        const client = MatrixClientPeg.get();
 | 
			
		||||
@@ -57,7 +60,7 @@ export default class NotifProvider extends AutocompleteProvider {
 | 
			
		||||
        return '❗️ ' + _t('Room Notification');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    renderCompletions(completions: [React.Component]): ?React.Component {
 | 
			
		||||
    renderCompletions(completions: React.ReactNode[]): React.ReactNode {
 | 
			
		||||
        return (
 | 
			
		||||
            <div
 | 
			
		||||
                className="mx_Autocomplete_Completion_container_pill mx_Autocomplete_Completion_container_truncate"
 | 
			
		||||
@@ -1,4 +1,3 @@
 | 
			
		||||
//@flow
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2017 Aviral Dasgupta
 | 
			
		||||
Copyright 2018 Michael Telatynski <7t3chguy@gmail.com>
 | 
			
		||||
@@ -26,6 +25,13 @@ function stripDiacritics(str: string): string {
 | 
			
		||||
    return str.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface IOptions<T extends {}> {
 | 
			
		||||
    keys: Array<string | keyof T>;
 | 
			
		||||
    funcs?: Array<(T) => string>;
 | 
			
		||||
    shouldMatchWordsOnly?: boolean;
 | 
			
		||||
    shouldMatchPrefix?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Simple search matcher that matches any results with the query string anywhere
 | 
			
		||||
 * in the search string. Returns matches in the order the query string appears
 | 
			
		||||
@@ -39,8 +45,13 @@ function stripDiacritics(str: string): string {
 | 
			
		||||
 * @param {function[]} options.funcs List of functions that when called with the
 | 
			
		||||
 *     object as an arg will return a string to use as an index
 | 
			
		||||
 */
 | 
			
		||||
export default class QueryMatcher {
 | 
			
		||||
    constructor(objects: Array<Object>, options: {[Object]: Object} = {}) {
 | 
			
		||||
export default class QueryMatcher<T> {
 | 
			
		||||
    private _options: IOptions<T>;
 | 
			
		||||
    private _keys: IOptions<T>["keys"];
 | 
			
		||||
    private _funcs: Required<IOptions<T>["funcs"]>;
 | 
			
		||||
    private _items: Map<string, T[]>;
 | 
			
		||||
 | 
			
		||||
    constructor(objects: T[], options: IOptions<T> = { keys: [] }) {
 | 
			
		||||
        this._options = options;
 | 
			
		||||
        this._keys = options.keys;
 | 
			
		||||
        this._funcs = options.funcs || [];
 | 
			
		||||
@@ -60,7 +71,7 @@ export default class QueryMatcher {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setObjects(objects: Array<Object>) {
 | 
			
		||||
    setObjects(objects: T[]) {
 | 
			
		||||
        this._items = new Map();
 | 
			
		||||
 | 
			
		||||
        for (const object of objects) {
 | 
			
		||||
@@ -81,7 +92,7 @@ export default class QueryMatcher {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    match(query: String): Array<Object> {
 | 
			
		||||
    match(query: string): T[] {
 | 
			
		||||
        query = stripDiacritics(query).toLowerCase();
 | 
			
		||||
        if (this._options.shouldMatchWordsOnly) {
 | 
			
		||||
            query = query.replace(/[^\w]/g, '');
 | 
			
		||||
@@ -17,7 +17,8 @@ See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import Room from "matrix-js-sdk/src/models/room";
 | 
			
		||||
import { _t } from '../languageHandler';
 | 
			
		||||
import AutocompleteProvider from './AutocompleteProvider';
 | 
			
		||||
import {MatrixClientPeg} from '../MatrixClientPeg';
 | 
			
		||||
@@ -26,7 +27,7 @@ import {PillCompletion} from './Components';
 | 
			
		||||
import * as sdk from '../index';
 | 
			
		||||
import _sortBy from 'lodash/sortBy';
 | 
			
		||||
import {makeRoomPermalink} from "../utils/permalinks/Permalinks";
 | 
			
		||||
import type {Completion, SelectionRange} from "./Autocompleter";
 | 
			
		||||
import {ICompletion, ISelectionRange} from "./Autocompleter";
 | 
			
		||||
 | 
			
		||||
const ROOM_REGEX = /\B#\S*/g;
 | 
			
		||||
 | 
			
		||||
@@ -48,6 +49,8 @@ function matcherObject(room, displayedAlias, matchName = "") {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default class RoomProvider extends AutocompleteProvider {
 | 
			
		||||
    matcher: QueryMatcher<Room>;
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super(ROOM_REGEX);
 | 
			
		||||
        this.matcher = new QueryMatcher([], {
 | 
			
		||||
@@ -55,7 +58,7 @@ export default class RoomProvider extends AutocompleteProvider {
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async getCompletions(query: string, selection: SelectionRange, force: boolean = false): Array<Completion> {
 | 
			
		||||
    async getCompletions(query: string, selection: ISelectionRange, force = false): Promise<ICompletion[]> {
 | 
			
		||||
        const RoomAvatar = sdk.getComponent('views.avatars.RoomAvatar');
 | 
			
		||||
 | 
			
		||||
        const client = MatrixClientPeg.get();
 | 
			
		||||
@@ -115,7 +118,7 @@ export default class RoomProvider extends AutocompleteProvider {
 | 
			
		||||
        return '💬 ' + _t('Rooms');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    renderCompletions(completions: [React.Component]): ?React.Component {
 | 
			
		||||
    renderCompletions(completions: React.ReactNode[]): React.ReactNode {
 | 
			
		||||
        return (
 | 
			
		||||
            <div
 | 
			
		||||
                className="mx_Autocomplete_Completion_container_pill mx_Autocomplete_Completion_container_truncate"
 | 
			
		||||
@@ -1,4 +1,3 @@
 | 
			
		||||
//@flow
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2016 Aviral Dasgupta
 | 
			
		||||
Copyright 2017 Vector Creations Ltd
 | 
			
		||||
@@ -18,7 +17,7 @@ See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import { _t } from '../languageHandler';
 | 
			
		||||
import AutocompleteProvider from './AutocompleteProvider';
 | 
			
		||||
import {PillCompletion} from './Components';
 | 
			
		||||
@@ -27,9 +26,13 @@ import QueryMatcher from './QueryMatcher';
 | 
			
		||||
import _sortBy from 'lodash/sortBy';
 | 
			
		||||
import {MatrixClientPeg} from '../MatrixClientPeg';
 | 
			
		||||
 | 
			
		||||
import type {MatrixEvent, Room, RoomMember, RoomState} from 'matrix-js-sdk';
 | 
			
		||||
import MatrixEvent from "matrix-js-sdk/src/models/event";
 | 
			
		||||
import Room from "matrix-js-sdk/src/models/room";
 | 
			
		||||
import RoomMember from "matrix-js-sdk/src/models/room-member";
 | 
			
		||||
import RoomState from "matrix-js-sdk/src/models/room-state";
 | 
			
		||||
import EventTimeline from "matrix-js-sdk/src/models/event-timeline";
 | 
			
		||||
import {makeUserPermalink} from "../utils/permalinks/Permalinks";
 | 
			
		||||
import type {Completion, SelectionRange} from "./Autocompleter";
 | 
			
		||||
import {ICompletion, ISelectionRange} from "./Autocompleter";
 | 
			
		||||
 | 
			
		||||
const USER_REGEX = /\B@\S*/g;
 | 
			
		||||
 | 
			
		||||
@@ -37,9 +40,15 @@ const USER_REGEX = /\B@\S*/g;
 | 
			
		||||
// to allow you to tab-complete /mat into /(matthew)
 | 
			
		||||
const FORCED_USER_REGEX = /[^/,:; \t\n]\S*/g;
 | 
			
		||||
 | 
			
		||||
interface IRoomTimelineData {
 | 
			
		||||
    timeline: EventTimeline;
 | 
			
		||||
    liveEvent?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default class UserProvider extends AutocompleteProvider {
 | 
			
		||||
    users: Array<RoomMember> = null;
 | 
			
		||||
    room: Room = null;
 | 
			
		||||
    matcher: QueryMatcher<RoomMember>;
 | 
			
		||||
    users: RoomMember[];
 | 
			
		||||
    room: Room;
 | 
			
		||||
 | 
			
		||||
    constructor(room: Room) {
 | 
			
		||||
        super(USER_REGEX, FORCED_USER_REGEX);
 | 
			
		||||
@@ -51,21 +60,19 @@ export default class UserProvider extends AutocompleteProvider {
 | 
			
		||||
            shouldMatchWordsOnly: false,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this._onRoomTimelineBound = this._onRoomTimeline.bind(this);
 | 
			
		||||
        this._onRoomStateMemberBound = this._onRoomStateMember.bind(this);
 | 
			
		||||
 | 
			
		||||
        MatrixClientPeg.get().on("Room.timeline", this._onRoomTimelineBound);
 | 
			
		||||
        MatrixClientPeg.get().on("RoomState.members", this._onRoomStateMemberBound);
 | 
			
		||||
        MatrixClientPeg.get().on("Room.timeline", this._onRoomTimeline);
 | 
			
		||||
        MatrixClientPeg.get().on("RoomState.members", this._onRoomStateMember);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    destroy() {
 | 
			
		||||
        if (MatrixClientPeg.get()) {
 | 
			
		||||
            MatrixClientPeg.get().removeListener("Room.timeline", this._onRoomTimelineBound);
 | 
			
		||||
            MatrixClientPeg.get().removeListener("RoomState.members", this._onRoomStateMemberBound);
 | 
			
		||||
            MatrixClientPeg.get().removeListener("Room.timeline", this._onRoomTimeline);
 | 
			
		||||
            MatrixClientPeg.get().removeListener("RoomState.members", this._onRoomStateMember);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _onRoomTimeline(ev: MatrixEvent, room: Room, toStartOfTimeline: boolean, removed: boolean, data: Object) {
 | 
			
		||||
    _onRoomTimeline = (ev: MatrixEvent, room: Room, toStartOfTimeline: boolean, removed: boolean,
 | 
			
		||||
                       data: IRoomTimelineData) => {
 | 
			
		||||
        if (!room) return;
 | 
			
		||||
        if (removed) return;
 | 
			
		||||
        if (room.roomId !== this.room.roomId) return;
 | 
			
		||||
@@ -79,9 +86,9 @@ export default class UserProvider extends AutocompleteProvider {
 | 
			
		||||
 | 
			
		||||
        // TODO: lazyload if we have no ev.sender room member?
 | 
			
		||||
        this.onUserSpoke(ev.sender);
 | 
			
		||||
    }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    _onRoomStateMember(ev: MatrixEvent, state: RoomState, member: RoomMember) {
 | 
			
		||||
    _onRoomStateMember = (ev: MatrixEvent, state: RoomState, member: RoomMember) => {
 | 
			
		||||
        // ignore members in other rooms
 | 
			
		||||
        if (member.roomId !== this.room.roomId) {
 | 
			
		||||
            return;
 | 
			
		||||
@@ -89,9 +96,9 @@ export default class UserProvider extends AutocompleteProvider {
 | 
			
		||||
 | 
			
		||||
        // blow away the users cache
 | 
			
		||||
        this.users = null;
 | 
			
		||||
    }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    async getCompletions(query: string, selection: SelectionRange, force: boolean = false): Array<Completion> {
 | 
			
		||||
    async getCompletions(query: string, selection: ISelectionRange, force = false): Promise<ICompletion[]> {
 | 
			
		||||
        const MemberAvatar = sdk.getComponent('views.avatars.MemberAvatar');
 | 
			
		||||
 | 
			
		||||
        // lazy-load user list into matcher
 | 
			
		||||
@@ -163,7 +170,7 @@ export default class UserProvider extends AutocompleteProvider {
 | 
			
		||||
        this.matcher.setObjects(this.users);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    renderCompletions(completions: [React.Component]): ?React.Component {
 | 
			
		||||
    renderCompletions(completions: React.ReactNode[]): React.ReactNode {
 | 
			
		||||
        return (
 | 
			
		||||
            <div className="mx_Autocomplete_Completion_container_pill" role="listbox" aria-label={_t("User Autocomplete")}>
 | 
			
		||||
                { completions }
 | 
			
		||||
@@ -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),
 | 
			
		||||
};
 | 
			
		||||
@@ -2,6 +2,7 @@
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    "experimentalDecorators": true,
 | 
			
		||||
    "emitDecoratorMetadata": true,
 | 
			
		||||
    "resolveJsonModule": true,
 | 
			
		||||
    "module": "commonjs",
 | 
			
		||||
    "moduleResolution": "node",
 | 
			
		||||
    "target": "es2016",
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user