From fced4ea51e02d979463201500bacb596cec3a6be Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 20 Apr 2020 19:00:54 +0100 Subject: [PATCH 01/10] Convert autocomplete stuff to TypeScript Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/SlashCommands.tsx | 2 +- ...teProvider.js => AutocompleteProvider.tsx} | 23 +++- .../{Autocompleter.js => Autocompleter.ts} | 46 +++++--- ...CommandProvider.js => CommandProvider.tsx} | 12 +- ...unityProvider.js => CommunityProvider.tsx} | 11 +- .../{Components.js => Components.tsx} | 51 ++++++--- ...ckGoProvider.js => DuckDuckGoProvider.tsx} | 10 +- .../{EmojiProvider.js => EmojiProvider.tsx} | 46 +++++--- .../{NotifProvider.js => NotifProvider.tsx} | 11 +- .../{QueryMatcher.js => QueryMatcher.ts} | 21 +++- .../{RoomProvider.js => RoomProvider.tsx} | 11 +- .../{UserProvider.js => UserProvider.tsx} | 45 ++++---- .../{Autocomplete.js => Autocomplete.tsx} | 103 +++++++++++------- tsconfig.json | 1 + 14 files changed, 251 insertions(+), 142 deletions(-) rename src/autocomplete/{AutocompleteProvider.js => AutocompleteProvider.tsx} (82%) rename src/autocomplete/{Autocompleter.js => Autocompleter.ts} (70%) rename src/autocomplete/{CommandProvider.js => CommandProvider.tsx} (89%) rename src/autocomplete/{CommunityProvider.js => CommunityProvider.tsx} (91%) rename src/autocomplete/{Components.js => Components.tsx} (70%) rename src/autocomplete/{DuckDuckGoProvider.js => DuckDuckGoProvider.tsx} (90%) rename src/autocomplete/{EmojiProvider.js => EmojiProvider.tsx} (83%) rename src/autocomplete/{NotifProvider.js => NotifProvider.tsx} (86%) rename src/autocomplete/{QueryMatcher.js => QueryMatcher.ts} (89%) rename src/autocomplete/{RoomProvider.js => RoomProvider.tsx} (92%) rename src/autocomplete/{UserProvider.js => UserProvider.tsx} (84%) rename src/components/views/rooms/{Autocomplete.js => Autocomplete.tsx} (80%) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 5e8c1087f7..6db683d339 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -84,7 +84,7 @@ interface ICommandOpts { hideCompletionAfterSpace?: boolean; } -class Command { +export class Command { command: string; aliases: string[]; args: undefined | string; diff --git a/src/autocomplete/AutocompleteProvider.js b/src/autocomplete/AutocompleteProvider.tsx similarity index 82% rename from src/autocomplete/AutocompleteProvider.js rename to src/autocomplete/AutocompleteProvider.tsx index 98ae83c526..7efe823250 100644 --- a/src/autocomplete/AutocompleteProvider.js +++ b/src/autocomplete/AutocompleteProvider.tsx @@ -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 { + async getCompletions(query: string, selection: ISelectionRange, force = false): Promise { 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; } diff --git a/src/autocomplete/Autocompleter.js b/src/autocomplete/Autocompleter.ts similarity index 70% rename from src/autocomplete/Autocompleter.js rename to src/autocomplete/Autocompleter.ts index a26eb6033b..8384eb9d4f 100644 --- a/src/autocomplete/Autocompleter.js +++ b/src/autocomplete/Autocompleter.ts @@ -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 { + async getCompletions(query: string, selection: ISelectionRange, force = false): Promise { /* 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); })); diff --git a/src/autocomplete/CommandProvider.js b/src/autocomplete/CommandProvider.tsx similarity index 89% rename from src/autocomplete/CommandProvider.js rename to src/autocomplete/CommandProvider.tsx index 0b8af4d6f9..b9e8975491 100644 --- a/src/autocomplete/CommandProvider.js +++ b/src/autocomplete/CommandProvider.tsx @@ -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; + 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 { + async getCompletions(query: string, selection: ISelectionRange, force?: boolean): Promise { 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 (
{ completions } diff --git a/src/autocomplete/CommunityProvider.js b/src/autocomplete/CommunityProvider.tsx similarity index 91% rename from src/autocomplete/CommunityProvider.js rename to src/autocomplete/CommunityProvider.tsx index b863603aae..f1a5c0f2ca 100644 --- a/src/autocomplete/CommunityProvider.js +++ b/src/autocomplete/CommunityProvider.tsx @@ -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; + 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 { + async getCompletions(query: string, selection: ISelectionRange, force: boolean = false): Promise { 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 (
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 { + 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 { + 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, -}; diff --git a/src/autocomplete/DuckDuckGoProvider.js b/src/autocomplete/DuckDuckGoProvider.tsx similarity index 90% rename from src/autocomplete/DuckDuckGoProvider.js rename to src/autocomplete/DuckDuckGoProvider.tsx index 8cff83554a..5a024d956c 100644 --- a/src/autocomplete/DuckDuckGoProvider.js +++ b/src/autocomplete/DuckDuckGoProvider.tsx @@ -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 { 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 (
{ +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; + nameMatcher: QueryMatcher; + 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 { + async getCompletions(query: string, selection: ISelectionRange, force?: boolean): Promise { 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 (
{ completions } diff --git a/src/autocomplete/NotifProvider.js b/src/autocomplete/NotifProvider.tsx similarity index 86% rename from src/autocomplete/NotifProvider.js rename to src/autocomplete/NotifProvider.tsx index e7c8f6f70d..b01f52fd23 100644 --- a/src/autocomplete/NotifProvider.js +++ b/src/autocomplete/NotifProvider.tsx @@ -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 { + async getCompletions(query: string, selection: ISelectionRange, force= false): Promise { 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 (
@@ -26,6 +25,13 @@ function stripDiacritics(str: string): string { return str.normalize('NFD').replace(/[\u0300-\u036f]/g, ''); } +interface IOptions { + keys: Array; + 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, options: {[Object]: Object} = {}) { +export default class QueryMatcher { + private _options: IOptions; + private _keys: IOptions["keys"]; + private _funcs: Required["funcs"]>; + private _items: Map; + + constructor(objects: T[], options: IOptions = { keys: [] }) { this._options = options; this._keys = options.keys; this._funcs = options.funcs || []; @@ -60,7 +71,7 @@ export default class QueryMatcher { } } - setObjects(objects: Array) { + setObjects(objects: T[]) { this._items = new Map(); for (const object of objects) { @@ -81,7 +92,7 @@ export default class QueryMatcher { } } - match(query: String): Array { + match(query: string): T[] { query = stripDiacritics(query).toLowerCase(); if (this._options.shouldMatchWordsOnly) { query = query.replace(/[^\w]/g, ''); diff --git a/src/autocomplete/RoomProvider.js b/src/autocomplete/RoomProvider.tsx similarity index 92% rename from src/autocomplete/RoomProvider.js rename to src/autocomplete/RoomProvider.tsx index a0f670e769..6d98b37318 100644 --- a/src/autocomplete/RoomProvider.js +++ b/src/autocomplete/RoomProvider.tsx @@ -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; + 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 { + async getCompletions(query: string, selection: ISelectionRange, force = false): Promise { 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 (
= null; - room: Room = null; + matcher: QueryMatcher; + 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 { + async getCompletions(query: string, selection: ISelectionRange, force = false): Promise { 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 (
{ completions } diff --git a/src/components/views/rooms/Autocomplete.js b/src/components/views/rooms/Autocomplete.tsx similarity index 80% rename from src/components/views/rooms/Autocomplete.js rename to src/components/views/rooms/Autocomplete.tsx index 76a3a19e00..d4fcc5852b 100644 --- a/src/components/views/rooms/Autocomplete.js +++ b/src/components/views/rooms/Autocomplete.tsx @@ -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 { + 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; + 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 ? ( -
this.container = e}> +
{ renderedCompletions }
) : 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), -}; diff --git a/tsconfig.json b/tsconfig.json index d70e0a85f0..dc61d72419 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,6 +2,7 @@ "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true, + "resolveJsonModule": true, "module": "commonjs", "moduleResolution": "node", "target": "es2016", From 6328a60301d8439735a57121f27d31edd4de3f74 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 20 Apr 2020 19:02:27 +0100 Subject: [PATCH 02/10] improve member name Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/rooms/Autocomplete.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/views/rooms/Autocomplete.tsx b/src/components/views/rooms/Autocomplete.tsx index d4fcc5852b..68d82f0bc3 100644 --- a/src/components/views/rooms/Autocomplete.tsx +++ b/src/components/views/rooms/Autocomplete.tsx @@ -65,7 +65,7 @@ export default class Autocomplete extends React.PureComponent { autocompleter: Autocompleter; queryRequested: string; debounceCompletionsRequest: NodeJS.Timeout; - container: React.RefObject; + containerRef: React.RefObject; constructor(props) { super(props); @@ -90,7 +90,7 @@ export default class Autocomplete extends React.PureComponent { forceComplete: false, }; - this.container = React.createRef(); + this.containerRef = React.createRef(); } componentDidMount() { @@ -268,12 +268,12 @@ export default class Autocomplete extends React.PureComponent { 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.current) { + if (selectedCompletion && this.containerRef.current) { const domNode = ReactDOM.findDOMNode(selectedCompletion); const offsetTop = domNode && domNode.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; + if (offsetTop > this.containerRef.current.scrollTop + this.containerRef.current.offsetHeight || + offsetTop < this.containerRef.current.scrollTop) { + this.containerRef.current.scrollTop = offsetTop - this.containerRef.current.offsetTop; } } } @@ -311,7 +311,7 @@ export default class Autocomplete extends React.PureComponent { }).filter((completion) => !!completion); return !this.state.hide && renderedCompletions.length > 0 ? ( -
+
{ renderedCompletions }
) : null; From 5c57b9ab9b3fdc35148549498e89f6dcb6cd3522 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 20 Apr 2020 19:04:55 +0100 Subject: [PATCH 03/10] delint Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/autocomplete/AutocompleteProvider.tsx | 4 ++-- src/autocomplete/CommunityProvider.tsx | 2 +- src/autocomplete/UserProvider.tsx | 4 ++-- src/components/views/rooms/Autocomplete.tsx | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/autocomplete/AutocompleteProvider.tsx b/src/autocomplete/AutocompleteProvider.tsx index 7efe823250..3749d9bb2c 100644 --- a/src/autocomplete/AutocompleteProvider.tsx +++ b/src/autocomplete/AutocompleteProvider.tsx @@ -64,14 +64,14 @@ export default class AutocompleteProvider { commandRegex = this.forcedCommandRegex || /\S+/g; } - if (commandRegex == null) { + if (commandRegex === null) { return null; } commandRegex.lastIndex = 0; let match; - while ((match = commandRegex.exec(query)) != null) { + while ((match = commandRegex.exec(query)) !== null) { const start = match.index; const end = start + match[0].length; if (selection.start <= end && selection.end >= start) { diff --git a/src/autocomplete/CommunityProvider.tsx b/src/autocomplete/CommunityProvider.tsx index f1a5c0f2ca..18ec125cb1 100644 --- a/src/autocomplete/CommunityProvider.tsx +++ b/src/autocomplete/CommunityProvider.tsx @@ -49,7 +49,7 @@ export default class CommunityProvider extends AutocompleteProvider { }); } - async getCompletions(query: string, selection: ISelectionRange, force: boolean = false): Promise { + async getCompletions(query: string, selection: ISelectionRange, force = false): Promise { const BaseAvatar = sdk.getComponent('views.avatars.BaseAvatar'); // Disable autocompletions when composing commands because of various issues diff --git a/src/autocomplete/UserProvider.tsx b/src/autocomplete/UserProvider.tsx index 35ea53f6c0..0500329ec7 100644 --- a/src/autocomplete/UserProvider.tsx +++ b/src/autocomplete/UserProvider.tsx @@ -98,14 +98,14 @@ export default class UserProvider extends AutocompleteProvider { this.users = null; }; - async getCompletions(query: string, selection: ISelectionRange, force = false): Promise { + async getCompletions(rawQuery: string, selection: ISelectionRange, force = false): Promise { const MemberAvatar = sdk.getComponent('views.avatars.MemberAvatar'); // lazy-load user list into matcher if (this.users === null) this._makeUsers(); let completions = []; - const {command, range} = this.getCurrentCommand(query, selection, force); + const {command, range} = this.getCurrentCommand(rawQuery, selection, force); if (!command) return completions; diff --git a/src/components/views/rooms/Autocomplete.tsx b/src/components/views/rooms/Autocomplete.tsx index 68d82f0bc3..3a2d5272f8 100644 --- a/src/components/views/rooms/Autocomplete.tsx +++ b/src/components/views/rooms/Autocomplete.tsx @@ -281,7 +281,7 @@ export default class Autocomplete extends React.PureComponent { render() { let position = 1; const renderedCompletions = this.state.completions.map((completionResult, i) => { - const completions = completionResult.completions.map((completion, i) => { + const completions = completionResult.completions.map((completion, j) => { const selected = position === this.state.selectionOffset; const className = classNames('mx_Autocomplete_Completion', {selected}); const componentPosition = position; @@ -292,7 +292,7 @@ export default class Autocomplete extends React.PureComponent { }; return React.cloneElement(completion.component, { - "key": i, + "key": j, "ref": `completion${componentPosition}`, "id": generateCompletionDomId(componentPosition - 1), // 0 index the completion IDs className, From 66d2a67142f7172b2e71db6d9e357a8a401d7e50 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 20 Apr 2020 19:17:58 +0100 Subject: [PATCH 04/10] deduplicate emojibase loading Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/autocomplete/EmojiProvider.tsx | 18 ++---------------- src/{emoji.js => emoji.ts} | 26 ++++++++++++++++++++++---- 2 files changed, 24 insertions(+), 20 deletions(-) rename src/{emoji.js => emoji.ts} (79%) diff --git a/src/autocomplete/EmojiProvider.tsx b/src/autocomplete/EmojiProvider.tsx index be6b590248..27e24af205 100644 --- a/src/autocomplete/EmojiProvider.tsx +++ b/src/autocomplete/EmojiProvider.tsx @@ -27,36 +27,22 @@ import _uniq from 'lodash/uniq'; import _sortBy from 'lodash/sortBy'; import SettingsStore from "../settings/SettingsStore"; import { shortcodeToUnicode } from '../HtmlUtils'; +import { EMOJI, IEmoji } from '../emoji'; import EMOTICON_REGEX from 'emojibase-regex/emoticon'; -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: IEmojiShort[] = (EMOJIBASE as IEmoji[]).sort((a, b) => { +const EMOJI_SHORTNAMES: IEmojiShort[] = EMOJI.sort((a, b) => { if (a.group === b.group) { return a.order - b.order; } diff --git a/src/emoji.js b/src/emoji.ts similarity index 79% rename from src/emoji.js rename to src/emoji.ts index 20b05531ca..c0a755145a 100644 --- a/src/emoji.js +++ b/src/emoji.ts @@ -14,12 +14,28 @@ See the License for the specific language governing permissions and limitations under the License. */ +// @ts-ignore - import * as EMOJIBASE actually breaks this import EMOJIBASE from 'emojibase-data/en/compact.json'; +export interface IEmoji { + annotation: string; + group: number; + hexcode: string; + order: number; + shortcodes: string[]; + tags: string[]; + unicode: string; + emoticon?: string; +} + +interface IEmojiWithFilterString extends IEmoji { + filterString?: string; +} + // The unicode is stored without the variant selector -const UNICODE_TO_EMOJI = new Map(); // not exported as gets for it are handled by getEmojiFromUnicode -export const EMOTICON_TO_EMOJI = new Map(); -export const SHORTCODE_TO_EMOJI = new Map(); +const UNICODE_TO_EMOJI = new Map(); // not exported as gets for it are handled by getEmojiFromUnicode +export const EMOTICON_TO_EMOJI = new Map(); +export const SHORTCODE_TO_EMOJI = new Map(); export const getEmojiFromUnicode = unicode => UNICODE_TO_EMOJI.get(stripVariation(unicode)); @@ -48,7 +64,7 @@ export const DATA_BY_CATEGORY = { }; // Store various mappings from unicode/emoticon/shortcode to the Emoji objects -EMOJIBASE.forEach(emoji => { +EMOJIBASE.forEach((emoji: IEmojiWithFilterString) => { const categoryId = EMOJIBASE_GROUP_ID_TO_CATEGORY[emoji.group]; if (DATA_BY_CATEGORY.hasOwnProperty(categoryId)) { DATA_BY_CATEGORY[categoryId].push(emoji); @@ -89,3 +105,5 @@ EMOJIBASE.forEach(emoji => { function stripVariation(str) { return str.replace(/[\uFE00-\uFE0F]$/, ""); } + +export const EMOJI: IEmoji[] = EMOJIBASE; From 8398e83d33d1f303b34bb36b8b6161613275f4d7 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 20 Apr 2020 20:35:57 +0100 Subject: [PATCH 05/10] add more type annotations Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/rooms/Autocomplete.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/views/rooms/Autocomplete.tsx b/src/components/views/rooms/Autocomplete.tsx index 3a2d5272f8..e27aeb5a58 100644 --- a/src/components/views/rooms/Autocomplete.tsx +++ b/src/components/views/rooms/Autocomplete.tsx @@ -115,7 +115,7 @@ export default class Autocomplete extends React.PureComponent { this.autocompleter.destroy(); } - complete(query, selection) { + complete(query: string, selection: ISelectionRange) { this.queryRequested = query; if (this.debounceCompletionsRequest) { clearTimeout(this.debounceCompletionsRequest); @@ -146,7 +146,7 @@ export default class Autocomplete extends React.PureComponent { }); } - processQuery(query, selection) { + processQuery(query: string, selection: ISelectionRange) { return this.autocompleter.getCompletions( query, selection, this.state.forceComplete, ).then((completions) => { @@ -158,7 +158,7 @@ export default class Autocomplete extends React.PureComponent { }); } - processCompletions(completions) { + processCompletions(completions: IProviderCompletions[]) { const completionList = flatMap(completions, (provider) => provider.completions); // Reset selection when completion list becomes empty. @@ -202,7 +202,7 @@ export default class Autocomplete extends React.PureComponent { } // called from MessageComposerInput - moveSelection(delta): ICompletion | undefined { + moveSelection(delta: number) { const completionCount = this.countCompletions(); if (completionCount === 0) return; // there are no items to move the selection through @@ -211,7 +211,7 @@ export default class Autocomplete extends React.PureComponent { this.setSelection(index); } - onEscape(e): boolean { + onEscape(e: KeyboardEvent): boolean { const completionCount = this.countCompletions(); if (completionCount === 0) { // autocomplete is already empty, so don't preventDefault @@ -264,7 +264,7 @@ export default class Autocomplete extends React.PureComponent { } } - componentDidUpdate(prevProps) { + componentDidUpdate(prevProps: IProps) { 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}`]; From 3b245ee678d781080c1ad815d5bb7667332c3212 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 20 Apr 2020 21:05:00 +0100 Subject: [PATCH 06/10] add more type annotations Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/autocomplete/RoomProvider.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/autocomplete/RoomProvider.tsx b/src/autocomplete/RoomProvider.tsx index 6d98b37318..9e72363432 100644 --- a/src/autocomplete/RoomProvider.tsx +++ b/src/autocomplete/RoomProvider.tsx @@ -31,7 +31,7 @@ import {ICompletion, ISelectionRange} from "./Autocompleter"; const ROOM_REGEX = /\B#\S*/g; -function score(query, space) { +function score(query: string, space: string) { const index = space.indexOf(query); if (index === -1) { return Infinity; @@ -40,7 +40,7 @@ function score(query, space) { } } -function matcherObject(room, displayedAlias, matchName = "") { +function matcherObject(room: Room, displayedAlias: string, matchName = "") { return { room, matchName, From bf891c85e849a7b47ef656cd85dd5f66c3e40ffc Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 21 Apr 2020 10:01:05 +0100 Subject: [PATCH 07/10] Enable esModuleInterop and iterate PR Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/autocomplete/AutocompleteProvider.tsx | 2 +- src/autocomplete/CommandProvider.tsx | 2 +- src/autocomplete/CommunityProvider.tsx | 2 +- src/autocomplete/Components.tsx | 4 ++-- src/autocomplete/DuckDuckGoProvider.tsx | 2 +- src/autocomplete/EmojiProvider.tsx | 2 +- src/autocomplete/NotifProvider.tsx | 2 +- src/autocomplete/RoomProvider.tsx | 2 +- src/autocomplete/UserProvider.tsx | 14 +++++++------- src/components/views/rooms/Autocomplete.tsx | 6 +++--- tsconfig.json | 1 + 11 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/autocomplete/AutocompleteProvider.tsx b/src/autocomplete/AutocompleteProvider.tsx index 3749d9bb2c..2cda2593a4 100644 --- a/src/autocomplete/AutocompleteProvider.tsx +++ b/src/autocomplete/AutocompleteProvider.tsx @@ -16,7 +16,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import * as React from 'react'; +import React from 'react'; import type {ICompletion, ISelectionRange} from './Autocompleter'; export interface ICommand { diff --git a/src/autocomplete/CommandProvider.tsx b/src/autocomplete/CommandProvider.tsx index b9e8975491..e7a6f44536 100644 --- a/src/autocomplete/CommandProvider.tsx +++ b/src/autocomplete/CommandProvider.tsx @@ -17,7 +17,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import * as React from 'react'; +import React from 'react'; import {_t} from '../languageHandler'; import AutocompleteProvider from './AutocompleteProvider'; import QueryMatcher from './QueryMatcher'; diff --git a/src/autocomplete/CommunityProvider.tsx b/src/autocomplete/CommunityProvider.tsx index 18ec125cb1..3edb1ff81d 100644 --- a/src/autocomplete/CommunityProvider.tsx +++ b/src/autocomplete/CommunityProvider.tsx @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import * as React from 'react'; +import React from 'react'; import Group from "matrix-js-sdk/src/models/group"; import { _t } from '../languageHandler'; import AutocompleteProvider from './AutocompleteProvider'; diff --git a/src/autocomplete/Components.tsx b/src/autocomplete/Components.tsx index 75e42bc8a6..044a090078 100644 --- a/src/autocomplete/Components.tsx +++ b/src/autocomplete/Components.tsx @@ -14,8 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -import * as React from 'react'; -import * as PropTypes from 'prop-types'; +import React from 'react'; +import PropTypes from 'prop-types'; import classNames from 'classnames'; /* These were earlier stateless functional components but had to be converted diff --git a/src/autocomplete/DuckDuckGoProvider.tsx b/src/autocomplete/DuckDuckGoProvider.tsx index 5a024d956c..e63f7255dc 100644 --- a/src/autocomplete/DuckDuckGoProvider.tsx +++ b/src/autocomplete/DuckDuckGoProvider.tsx @@ -16,7 +16,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import * as React from 'react'; +import React from 'react'; import { _t } from '../languageHandler'; import AutocompleteProvider from './AutocompleteProvider'; diff --git a/src/autocomplete/EmojiProvider.tsx b/src/autocomplete/EmojiProvider.tsx index 27e24af205..b67e26117b 100644 --- a/src/autocomplete/EmojiProvider.tsx +++ b/src/autocomplete/EmojiProvider.tsx @@ -17,7 +17,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import * as React from 'react'; +import React from 'react'; import { _t } from '../languageHandler'; import AutocompleteProvider from './AutocompleteProvider'; import QueryMatcher from './QueryMatcher'; diff --git a/src/autocomplete/NotifProvider.tsx b/src/autocomplete/NotifProvider.tsx index b01f52fd23..b217612b0e 100644 --- a/src/autocomplete/NotifProvider.tsx +++ b/src/autocomplete/NotifProvider.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import * as React from 'react'; +import React from 'react'; import Room from "matrix-js-sdk/src/models/room"; import AutocompleteProvider from './AutocompleteProvider'; import { _t } from '../languageHandler'; diff --git a/src/autocomplete/RoomProvider.tsx b/src/autocomplete/RoomProvider.tsx index 9e72363432..01e770407c 100644 --- a/src/autocomplete/RoomProvider.tsx +++ b/src/autocomplete/RoomProvider.tsx @@ -17,7 +17,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import * as React from 'react'; +import React from 'react'; import Room from "matrix-js-sdk/src/models/room"; import { _t } from '../languageHandler'; import AutocompleteProvider from './AutocompleteProvider'; diff --git a/src/autocomplete/UserProvider.tsx b/src/autocomplete/UserProvider.tsx index 0500329ec7..357c8750f7 100644 --- a/src/autocomplete/UserProvider.tsx +++ b/src/autocomplete/UserProvider.tsx @@ -17,7 +17,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import * as React from 'react'; +import React from 'react'; import { _t } from '../languageHandler'; import AutocompleteProvider from './AutocompleteProvider'; import {PillCompletion} from './Components'; @@ -60,18 +60,18 @@ export default class UserProvider extends AutocompleteProvider { shouldMatchWordsOnly: false, }); - MatrixClientPeg.get().on("Room.timeline", this._onRoomTimeline); - MatrixClientPeg.get().on("RoomState.members", this._onRoomStateMember); + MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline); + MatrixClientPeg.get().on("RoomState.members", this.onRoomStateMember); } destroy() { if (MatrixClientPeg.get()) { - MatrixClientPeg.get().removeListener("Room.timeline", this._onRoomTimeline); - MatrixClientPeg.get().removeListener("RoomState.members", this._onRoomStateMember); + MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline); + MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember); } } - _onRoomTimeline = (ev: MatrixEvent, room: Room, toStartOfTimeline: boolean, removed: boolean, + private onRoomTimeline = (ev: MatrixEvent, room: Room, toStartOfTimeline: boolean, removed: boolean, data: IRoomTimelineData) => { if (!room) return; if (removed) return; @@ -88,7 +88,7 @@ export default class UserProvider extends AutocompleteProvider { this.onUserSpoke(ev.sender); }; - _onRoomStateMember = (ev: MatrixEvent, state: RoomState, member: RoomMember) => { + private onRoomStateMember = (ev: MatrixEvent, state: RoomState, member: RoomMember) => { // ignore members in other rooms if (member.roomId !== this.room.roomId) { return; diff --git a/src/components/views/rooms/Autocomplete.tsx b/src/components/views/rooms/Autocomplete.tsx index e27aeb5a58..455b04e1ab 100644 --- a/src/components/views/rooms/Autocomplete.tsx +++ b/src/components/views/rooms/Autocomplete.tsx @@ -94,10 +94,10 @@ export default class Autocomplete extends React.PureComponent { } componentDidMount() { - this._applyNewProps(); + this.applyNewProps(); } - _applyNewProps(oldQuery?: string, oldRoom?: Room) { + private applyNewProps(oldQuery?: string, oldRoom?: Room) { if (oldRoom && this.props.room.roomId !== oldRoom.roomId) { this.autocompleter.destroy(); this.autocompleter = new Autocompleter(this.props.room); @@ -265,7 +265,7 @@ export default class Autocomplete extends React.PureComponent { } componentDidUpdate(prevProps: IProps) { - this._applyNewProps(prevProps.query, prevProps.room); + 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.containerRef.current) { diff --git a/tsconfig.json b/tsconfig.json index dc61d72419..b87f640734 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,6 +3,7 @@ "experimentalDecorators": true, "emitDecoratorMetadata": true, "resolveJsonModule": true, + "esModuleInterop": true, "module": "commonjs", "moduleResolution": "node", "target": "es2016", From 598cf216840c31f3913f581e66db57a61a1e606f Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 21 Apr 2020 18:01:23 +0100 Subject: [PATCH 08/10] discard propTypes. Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/autocomplete/Components.tsx | 16 ---------------- .../views/settings/account/PhoneNumbers.js | 3 ++- 2 files changed, 2 insertions(+), 17 deletions(-) diff --git a/src/autocomplete/Components.tsx b/src/autocomplete/Components.tsx index 044a090078..19a7a969d6 100644 --- a/src/autocomplete/Components.tsx +++ b/src/autocomplete/Components.tsx @@ -15,7 +15,6 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; import classNames from 'classnames'; /* These were earlier stateless functional components but had to be converted @@ -32,13 +31,6 @@ interface ITextualCompletionProps { } export class TextualCompletion extends React.PureComponent { - static propTypes = { - title: PropTypes.string, - subtitle: PropTypes.string, - description: PropTypes.string, - className: PropTypes.string, - }; - render() { const { title, @@ -66,14 +58,6 @@ interface IPillCompletionProps { } export class PillCompletion extends React.PureComponent { - static propTypes = { - title: PropTypes.string, - subtitle: PropTypes.string, - description: PropTypes.string, - initialComponent: PropTypes.element, - className: PropTypes.string, - }; - render() { const { title, diff --git a/src/components/views/settings/account/PhoneNumbers.js b/src/components/views/settings/account/PhoneNumbers.js index ad2dabd8ae..bc0df4bba4 100644 --- a/src/components/views/settings/account/PhoneNumbers.js +++ b/src/components/views/settings/account/PhoneNumbers.js @@ -153,7 +153,8 @@ export default class PhoneNumbers extends React.Component { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const phoneNumber = this.state.newPhoneNumber; - const phoneCountry = this.state.phoneCountry; + // const phoneCountry = this.state.phoneCountry; + const phoneCountry = "XK"; const task = new AddThreepid(); this.setState({verifying: true, continueDisabled: true, addTask: task}); From 65b3adfbe3df4c3eaa86bf555c867658a3323390 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 21 Apr 2020 18:08:01 +0100 Subject: [PATCH 09/10] discard propTypes some more Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/rooms/Autocomplete.tsx | 23 ++++++--------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/src/components/views/rooms/Autocomplete.tsx b/src/components/views/rooms/Autocomplete.tsx index 455b04e1ab..6843df4121 100644 --- a/src/components/views/rooms/Autocomplete.tsx +++ b/src/components/views/rooms/Autocomplete.tsx @@ -15,9 +15,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -import * as React from 'react'; -import * as ReactDOM from 'react-dom'; -import * as PropTypes from 'prop-types'; +import React from 'react'; +import ReactDOM from 'react-dom'; import classNames from 'classnames'; import flatMap from 'lodash/flatMap'; import {ICompletion, ISelectionRange, IProviderCompletions} from '../../../autocomplete/Autocompleter'; @@ -31,10 +30,14 @@ const COMPOSER_SELECTED = 0; export const generateCompletionDomId = (number) => `mx_Autocomplete_Completion_${number}`; interface IProps { + // the query string for which to show autocomplete suggestions query: string; + // method invoked with range and text content when completion is confirmed onConfirm: (ICompletion) => void; + // method invoked when selected (if any) completion changes onSelectionChange?: (ICompletion, number) => void; selection: ISelectionRange; + // The room in which we're autocompleting room: Room; } @@ -48,20 +51,6 @@ interface IState { } export default class Autocomplete extends React.PureComponent { - 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; From fd10ab840e28b90d6bea9ce0d29235ff2baaeba9 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 21 Apr 2020 18:10:47 +0100 Subject: [PATCH 10/10] undo unrelated change Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/settings/account/PhoneNumbers.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/views/settings/account/PhoneNumbers.js b/src/components/views/settings/account/PhoneNumbers.js index bc0df4bba4..ad2dabd8ae 100644 --- a/src/components/views/settings/account/PhoneNumbers.js +++ b/src/components/views/settings/account/PhoneNumbers.js @@ -153,8 +153,7 @@ export default class PhoneNumbers extends React.Component { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const phoneNumber = this.state.newPhoneNumber; - // const phoneCountry = this.state.phoneCountry; - const phoneCountry = "XK"; + const phoneCountry = this.state.phoneCountry; const task = new AddThreepid(); this.setState({verifying: true, continueDisabled: true, addTask: task});