Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into t3chguy/a11y/composer-list-autocomplete

 Conflicts:
	src/components/views/rooms/BasicMessageComposer.tsx
	src/editor/autocomplete.ts
This commit is contained in:
Michael Telatynski 2021-07-15 09:59:40 +01:00
commit ebfe38dc4a
910 changed files with 29171 additions and 17758 deletions

View file

@ -17,7 +17,7 @@ limitations under the License.
*/
import React from 'react';
import type {ICompletion, ISelectionRange} from './Autocompleter';
import type { ICompletion, ISelectionRange } from './Autocompleter';
export interface ICommand {
command: string | null;

View file

@ -15,8 +15,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {ReactElement} from 'react';
import Room from 'matrix-js-sdk/src/models/room';
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';
@ -24,8 +25,8 @@ import RoomProvider from './RoomProvider';
import UserProvider from './UserProvider';
import EmojiProvider from './EmojiProvider';
import NotifProvider from './NotifProvider';
import {timeout} from "../utils/promise";
import AutocompleteProvider, {ICommand} from "./AutocompleteProvider";
import { timeout } from "../utils/promise";
import AutocompleteProvider, { ICommand } from "./AutocompleteProvider";
import SettingsStore from "../settings/SettingsStore";
import SpaceProvider from "./SpaceProvider";
@ -54,13 +55,14 @@ const PROVIDERS = [
EmojiProvider,
NotifProvider,
CommandProvider,
CommunityProvider,
DuckDuckGoProvider,
];
// as the spaces feature is device configurable only, and toggling it refreshes the page, we can do this here
if (SettingsStore.getValue("feature_spaces")) {
PROVIDERS.push(SpaceProvider);
} else {
PROVIDERS.push(CommunityProvider);
}
// Providers will get rejected if they take longer than this.

View file

@ -18,12 +18,12 @@ limitations under the License.
*/
import React from 'react';
import {_t} from '../languageHandler';
import { _t } from '../languageHandler';
import AutocompleteProvider from './AutocompleteProvider';
import QueryMatcher from './QueryMatcher';
import {TextualCompletion} from './Components';
import {ICompletion, ISelectionRange} from "./Autocompleter";
import {Command, Commands, CommandMap} from '../SlashCommands';
import { TextualCompletion } from './Components';
import { ICompletion, ISelectionRange } from "./Autocompleter";
import { Command, Commands, CommandMap } from '../SlashCommands';
const COMMAND_RE = /(^\/\w*)(?: .*)?/g;
@ -34,7 +34,7 @@ export default class CommandProvider extends AutocompleteProvider {
super(COMMAND_RE);
this.matcher = new QueryMatcher(Commands, {
keys: ['command', 'args', 'description'],
funcs: [({aliases}) => aliases.join(" ")], // aliases
funcs: [({ aliases }) => aliases.join(" ")], // aliases
});
}
@ -44,7 +44,7 @@ export default class CommandProvider extends AutocompleteProvider {
force?: boolean,
limit = -1,
): Promise<ICompletion[]> {
const {command, range} = this.getCurrentCommand(query, selection);
const { command, range } = this.getCurrentCommand(query, selection);
if (!command) return [];
let matches = [];
@ -68,7 +68,6 @@ export default class CommandProvider extends AutocompleteProvider {
}
}
return matches.filter(cmd => cmd.isEnabled()).map((result) => {
let completion = result.getCommand() + ' ';
const usedAlias = result.aliases.find(alias => `/${alias}` === command[1]);

View file

@ -19,15 +19,15 @@ import React from 'react';
import Group from "matrix-js-sdk/src/models/group";
import { _t } from '../languageHandler';
import AutocompleteProvider from './AutocompleteProvider';
import {MatrixClientPeg} from '../MatrixClientPeg';
import { MatrixClientPeg } from '../MatrixClientPeg';
import QueryMatcher from './QueryMatcher';
import {PillCompletion} from './Components';
import * as sdk from '../index';
import {sortBy} from "lodash";
import {makeGroupPermalink} from "../utils/permalinks/Permalinks";
import {ICompletion, ISelectionRange} from "./Autocompleter";
import { PillCompletion } from './Components';
import { sortBy } from "lodash";
import { makeGroupPermalink } from "../utils/permalinks/Permalinks";
import { ICompletion, ISelectionRange } from "./Autocompleter";
import FlairStore from "../stores/FlairStore";
import {mediaFromMxc} from "../customisations/Media";
import { mediaFromMxc } from "../customisations/Media";
import BaseAvatar from '../components/views/avatars/BaseAvatar';
const COMMUNITY_REGEX = /\B\+\S*/g;
@ -56,8 +56,6 @@ export default class CommunityProvider extends AutocompleteProvider {
force = false,
limit = -1,
): Promise<ICompletion[]> {
const BaseAvatar = sdk.getComponent('views.avatars.BaseAvatar');
// Disable autocompletions when composing commands because of various issues
// (see https://github.com/vector-im/element-web/issues/4762)
if (/^(\/join|\/leave)/.test(query)) {
@ -66,11 +64,11 @@ export default class CommunityProvider extends AutocompleteProvider {
const cli = MatrixClientPeg.get();
let completions = [];
const {command, range} = this.getCurrentCommand(query, selection, force);
const { command, range } = this.getCurrentCommand(query, selection, force);
if (command) {
const joinedGroups = cli.getGroups().filter(({myMembership}) => myMembership === 'join');
const joinedGroups = cli.getGroups().filter(({ myMembership }) => myMembership === 'join');
const groups = (await Promise.all(joinedGroups.map(async ({groupId}) => {
const groups = (await Promise.all(joinedGroups.map(async ({ groupId }) => {
try {
return FlairStore.getGroupProfileCached(cli, groupId);
} catch (e) { // if FlairStore failed, fall back to just groupId
@ -90,7 +88,7 @@ export default class CommunityProvider extends AutocompleteProvider {
completions = sortBy(completions, [
(c) => score(matchedString, c.groupId),
(c) => c.groupId.length,
]).map(({avatarUrl, groupId, name}) => ({
]).map(({ avatarUrl, groupId, name }) => ({
completion: groupId,
suffix: ' ',
type: "community",

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, {forwardRef} from 'react';
import React, { forwardRef } from 'react';
import classNames from 'classnames';
/* These were earlier stateless functional components but had to be converted
@ -31,7 +31,7 @@ interface ITextualCompletionProps {
}
export const TextualCompletion = forwardRef<ITextualCompletionProps, any>((props, ref) => {
const {title, subtitle, description, className, ...restProps} = props;
const { title, subtitle, description, className, ...restProps } = props;
return (
<div {...restProps}
className={classNames('mx_Autocomplete_Completion_block', className)}
@ -50,7 +50,7 @@ interface IPillCompletionProps extends ITextualCompletionProps {
}
export const PillCompletion = forwardRef<IPillCompletionProps, any>((props, ref) => {
const {title, subtitle, description, className, children, ...restProps} = props;
const { title, subtitle, description, className, children, ...restProps } = props;
return (
<div {...restProps}
className={classNames('mx_Autocomplete_Completion_pill', className)}

View file

@ -20,8 +20,8 @@ import React from 'react';
import { _t } from '../languageHandler';
import AutocompleteProvider from './AutocompleteProvider';
import {TextualCompletion} from './Components';
import {ICompletion, ISelectionRange} from "./Autocompleter";
import { TextualCompletion } from './Components';
import { ICompletion, ISelectionRange } from "./Autocompleter";
const DDG_REGEX = /\/ddg\s+(.+)$/g;
const REFERRER = 'vector';
@ -42,7 +42,7 @@ export default class DuckDuckGoProvider extends AutocompleteProvider {
force = false,
limit = -1,
): Promise<ICompletion[]> {
const {command, range} = this.getCurrentCommand(query, selection);
const { command, range } = this.getCurrentCommand(query, selection);
if (!query || !command) {
return [];
}

View file

@ -21,9 +21,9 @@ import React from 'react';
import { _t } from '../languageHandler';
import AutocompleteProvider from './AutocompleteProvider';
import QueryMatcher from './QueryMatcher';
import {PillCompletion} from './Components';
import {ICompletion, ISelectionRange} from './Autocompleter';
import {uniq, sortBy} from 'lodash';
import { PillCompletion } from './Components';
import { ICompletion, ISelectionRange } from './Autocompleter';
import { uniq, sortBy } from 'lodash';
import SettingsStore from "../settings/SettingsStore";
import { shortcodeToUnicode } from '../HtmlUtils';
import { EMOJI, IEmoji } from '../emoji';
@ -95,7 +95,7 @@ export default class EmojiProvider extends AutocompleteProvider {
}
let completions = [];
const {command, range} = this.getCurrentCommand(query, selection);
const { command, range } = this.getCurrentCommand(query, selection);
if (command) {
const matchedString = command[0];
completions = this.matcher.match(matchedString, limit);
@ -121,7 +121,7 @@ export default class EmojiProvider extends AutocompleteProvider {
sorters.push((c) => c._orderBy);
completions = sortBy(uniq(completions), sorters);
completions = completions.map(({shortname}) => {
completions = completions.map(({ shortname }) => {
const unicode = shortcodeToUnicode(shortname);
return {
completion: unicode,

View file

@ -15,13 +15,14 @@ limitations under the License.
*/
import React from 'react';
import Room from "matrix-js-sdk/src/models/room";
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 {ICompletion, ISelectionRange} from "./Autocompleter";
import { MatrixClientPeg } from '../MatrixClientPeg';
import { PillCompletion } from './Components';
import { ICompletion, ISelectionRange } from "./Autocompleter";
import RoomAvatar from '../components/views/avatars/RoomAvatar';
const AT_ROOM_REGEX = /@\S*/g;
@ -39,13 +40,11 @@ export default class NotifProvider extends AutocompleteProvider {
force = false,
limit = -1,
): Promise<ICompletion[]> {
const RoomAvatar = sdk.getComponent('views.avatars.RoomAvatar');
const client = MatrixClientPeg.get();
if (!this.room.currentState.mayTriggerNotifOfType('room', client.credentials.userId)) return [];
const {command, range} = this.getCurrentCommand(query, selection, force);
const { command, range } = this.getCurrentCommand(query, selection, force);
if (command && command[0] && '@room'.startsWith(command[0]) && command[0].length > 1) {
return [{
completion: '@room',

View file

@ -16,12 +16,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {at, uniq} from 'lodash';
import {removeHiddenChars} from "matrix-js-sdk/src/utils";
import { at, uniq } from 'lodash';
import { removeHiddenChars } from "matrix-js-sdk/src/utils";
interface IOptions<T extends {}> {
keys: Array<string | keyof T>;
funcs?: Array<(T) => string>;
funcs?: Array<(T) => string | string[]>;
shouldMatchWordsOnly?: boolean;
// whether to apply unhomoglyph and strip diacritics to fuzz up the search. Defaults to true
fuzzy?: boolean;
@ -69,7 +69,12 @@ export default class QueryMatcher<T extends Object> {
if (this._options.funcs) {
for (const f of this._options.funcs) {
keyValues.push(f(object));
const v = f(object);
if (Array.isArray(v)) {
keyValues.push(...v);
} else {
keyValues.push(v);
}
}
}
@ -107,7 +112,7 @@ export default class QueryMatcher<T extends Object> {
const index = resultKey.indexOf(query);
if (index !== -1) {
matches.push(
...candidates.map((candidate) => ({index, ...candidate})),
...candidates.map((candidate) => ({ index, ...candidate })),
);
}
}

View file

@ -17,28 +17,24 @@ limitations under the License.
*/
import React from "react";
import {uniqBy, sortBy} from "lodash";
import Room from "matrix-js-sdk/src/models/room";
import { uniqBy, sortBy } from "lodash";
import { Room } from "matrix-js-sdk/src/models/room";
import { _t } from '../languageHandler';
import AutocompleteProvider from './AutocompleteProvider';
import {MatrixClientPeg} from '../MatrixClientPeg';
import { MatrixClientPeg } from '../MatrixClientPeg';
import QueryMatcher from './QueryMatcher';
import {PillCompletion} from './Components';
import {makeRoomPermalink} from "../utils/permalinks/Permalinks";
import {ICompletion, ISelectionRange} from "./Autocompleter";
import { PillCompletion } from './Components';
import { makeRoomPermalink } from "../utils/permalinks/Permalinks";
import { ICompletion, ISelectionRange } from "./Autocompleter";
import RoomAvatar from '../components/views/avatars/RoomAvatar';
import SettingsStore from "../settings/SettingsStore";
const ROOM_REGEX = /\B#\S*/g;
function score(query: string, space: string) {
const index = space.indexOf(query);
if (index === -1) {
return Infinity;
} else {
return index;
}
// Prefer canonical aliases over non-canonical ones
function canonicalScore(displayedAlias: string, room: Room): number {
return displayedAlias === room.getCanonicalAlias() ? 0 : 1;
}
function matcherObject(room: Room, displayedAlias: string, matchName = "") {
@ -77,7 +73,7 @@ export default class RoomProvider extends AutocompleteProvider {
limit = -1,
): Promise<ICompletion[]> {
let completions = [];
const {command, range} = this.getCurrentCommand(query, selection, force);
const { command, range } = this.getCurrentCommand(query, selection, force);
if (command) {
// the only reason we need to do this is because Fuse only matches on properties
let matcherObjects = this.getRooms().reduce((aliases, room) => {
@ -106,7 +102,7 @@ export default class RoomProvider extends AutocompleteProvider {
const matchedString = command[0];
completions = this.matcher.match(matchedString, limit);
completions = sortBy(completions, [
(c) => score(matchedString, c.displayedAlias),
(c) => canonicalScore(c.displayedAlias, c.room),
(c) => c.displayedAlias.length,
]);
completions = uniqBy(completions, (match) => match.room);

View file

@ -17,7 +17,7 @@ limitations under the License.
import React from "react";
import { _t } from '../languageHandler';
import {MatrixClientPeg} from '../MatrixClientPeg';
import { MatrixClientPeg } from '../MatrixClientPeg';
import RoomProvider from "./RoomProvider";
export default class SpaceProvider extends RoomProvider {

View file

@ -20,19 +20,19 @@ limitations under the License.
import React from 'react';
import { _t } from '../languageHandler';
import AutocompleteProvider from './AutocompleteProvider';
import {PillCompletion} from './Components';
import * as sdk from '../index';
import { PillCompletion } from './Components';
import QueryMatcher from './QueryMatcher';
import {sortBy} from 'lodash';
import {MatrixClientPeg} from '../MatrixClientPeg';
import { sortBy } from 'lodash';
import { MatrixClientPeg } from '../MatrixClientPeg';
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 {ICompletion, ISelectionRange} from "./Autocompleter";
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 { ICompletion, ISelectionRange } from "./Autocompleter";
import MemberAvatar from '../components/views/avatars/MemberAvatar';
const USER_REGEX = /\B@\S*/g;
@ -108,13 +108,11 @@ export default class UserProvider extends AutocompleteProvider {
force = false,
limit = -1,
): Promise<ICompletion[]> {
const MemberAvatar = sdk.getComponent('views.avatars.MemberAvatar');
// lazy-load user list into matcher
if (!this.users) this._makeUsers();
let completions = [];
const {command, range} = this.getCurrentCommand(rawQuery, selection, force);
const { command, range } = this.getCurrentCommand(rawQuery, selection, force);
if (!command) return completions;
@ -158,7 +156,7 @@ export default class UserProvider extends AutocompleteProvider {
}
const currentUserId = MatrixClientPeg.get().credentials.userId;
this.users = this.room.getJoinedMembers().filter(({userId}) => userId !== currentUserId);
this.users = this.room.getJoinedMembers().filter(({ userId }) => userId !== currentUserId);
this.users = this.users.concat(this.room.getMembersWithMembership("invite"));
this.users = sortBy(this.users, (member) => 1E20 - lastSpoken[member.userId] || 1E20);