Apply prettier formatting
This commit is contained in:
parent
1cac306093
commit
526645c791
1576 changed files with 65385 additions and 62478 deletions
|
@ -16,10 +16,10 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React from "react";
|
||||
|
||||
import { TimelineRenderingType } from '../contexts/RoomContext';
|
||||
import type { ICompletion, ISelectionRange } from './Autocompleter';
|
||||
import { TimelineRenderingType } from "../contexts/RoomContext";
|
||||
import type { ICompletion, ISelectionRange } from "./Autocompleter";
|
||||
|
||||
export interface ICommand {
|
||||
command: string | null;
|
||||
|
@ -44,13 +44,13 @@ export default abstract class AutocompleteProvider {
|
|||
protected constructor({ commandRegex, forcedCommandRegex, renderingType }: IAutocompleteOptions) {
|
||||
if (commandRegex) {
|
||||
if (!commandRegex.global) {
|
||||
throw new Error('commandRegex must have global flag set');
|
||||
throw new Error("commandRegex must have global flag set");
|
||||
}
|
||||
this.commandRegex = commandRegex;
|
||||
}
|
||||
if (forcedCommandRegex) {
|
||||
if (!forcedCommandRegex.global) {
|
||||
throw new Error('forcedCommandRegex must have global flag set');
|
||||
throw new Error("forcedCommandRegex must have global flag set");
|
||||
}
|
||||
this.forcedCommandRegex = forcedCommandRegex;
|
||||
}
|
||||
|
|
|
@ -15,18 +15,18 @@ 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 RoomProvider from './RoomProvider';
|
||||
import UserProvider from './UserProvider';
|
||||
import EmojiProvider from './EmojiProvider';
|
||||
import NotifProvider from './NotifProvider';
|
||||
import CommandProvider from "./CommandProvider";
|
||||
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 SpaceProvider from "./SpaceProvider";
|
||||
import { TimelineRenderingType } from '../contexts/RoomContext';
|
||||
import { TimelineRenderingType } from "../contexts/RoomContext";
|
||||
|
||||
export interface ISelectionRange {
|
||||
beginning?: boolean; // whether the selection is in the first block of the editor or not
|
||||
|
@ -47,14 +47,7 @@ export interface ICompletion {
|
|||
href?: string;
|
||||
}
|
||||
|
||||
const PROVIDERS = [
|
||||
UserProvider,
|
||||
RoomProvider,
|
||||
EmojiProvider,
|
||||
NotifProvider,
|
||||
CommandProvider,
|
||||
SpaceProvider,
|
||||
];
|
||||
const PROVIDERS = [UserProvider, RoomProvider, EmojiProvider, NotifProvider, CommandProvider, SpaceProvider];
|
||||
|
||||
// Providers will get rejected if they take longer than this.
|
||||
const PROVIDER_COMPLETION_TIMEOUT = 3000;
|
||||
|
@ -94,28 +87,32 @@ export default class Autocompleter {
|
|||
to predict whether an action will actually do what is intended
|
||||
*/
|
||||
// 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(async provider => {
|
||||
return timeout(
|
||||
provider.getCompletions(query, selection, force, limit),
|
||||
null,
|
||||
PROVIDER_COMPLETION_TIMEOUT,
|
||||
);
|
||||
}));
|
||||
const completionsList: ICompletion[][] = await Promise.all(
|
||||
this.providers.map(async (provider) => {
|
||||
return timeout(
|
||||
provider.getCompletions(query, selection, force, limit),
|
||||
null,
|
||||
PROVIDER_COMPLETION_TIMEOUT,
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
// map then filter to maintain the index for the map-operation, for this.providers to line up
|
||||
return completionsList.map((completions, i) => {
|
||||
if (!completions || !completions.length) return;
|
||||
return completionsList
|
||||
.map((completions, i) => {
|
||||
if (!completions || !completions.length) return;
|
||||
|
||||
return {
|
||||
completions,
|
||||
provider: this.providers[i],
|
||||
return {
|
||||
completions,
|
||||
provider: this.providers[i],
|
||||
|
||||
/* the currently matched "command" the completer tried to complete
|
||||
* we pass this through so that Autocomplete can figure out when to
|
||||
* re-show itself once hidden.
|
||||
*/
|
||||
command: this.providers[i].getCurrentCommand(query, selection, force),
|
||||
};
|
||||
}).filter(Boolean);
|
||||
/* the currently matched "command" the completer tried to complete
|
||||
* we pass this through so that Autocomplete can figure out when to
|
||||
* re-show itself once hidden.
|
||||
*/
|
||||
command: this.providers[i].getCurrentCommand(query, selection, force),
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,16 +17,16 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Room } from 'matrix-js-sdk/src/models/room';
|
||||
import React from "react";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
|
||||
import { _t } from '../languageHandler';
|
||||
import AutocompleteProvider from './AutocompleteProvider';
|
||||
import QueryMatcher from './QueryMatcher';
|
||||
import { TextualCompletion } from './Components';
|
||||
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 { TimelineRenderingType } from '../contexts/RoomContext';
|
||||
import { Command, Commands, CommandMap } from "../SlashCommands";
|
||||
import { TimelineRenderingType } from "../contexts/RoomContext";
|
||||
|
||||
const COMMAND_RE = /(^\/\w*)(?: .*)?/g;
|
||||
|
||||
|
@ -36,7 +36,7 @@ export default class CommandProvider extends AutocompleteProvider {
|
|||
constructor(room: Room, renderingType?: TimelineRenderingType) {
|
||||
super({ commandRegex: COMMAND_RE, renderingType });
|
||||
this.matcher = new QueryMatcher(Commands, {
|
||||
keys: ['command', 'args', 'description'],
|
||||
keys: ["command", "args", "description"],
|
||||
funcs: [({ aliases }) => aliases.join(" ")], // aliases
|
||||
context: renderingType,
|
||||
});
|
||||
|
@ -62,7 +62,7 @@ export default class CommandProvider extends AutocompleteProvider {
|
|||
matches = [CommandMap.get(name)];
|
||||
}
|
||||
} else {
|
||||
if (query === '/') {
|
||||
if (query === "/") {
|
||||
// If they have just entered `/` show everything
|
||||
// We exclude the limit on purpose to have a comprehensive list
|
||||
matches = Commands;
|
||||
|
@ -72,31 +72,36 @@ export default class CommandProvider extends AutocompleteProvider {
|
|||
}
|
||||
}
|
||||
|
||||
return matches.filter(cmd => {
|
||||
const display = !cmd.renderingTypes || cmd.renderingTypes.includes(this.renderingType);
|
||||
return cmd.isEnabled() && display;
|
||||
}).map((result) => {
|
||||
let completion = result.getCommand() + ' ';
|
||||
const usedAlias = result.aliases.find(alias => `/${alias}` === command[1]);
|
||||
// If the command (or an alias) is the same as the one they entered, we don't want to discard their arguments
|
||||
if (usedAlias || result.getCommand() === command[1]) {
|
||||
completion = command[0];
|
||||
}
|
||||
return matches
|
||||
.filter((cmd) => {
|
||||
const display = !cmd.renderingTypes || cmd.renderingTypes.includes(this.renderingType);
|
||||
return cmd.isEnabled() && display;
|
||||
})
|
||||
.map((result) => {
|
||||
let completion = result.getCommand() + " ";
|
||||
const usedAlias = result.aliases.find((alias) => `/${alias}` === command[1]);
|
||||
// If the command (or an alias) is the same as the one they entered, we don't want to discard their arguments
|
||||
if (usedAlias || result.getCommand() === command[1]) {
|
||||
completion = command[0];
|
||||
}
|
||||
|
||||
return {
|
||||
completion,
|
||||
type: "command",
|
||||
component: <TextualCompletion
|
||||
title={`/${usedAlias || result.command}`}
|
||||
subtitle={result.args}
|
||||
description={_t(result.description)} />,
|
||||
range,
|
||||
};
|
||||
});
|
||||
return {
|
||||
completion,
|
||||
type: "command",
|
||||
component: (
|
||||
<TextualCompletion
|
||||
title={`/${usedAlias || result.command}`}
|
||||
subtitle={result.args}
|
||||
description={_t(result.description)}
|
||||
/>
|
||||
),
|
||||
range,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
getName() {
|
||||
return '*️⃣ ' + _t('Commands');
|
||||
return "*️⃣ " + _t("Commands");
|
||||
}
|
||||
|
||||
renderCompletions(completions: React.ReactNode[]): React.ReactNode {
|
||||
|
@ -106,7 +111,7 @@ export default class CommandProvider extends AutocompleteProvider {
|
|||
role="presentation"
|
||||
aria-label={_t("Command Autocomplete")}
|
||||
>
|
||||
{ completions }
|
||||
{completions}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { forwardRef } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import React, { forwardRef } from "react";
|
||||
import classNames from "classnames";
|
||||
|
||||
/* These were earlier stateless functional components but had to be converted
|
||||
since we need to use refs/findDOMNode to access the underlying DOM node to focus the correct completion,
|
||||
|
@ -31,24 +31,18 @@ interface ITextualCompletionProps {
|
|||
}
|
||||
|
||||
export const TextualCompletion = forwardRef<ITextualCompletionProps, any>((props, ref) => {
|
||||
const {
|
||||
title,
|
||||
subtitle,
|
||||
description,
|
||||
className,
|
||||
'aria-selected': ariaSelectedAttribute,
|
||||
...restProps
|
||||
} = props;
|
||||
const { title, subtitle, description, className, "aria-selected": ariaSelectedAttribute, ...restProps } = props;
|
||||
return (
|
||||
<div {...restProps}
|
||||
className={classNames('mx_Autocomplete_Completion_block', className)}
|
||||
<div
|
||||
{...restProps}
|
||||
className={classNames("mx_Autocomplete_Completion_block", className)}
|
||||
role="option"
|
||||
aria-selected={ariaSelectedAttribute}
|
||||
ref={ref}
|
||||
>
|
||||
<span className="mx_Autocomplete_Completion_title">{ title }</span>
|
||||
<span className="mx_Autocomplete_Completion_subtitle">{ subtitle }</span>
|
||||
<span className="mx_Autocomplete_Completion_description">{ description }</span>
|
||||
<span className="mx_Autocomplete_Completion_title">{title}</span>
|
||||
<span className="mx_Autocomplete_Completion_subtitle">{subtitle}</span>
|
||||
<span className="mx_Autocomplete_Completion_description">{description}</span>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -64,20 +58,21 @@ export const PillCompletion = forwardRef<IPillCompletionProps, any>((props, ref)
|
|||
description,
|
||||
className,
|
||||
children,
|
||||
'aria-selected': ariaSelectedAttribute,
|
||||
"aria-selected": ariaSelectedAttribute,
|
||||
...restProps
|
||||
} = props;
|
||||
return (
|
||||
<div {...restProps}
|
||||
className={classNames('mx_Autocomplete_Completion_pill', className)}
|
||||
<div
|
||||
{...restProps}
|
||||
className={classNames("mx_Autocomplete_Completion_pill", className)}
|
||||
role="option"
|
||||
aria-selected={ariaSelectedAttribute}
|
||||
ref={ref}
|
||||
>
|
||||
{ children }
|
||||
<span className="mx_Autocomplete_Completion_title">{ title }</span>
|
||||
<span className="mx_Autocomplete_Completion_subtitle">{ subtitle }</span>
|
||||
<span className="mx_Autocomplete_Completion_description">{ description }</span>
|
||||
{children}
|
||||
<span className="mx_Autocomplete_Completion_title">{title}</span>
|
||||
<span className="mx_Autocomplete_Completion_subtitle">{subtitle}</span>
|
||||
<span className="mx_Autocomplete_Completion_description">{description}</span>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -18,26 +18,26 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { uniq, sortBy } from 'lodash';
|
||||
import EMOTICON_REGEX from 'emojibase-regex/emoticon';
|
||||
import { Room } from 'matrix-js-sdk/src/models/room';
|
||||
import React from "react";
|
||||
import { uniq, sortBy } from "lodash";
|
||||
import EMOTICON_REGEX from "emojibase-regex/emoticon";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
|
||||
import { _t } from '../languageHandler';
|
||||
import AutocompleteProvider from './AutocompleteProvider';
|
||||
import QueryMatcher from './QueryMatcher';
|
||||
import { PillCompletion } from './Components';
|
||||
import { ICompletion, ISelectionRange } from './Autocompleter';
|
||||
import { _t } from "../languageHandler";
|
||||
import AutocompleteProvider from "./AutocompleteProvider";
|
||||
import QueryMatcher from "./QueryMatcher";
|
||||
import { PillCompletion } from "./Components";
|
||||
import { ICompletion, ISelectionRange } from "./Autocompleter";
|
||||
import SettingsStore from "../settings/SettingsStore";
|
||||
import { EMOJI, IEmoji, getEmojiFromUnicode } from '../emoji';
|
||||
import { TimelineRenderingType } from '../contexts/RoomContext';
|
||||
import * as recent from '../emojipicker/recent';
|
||||
import { EMOJI, IEmoji, getEmojiFromUnicode } from "../emoji";
|
||||
import { TimelineRenderingType } from "../contexts/RoomContext";
|
||||
import * as recent from "../emojipicker/recent";
|
||||
|
||||
const LIMIT = 20;
|
||||
|
||||
// Match for ascii-style ";-)" emoticons or ":wink:" shortcodes provided by emojibase
|
||||
// anchored to only match from the start of parts otherwise it'll show emoji suggestions whilst typing matrix IDs
|
||||
const EMOJI_REGEX = new RegExp('(' + EMOTICON_REGEX.source + '|(?:^|\\s):[+-\\w]*:?)$', 'g');
|
||||
const EMOJI_REGEX = new RegExp("(" + EMOTICON_REGEX.source + "|(?:^|\\s):[+-\\w]*:?)$", "g");
|
||||
|
||||
interface ISortedEmoji {
|
||||
emoji: IEmoji;
|
||||
|
@ -80,12 +80,12 @@ export default class EmojiProvider extends AutocompleteProvider {
|
|||
super({ commandRegex: EMOJI_REGEX, renderingType });
|
||||
this.matcher = new QueryMatcher<ISortedEmoji>(SORTED_EMOJI, {
|
||||
keys: [],
|
||||
funcs: [o => o.emoji.shortcodes.map(s => `:${s}:`)],
|
||||
funcs: [(o) => o.emoji.shortcodes.map((s) => `:${s}:`)],
|
||||
// For matching against ascii equivalents
|
||||
shouldMatchWordsOnly: false,
|
||||
});
|
||||
this.nameMatcher = new QueryMatcher(SORTED_EMOJI, {
|
||||
keys: ['emoji.label'],
|
||||
keys: ["emoji.label"],
|
||||
// For removing punctuation
|
||||
shouldMatchWordsOnly: true,
|
||||
});
|
||||
|
@ -115,39 +115,37 @@ export default class EmojiProvider extends AutocompleteProvider {
|
|||
|
||||
let sorters = [];
|
||||
// make sure that emoticons come first
|
||||
sorters.push(c => score(matchedString, c.emoji.emoticon || ""));
|
||||
sorters.push((c) => score(matchedString, c.emoji.emoticon || ""));
|
||||
|
||||
// then sort by score (Infinity if matchedString not in shortcode)
|
||||
sorters.push(c => score(matchedString, c.emoji.shortcodes[0]));
|
||||
sorters.push((c) => score(matchedString, c.emoji.shortcodes[0]));
|
||||
// then sort by max score of all shortcodes, trim off the `:`
|
||||
const trimmedMatch = colonsTrimmed(matchedString);
|
||||
sorters.push(c => Math.min(
|
||||
...c.emoji.shortcodes.map(s => score(trimmedMatch, s)),
|
||||
));
|
||||
sorters.push((c) => Math.min(...c.emoji.shortcodes.map((s) => score(trimmedMatch, s))));
|
||||
// If the matchedString is not empty, sort by length of shortcode. Example:
|
||||
// matchedString = ":bookmark"
|
||||
// completions = [":bookmark:", ":bookmark_tabs:", ...]
|
||||
if (matchedString.length > 1) {
|
||||
sorters.push(c => c.emoji.shortcodes[0].length);
|
||||
sorters.push((c) => c.emoji.shortcodes[0].length);
|
||||
}
|
||||
// Finally, sort by original ordering
|
||||
sorters.push(c => c._orderBy);
|
||||
sorters.push((c) => c._orderBy);
|
||||
completions = sortBy<ISortedEmoji>(uniq(completions), sorters);
|
||||
|
||||
completions = completions.slice(0, LIMIT);
|
||||
|
||||
// Do a second sort to place emoji matching with frequently used one on top
|
||||
sorters = [];
|
||||
this.recentlyUsed.forEach(emoji => {
|
||||
sorters.push(c => score(emoji.shortcodes[0], c.emoji.shortcodes[0]));
|
||||
this.recentlyUsed.forEach((emoji) => {
|
||||
sorters.push((c) => score(emoji.shortcodes[0], c.emoji.shortcodes[0]));
|
||||
});
|
||||
completions = sortBy<ISortedEmoji>(uniq(completions), sorters);
|
||||
|
||||
return completions.map(c => ({
|
||||
return completions.map((c) => ({
|
||||
completion: c.emoji.unicode,
|
||||
component: (
|
||||
<PillCompletion title={`:${c.emoji.shortcodes[0]}:`} aria-label={c.emoji.unicode}>
|
||||
<span>{ c.emoji.unicode }</span>
|
||||
<span>{c.emoji.unicode}</span>
|
||||
</PillCompletion>
|
||||
),
|
||||
range,
|
||||
|
@ -157,7 +155,7 @@ export default class EmojiProvider extends AutocompleteProvider {
|
|||
}
|
||||
|
||||
getName() {
|
||||
return '😃 ' + _t('Emoji');
|
||||
return "😃 " + _t("Emoji");
|
||||
}
|
||||
|
||||
renderCompletions(completions: React.ReactNode[]): React.ReactNode {
|
||||
|
@ -167,7 +165,7 @@ export default class EmojiProvider extends AutocompleteProvider {
|
|||
role="presentation"
|
||||
aria-label={_t("Emoji Autocomplete")}
|
||||
>
|
||||
{ completions }
|
||||
{completions}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -14,16 +14,16 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import 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 AutocompleteProvider from "./AutocompleteProvider";
|
||||
import { _t } from "../languageHandler";
|
||||
import { MatrixClientPeg } from "../MatrixClientPeg";
|
||||
import { PillCompletion } from "./Components";
|
||||
import { ICompletion, ISelectionRange } from "./Autocompleter";
|
||||
import RoomAvatar from '../components/views/avatars/RoomAvatar';
|
||||
import { TimelineRenderingType } from '../contexts/RoomContext';
|
||||
import RoomAvatar from "../components/views/avatars/RoomAvatar";
|
||||
import { TimelineRenderingType } from "../contexts/RoomContext";
|
||||
|
||||
const AT_ROOM_REGEX = /@\S*/g;
|
||||
|
||||
|
@ -32,38 +32,36 @@ export default class NotifProvider extends AutocompleteProvider {
|
|||
super({ commandRegex: AT_ROOM_REGEX, renderingType });
|
||||
}
|
||||
|
||||
async getCompletions(
|
||||
query: string,
|
||||
selection: ISelectionRange,
|
||||
force = false,
|
||||
limit = -1,
|
||||
): Promise<ICompletion[]> {
|
||||
async getCompletions(query: string, selection: ISelectionRange, force = false, limit = -1): Promise<ICompletion[]> {
|
||||
const client = MatrixClientPeg.get();
|
||||
|
||||
if (!this.room.currentState.mayTriggerNotifOfType('room', client.credentials.userId)) return [];
|
||||
if (!this.room.currentState.mayTriggerNotifOfType("room", client.credentials.userId)) return [];
|
||||
|
||||
const { command, range } = this.getCurrentCommand(query, selection, force);
|
||||
if (command?.[0].length > 1 &&
|
||||
['@room', '@channel', '@everyone', '@here'].some(c => c.startsWith(command[0]))
|
||||
if (
|
||||
command?.[0].length > 1 &&
|
||||
["@room", "@channel", "@everyone", "@here"].some((c) => c.startsWith(command[0]))
|
||||
) {
|
||||
return [{
|
||||
completion: '@room',
|
||||
completionId: '@room',
|
||||
type: "at-room",
|
||||
suffix: ' ',
|
||||
component: (
|
||||
<PillCompletion title="@room" description={_t("Notify the whole room")}>
|
||||
<RoomAvatar width={24} height={24} room={this.room} />
|
||||
</PillCompletion>
|
||||
),
|
||||
range,
|
||||
}];
|
||||
return [
|
||||
{
|
||||
completion: "@room",
|
||||
completionId: "@room",
|
||||
type: "at-room",
|
||||
suffix: " ",
|
||||
component: (
|
||||
<PillCompletion title="@room" description={_t("Notify the whole room")}>
|
||||
<RoomAvatar width={24} height={24} room={this.room} />
|
||||
</PillCompletion>
|
||||
),
|
||||
range,
|
||||
},
|
||||
];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
getName() {
|
||||
return '❗️ ' + _t('Room Notification');
|
||||
return "❗️ " + _t("Room Notification");
|
||||
}
|
||||
|
||||
renderCompletions(completions: React.ReactNode[]): React.ReactNode {
|
||||
|
@ -73,7 +71,7 @@ export default class NotifProvider extends AutocompleteProvider {
|
|||
role="presentation"
|
||||
aria-label={_t("Notification Autocomplete")}
|
||||
>
|
||||
{ completions }
|
||||
{completions}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -16,10 +16,10 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { at, uniq } from 'lodash';
|
||||
import { at, uniq } from "lodash";
|
||||
import { removeHiddenChars } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import { TimelineRenderingType } from '../contexts/RoomContext';
|
||||
import { TimelineRenderingType } from "../contexts/RoomContext";
|
||||
import { Leaves } from "../@types/common";
|
||||
|
||||
interface IOptions<T extends {}> {
|
||||
|
@ -47,7 +47,7 @@ interface IOptions<T extends {}> {
|
|||
*/
|
||||
export default class QueryMatcher<T extends {}> {
|
||||
private _options: IOptions<T>;
|
||||
private _items: Map<string, {object: T, keyWeight: number}[]>;
|
||||
private _items: Map<string, { object: T; keyWeight: number }[]>;
|
||||
|
||||
constructor(objects: T[], options: IOptions<T> = { keys: [] }) {
|
||||
this._options = options;
|
||||
|
@ -99,7 +99,7 @@ export default class QueryMatcher<T extends {}> {
|
|||
match(query: string, limit = -1): T[] {
|
||||
query = this.processQuery(query);
|
||||
if (this._options.shouldMatchWordsOnly) {
|
||||
query = query.replace(/[^\w]/g, '');
|
||||
query = query.replace(/[^\w]/g, "");
|
||||
}
|
||||
if (query.length === 0) {
|
||||
return [];
|
||||
|
@ -111,13 +111,11 @@ export default class QueryMatcher<T extends {}> {
|
|||
for (const [key, candidates] of this._items.entries()) {
|
||||
let resultKey = key;
|
||||
if (this._options.shouldMatchWordsOnly) {
|
||||
resultKey = resultKey.replace(/[^\w]/g, '');
|
||||
resultKey = resultKey.replace(/[^\w]/g, "");
|
||||
}
|
||||
const index = resultKey.indexOf(query);
|
||||
if (index !== -1) {
|
||||
matches.push(
|
||||
...candidates.map((candidate) => ({ index, ...candidate })),
|
||||
);
|
||||
matches.push(...candidates.map((candidate) => ({ index, ...candidate })));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,14 +20,14 @@ import React from "react";
|
|||
import { sortBy, uniqBy } from "lodash";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
|
||||
import { _t } from '../languageHandler';
|
||||
import AutocompleteProvider from './AutocompleteProvider';
|
||||
import { MatrixClientPeg } from '../MatrixClientPeg';
|
||||
import QueryMatcher from './QueryMatcher';
|
||||
import { PillCompletion } from './Components';
|
||||
import { _t } from "../languageHandler";
|
||||
import AutocompleteProvider from "./AutocompleteProvider";
|
||||
import { MatrixClientPeg } from "../MatrixClientPeg";
|
||||
import QueryMatcher from "./QueryMatcher";
|
||||
import { PillCompletion } from "./Components";
|
||||
import { makeRoomPermalink } from "../utils/permalinks/Permalinks";
|
||||
import { ICompletion, ISelectionRange } from "./Autocompleter";
|
||||
import RoomAvatar from '../components/views/avatars/RoomAvatar';
|
||||
import RoomAvatar from "../components/views/avatars/RoomAvatar";
|
||||
import { TimelineRenderingType } from "../contexts/RoomContext";
|
||||
|
||||
const ROOM_REGEX = /\B#\S*/g;
|
||||
|
@ -51,7 +51,7 @@ export default class RoomProvider extends AutocompleteProvider {
|
|||
constructor(room: Room, renderingType?: TimelineRenderingType) {
|
||||
super({ commandRegex: ROOM_REGEX, renderingType });
|
||||
this.matcher = new QueryMatcher([], {
|
||||
keys: ['displayedAlias', 'matchName'],
|
||||
keys: ["displayedAlias", "matchName"],
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -59,15 +59,10 @@ export default class RoomProvider extends AutocompleteProvider {
|
|||
const cli = MatrixClientPeg.get();
|
||||
|
||||
// filter out spaces here as they get their own autocomplete provider
|
||||
return cli.getVisibleRooms().filter(r => !r.isSpaceRoom());
|
||||
return cli.getVisibleRooms().filter((r) => !r.isSpaceRoom());
|
||||
}
|
||||
|
||||
async getCompletions(
|
||||
query: string,
|
||||
selection: ISelectionRange,
|
||||
force = false,
|
||||
limit = -1,
|
||||
): Promise<ICompletion[]> {
|
||||
async getCompletions(query: string, selection: ISelectionRange, force = false, limit = -1): Promise<ICompletion[]> {
|
||||
let completions = [];
|
||||
const { command, range } = this.getCurrentCommand(query, selection, force);
|
||||
if (command) {
|
||||
|
@ -77,7 +72,7 @@ export default class RoomProvider extends AutocompleteProvider {
|
|||
aliases = aliases.concat(matcherObject(room, room.getCanonicalAlias(), room.name));
|
||||
}
|
||||
if (room.getAltAliases().length) {
|
||||
const altAliases = room.getAltAliases().map(alias => matcherObject(room, alias));
|
||||
const altAliases = room.getAltAliases().map((alias) => matcherObject(room, alias));
|
||||
aliases = aliases.concat(altAliases);
|
||||
}
|
||||
return aliases;
|
||||
|
@ -85,9 +80,9 @@ export default class RoomProvider extends AutocompleteProvider {
|
|||
// Filter out any matches where the user will have also autocompleted new rooms
|
||||
matcherObjects = matcherObjects.filter((r) => {
|
||||
const tombstone = r.room.currentState.getStateEvents("m.room.tombstone", "");
|
||||
if (tombstone && tombstone.getContent() && tombstone.getContent()['replacement_room']) {
|
||||
if (tombstone && tombstone.getContent() && tombstone.getContent()["replacement_room"]) {
|
||||
const hasReplacementRoom = matcherObjects.some(
|
||||
(r2) => r2.room.roomId === tombstone.getContent()['replacement_room'],
|
||||
(r2) => r2.room.roomId === tombstone.getContent()["replacement_room"],
|
||||
);
|
||||
return !hasReplacementRoom;
|
||||
}
|
||||
|
@ -102,27 +97,29 @@ export default class RoomProvider extends AutocompleteProvider {
|
|||
(c) => c.displayedAlias.length,
|
||||
]);
|
||||
completions = uniqBy(completions, (match) => match.room);
|
||||
completions = completions.map((room) => {
|
||||
return {
|
||||
completion: room.displayedAlias,
|
||||
completionId: room.room.roomId,
|
||||
type: "room",
|
||||
suffix: ' ',
|
||||
href: makeRoomPermalink(room.displayedAlias),
|
||||
component: (
|
||||
<PillCompletion title={room.room.name} description={room.displayedAlias}>
|
||||
<RoomAvatar width={24} height={24} room={room.room} />
|
||||
</PillCompletion>
|
||||
),
|
||||
range,
|
||||
};
|
||||
}).filter((completion) => !!completion.completion && completion.completion.length > 0);
|
||||
completions = completions
|
||||
.map((room) => {
|
||||
return {
|
||||
completion: room.displayedAlias,
|
||||
completionId: room.room.roomId,
|
||||
type: "room",
|
||||
suffix: " ",
|
||||
href: makeRoomPermalink(room.displayedAlias),
|
||||
component: (
|
||||
<PillCompletion title={room.room.name} description={room.displayedAlias}>
|
||||
<RoomAvatar width={24} height={24} room={room.room} />
|
||||
</PillCompletion>
|
||||
),
|
||||
range,
|
||||
};
|
||||
})
|
||||
.filter((completion) => !!completion.completion && completion.completion.length > 0);
|
||||
}
|
||||
return completions;
|
||||
}
|
||||
|
||||
getName() {
|
||||
return _t('Rooms');
|
||||
return _t("Rooms");
|
||||
}
|
||||
|
||||
renderCompletions(completions: React.ReactNode[]): React.ReactNode {
|
||||
|
@ -132,7 +129,7 @@ export default class RoomProvider extends AutocompleteProvider {
|
|||
role="presentation"
|
||||
aria-label={_t("Room Autocomplete")}
|
||||
>
|
||||
{ completions }
|
||||
{completions}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -16,13 +16,15 @@ limitations under the License.
|
|||
|
||||
import React from "react";
|
||||
|
||||
import { _t } from '../languageHandler';
|
||||
import { MatrixClientPeg } from '../MatrixClientPeg';
|
||||
import { _t } from "../languageHandler";
|
||||
import { MatrixClientPeg } from "../MatrixClientPeg";
|
||||
import RoomProvider from "./RoomProvider";
|
||||
|
||||
export default class SpaceProvider extends RoomProvider {
|
||||
protected getRooms() {
|
||||
return MatrixClientPeg.get().getVisibleRooms().filter(r => r.isSpaceRoom());
|
||||
return MatrixClientPeg.get()
|
||||
.getVisibleRooms()
|
||||
.filter((r) => r.isSpaceRoom());
|
||||
}
|
||||
|
||||
getName() {
|
||||
|
@ -36,7 +38,7 @@ export default class SpaceProvider extends RoomProvider {
|
|||
role="listbox"
|
||||
aria-label={_t("Space Autocomplete")}
|
||||
>
|
||||
{ completions }
|
||||
{completions}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -17,24 +17,24 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { sortBy } from 'lodash';
|
||||
import React from "react";
|
||||
import { sortBy } from "lodash";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { Room, RoomEvent } from "matrix-js-sdk/src/models/room";
|
||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||
import { RoomState, RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
|
||||
import { IRoomTimelineData } from "matrix-js-sdk/src/models/event-timeline-set";
|
||||
|
||||
import { MatrixClientPeg } from '../MatrixClientPeg';
|
||||
import QueryMatcher from './QueryMatcher';
|
||||
import { PillCompletion } from './Components';
|
||||
import AutocompleteProvider from './AutocompleteProvider';
|
||||
import { _t } from '../languageHandler';
|
||||
import { MatrixClientPeg } from "../MatrixClientPeg";
|
||||
import QueryMatcher from "./QueryMatcher";
|
||||
import { PillCompletion } from "./Components";
|
||||
import AutocompleteProvider from "./AutocompleteProvider";
|
||||
import { _t } from "../languageHandler";
|
||||
import { makeUserPermalink } from "../utils/permalinks/Permalinks";
|
||||
import { ICompletion, ISelectionRange } from "./Autocompleter";
|
||||
import MemberAvatar from '../components/views/avatars/MemberAvatar';
|
||||
import { TimelineRenderingType } from '../contexts/RoomContext';
|
||||
import UserIdentifierCustomisations from '../customisations/UserIdentifier';
|
||||
import MemberAvatar from "../components/views/avatars/MemberAvatar";
|
||||
import { TimelineRenderingType } from "../contexts/RoomContext";
|
||||
import UserIdentifierCustomisations from "../customisations/UserIdentifier";
|
||||
|
||||
const USER_REGEX = /\B@\S*/g;
|
||||
|
||||
|
@ -55,8 +55,8 @@ export default class UserProvider extends AutocompleteProvider {
|
|||
});
|
||||
this.room = room;
|
||||
this.matcher = new QueryMatcher([], {
|
||||
keys: ['name'],
|
||||
funcs: [obj => obj.userId.slice(1)], // index by user id minus the leading '@'
|
||||
keys: ["name"],
|
||||
funcs: [(obj) => obj.userId.slice(1)], // index by user id minus the leading '@'
|
||||
shouldMatchWordsOnly: false,
|
||||
});
|
||||
|
||||
|
@ -117,21 +117,22 @@ export default class UserProvider extends AutocompleteProvider {
|
|||
|
||||
const fullMatch = command[0];
|
||||
// Don't search if the query is a single "@"
|
||||
if (fullMatch && fullMatch !== '@') {
|
||||
if (fullMatch && fullMatch !== "@") {
|
||||
// Don't include the '@' in our search query - it's only used as a way to trigger completion
|
||||
const query = fullMatch.startsWith('@') ? fullMatch.substring(1) : fullMatch;
|
||||
const query = fullMatch.startsWith("@") ? fullMatch.substring(1) : fullMatch;
|
||||
completions = this.matcher.match(query, limit).map((user) => {
|
||||
const description = UserIdentifierCustomisations.getDisplayUserIdentifier(
|
||||
user.userId, { roomId: this.room.roomId, withDisplayName: true },
|
||||
);
|
||||
const displayName = (user.name || user.userId || '');
|
||||
const description = UserIdentifierCustomisations.getDisplayUserIdentifier(user.userId, {
|
||||
roomId: this.room.roomId,
|
||||
withDisplayName: true,
|
||||
});
|
||||
const displayName = user.name || user.userId || "";
|
||||
return {
|
||||
// Length of completion should equal length of text in decorator. draft-js
|
||||
// relies on the length of the entity === length of the text in the decoration.
|
||||
completion: user.rawDisplayName,
|
||||
completionId: user.userId,
|
||||
type: "user",
|
||||
suffix: (selection.beginning && range.start === 0) ? ': ' : ' ',
|
||||
suffix: selection.beginning && range.start === 0 ? ": " : " ",
|
||||
href: makeUserPermalink(user.userId),
|
||||
component: (
|
||||
<PillCompletion title={displayName} description={description}>
|
||||
|
@ -146,7 +147,7 @@ export default class UserProvider extends AutocompleteProvider {
|
|||
}
|
||||
|
||||
getName(): string {
|
||||
return _t('Users');
|
||||
return _t("Users");
|
||||
}
|
||||
|
||||
private makeUsers() {
|
||||
|
@ -161,7 +162,7 @@ export default class UserProvider extends AutocompleteProvider {
|
|||
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);
|
||||
this.users = sortBy(this.users, (member) => 1e20 - lastSpoken[member.userId] || 1e20);
|
||||
|
||||
this.matcher.setObjects(this.users);
|
||||
}
|
||||
|
@ -173,7 +174,9 @@ export default class UserProvider extends AutocompleteProvider {
|
|||
|
||||
// Move the user that spoke to the front of the array
|
||||
this.users.splice(
|
||||
this.users.findIndex((user2) => user2.userId === user.userId), 1);
|
||||
this.users.findIndex((user2) => user2.userId === user.userId),
|
||||
1,
|
||||
);
|
||||
this.users = [user, ...this.users];
|
||||
|
||||
this.matcher.setObjects(this.users);
|
||||
|
@ -186,7 +189,7 @@ export default class UserProvider extends AutocompleteProvider {
|
|||
role="presentation"
|
||||
aria-label={_t("User Autocomplete")}
|
||||
>
|
||||
{ completions }
|
||||
{completions}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue