Chat Effects & Commands in thread context (#7138)
This commit is contained in:
parent
e549438e2a
commit
256c468c15
15 changed files with 108 additions and 31 deletions
|
@ -57,6 +57,7 @@ import SlashCommandHelpDialog from "./components/views/dialogs/SlashCommandHelpD
|
||||||
|
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { shouldShowComponent } from "./customisations/helpers/UIComponents";
|
import { shouldShowComponent } from "./customisations/helpers/UIComponents";
|
||||||
|
import { TimelineRenderingType } from './contexts/RoomContext';
|
||||||
|
|
||||||
// XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816
|
// XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816
|
||||||
interface HTMLInputEvent extends Event {
|
interface HTMLInputEvent extends Event {
|
||||||
|
@ -102,6 +103,7 @@ interface ICommandOpts {
|
||||||
category: string;
|
category: string;
|
||||||
hideCompletionAfterSpace?: boolean;
|
hideCompletionAfterSpace?: boolean;
|
||||||
isEnabled?(): boolean;
|
isEnabled?(): boolean;
|
||||||
|
renderingTypes?: TimelineRenderingType[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Command {
|
export class Command {
|
||||||
|
@ -112,7 +114,8 @@ export class Command {
|
||||||
runFn: undefined | RunFn;
|
runFn: undefined | RunFn;
|
||||||
category: string;
|
category: string;
|
||||||
hideCompletionAfterSpace: boolean;
|
hideCompletionAfterSpace: boolean;
|
||||||
_isEnabled?: () => boolean;
|
private _isEnabled?: () => boolean;
|
||||||
|
public renderingTypes?: TimelineRenderingType[];
|
||||||
|
|
||||||
constructor(opts: ICommandOpts) {
|
constructor(opts: ICommandOpts) {
|
||||||
this.command = opts.command;
|
this.command = opts.command;
|
||||||
|
@ -123,6 +126,7 @@ export class Command {
|
||||||
this.category = opts.category || CommandCategories.other;
|
this.category = opts.category || CommandCategories.other;
|
||||||
this.hideCompletionAfterSpace = opts.hideCompletionAfterSpace || false;
|
this.hideCompletionAfterSpace = opts.hideCompletionAfterSpace || false;
|
||||||
this._isEnabled = opts.isEnabled;
|
this._isEnabled = opts.isEnabled;
|
||||||
|
this.renderingTypes = opts.renderingTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
getCommand() {
|
getCommand() {
|
||||||
|
@ -143,7 +147,7 @@ export class Command {
|
||||||
return _t('Usage') + ': ' + this.getCommandWithArgs();
|
return _t('Usage') + ': ' + this.getCommandWithArgs();
|
||||||
}
|
}
|
||||||
|
|
||||||
isEnabled() {
|
isEnabled(): boolean {
|
||||||
return this._isEnabled ? this._isEnabled() : true;
|
return this._isEnabled ? this._isEnabled() : true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -271,6 +275,7 @@ export const Commands = [
|
||||||
return reject(this.getUsage());
|
return reject(this.getUsage());
|
||||||
},
|
},
|
||||||
category: CommandCategories.admin,
|
category: CommandCategories.admin,
|
||||||
|
renderingTypes: [TimelineRenderingType.Room],
|
||||||
}),
|
}),
|
||||||
new Command({
|
new Command({
|
||||||
command: 'nick',
|
command: 'nick',
|
||||||
|
@ -283,6 +288,7 @@ export const Commands = [
|
||||||
return reject(this.getUsage());
|
return reject(this.getUsage());
|
||||||
},
|
},
|
||||||
category: CommandCategories.actions,
|
category: CommandCategories.actions,
|
||||||
|
renderingTypes: [TimelineRenderingType.Room],
|
||||||
}),
|
}),
|
||||||
new Command({
|
new Command({
|
||||||
command: 'myroomnick',
|
command: 'myroomnick',
|
||||||
|
@ -302,6 +308,7 @@ export const Commands = [
|
||||||
return reject(this.getUsage());
|
return reject(this.getUsage());
|
||||||
},
|
},
|
||||||
category: CommandCategories.actions,
|
category: CommandCategories.actions,
|
||||||
|
renderingTypes: [TimelineRenderingType.Room],
|
||||||
}),
|
}),
|
||||||
new Command({
|
new Command({
|
||||||
command: 'roomavatar',
|
command: 'roomavatar',
|
||||||
|
@ -319,6 +326,7 @@ export const Commands = [
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
category: CommandCategories.actions,
|
category: CommandCategories.actions,
|
||||||
|
renderingTypes: [TimelineRenderingType.Room],
|
||||||
}),
|
}),
|
||||||
new Command({
|
new Command({
|
||||||
command: 'myroomavatar',
|
command: 'myroomavatar',
|
||||||
|
@ -345,6 +353,7 @@ export const Commands = [
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
category: CommandCategories.actions,
|
category: CommandCategories.actions,
|
||||||
|
renderingTypes: [TimelineRenderingType.Room],
|
||||||
}),
|
}),
|
||||||
new Command({
|
new Command({
|
||||||
command: 'myavatar',
|
command: 'myavatar',
|
||||||
|
@ -362,6 +371,7 @@ export const Commands = [
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
category: CommandCategories.actions,
|
category: CommandCategories.actions,
|
||||||
|
renderingTypes: [TimelineRenderingType.Room],
|
||||||
}),
|
}),
|
||||||
new Command({
|
new Command({
|
||||||
command: 'topic',
|
command: 'topic',
|
||||||
|
@ -387,6 +397,7 @@ export const Commands = [
|
||||||
return success();
|
return success();
|
||||||
},
|
},
|
||||||
category: CommandCategories.admin,
|
category: CommandCategories.admin,
|
||||||
|
renderingTypes: [TimelineRenderingType.Room],
|
||||||
}),
|
}),
|
||||||
new Command({
|
new Command({
|
||||||
command: 'roomname',
|
command: 'roomname',
|
||||||
|
@ -399,6 +410,7 @@ export const Commands = [
|
||||||
return reject(this.getUsage());
|
return reject(this.getUsage());
|
||||||
},
|
},
|
||||||
category: CommandCategories.admin,
|
category: CommandCategories.admin,
|
||||||
|
renderingTypes: [TimelineRenderingType.Room],
|
||||||
}),
|
}),
|
||||||
new Command({
|
new Command({
|
||||||
command: 'invite',
|
command: 'invite',
|
||||||
|
@ -462,6 +474,7 @@ export const Commands = [
|
||||||
return reject(this.getUsage());
|
return reject(this.getUsage());
|
||||||
},
|
},
|
||||||
category: CommandCategories.actions,
|
category: CommandCategories.actions,
|
||||||
|
renderingTypes: [TimelineRenderingType.Room],
|
||||||
}),
|
}),
|
||||||
new Command({
|
new Command({
|
||||||
command: 'join',
|
command: 'join',
|
||||||
|
@ -577,6 +590,7 @@ export const Commands = [
|
||||||
return reject(this.getUsage());
|
return reject(this.getUsage());
|
||||||
},
|
},
|
||||||
category: CommandCategories.actions,
|
category: CommandCategories.actions,
|
||||||
|
renderingTypes: [TimelineRenderingType.Room],
|
||||||
}),
|
}),
|
||||||
new Command({
|
new Command({
|
||||||
command: 'part',
|
command: 'part',
|
||||||
|
@ -620,6 +634,7 @@ export const Commands = [
|
||||||
return success(leaveRoomBehaviour(targetRoomId));
|
return success(leaveRoomBehaviour(targetRoomId));
|
||||||
},
|
},
|
||||||
category: CommandCategories.actions,
|
category: CommandCategories.actions,
|
||||||
|
renderingTypes: [TimelineRenderingType.Room],
|
||||||
}),
|
}),
|
||||||
new Command({
|
new Command({
|
||||||
command: 'kick',
|
command: 'kick',
|
||||||
|
@ -635,6 +650,7 @@ export const Commands = [
|
||||||
return reject(this.getUsage());
|
return reject(this.getUsage());
|
||||||
},
|
},
|
||||||
category: CommandCategories.admin,
|
category: CommandCategories.admin,
|
||||||
|
renderingTypes: [TimelineRenderingType.Room],
|
||||||
}),
|
}),
|
||||||
new Command({
|
new Command({
|
||||||
command: 'ban',
|
command: 'ban',
|
||||||
|
@ -650,6 +666,7 @@ export const Commands = [
|
||||||
return reject(this.getUsage());
|
return reject(this.getUsage());
|
||||||
},
|
},
|
||||||
category: CommandCategories.admin,
|
category: CommandCategories.admin,
|
||||||
|
renderingTypes: [TimelineRenderingType.Room],
|
||||||
}),
|
}),
|
||||||
new Command({
|
new Command({
|
||||||
command: 'unban',
|
command: 'unban',
|
||||||
|
@ -666,6 +683,7 @@ export const Commands = [
|
||||||
return reject(this.getUsage());
|
return reject(this.getUsage());
|
||||||
},
|
},
|
||||||
category: CommandCategories.admin,
|
category: CommandCategories.admin,
|
||||||
|
renderingTypes: [TimelineRenderingType.Room],
|
||||||
}),
|
}),
|
||||||
new Command({
|
new Command({
|
||||||
command: 'ignore',
|
command: 'ignore',
|
||||||
|
@ -755,6 +773,7 @@ export const Commands = [
|
||||||
return reject(this.getUsage());
|
return reject(this.getUsage());
|
||||||
},
|
},
|
||||||
category: CommandCategories.admin,
|
category: CommandCategories.admin,
|
||||||
|
renderingTypes: [TimelineRenderingType.Room],
|
||||||
}),
|
}),
|
||||||
new Command({
|
new Command({
|
||||||
command: 'deop',
|
command: 'deop',
|
||||||
|
@ -776,6 +795,7 @@ export const Commands = [
|
||||||
return reject(this.getUsage());
|
return reject(this.getUsage());
|
||||||
},
|
},
|
||||||
category: CommandCategories.admin,
|
category: CommandCategories.admin,
|
||||||
|
renderingTypes: [TimelineRenderingType.Room],
|
||||||
}),
|
}),
|
||||||
new Command({
|
new Command({
|
||||||
command: 'devtools',
|
command: 'devtools',
|
||||||
|
@ -838,6 +858,7 @@ export const Commands = [
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
category: CommandCategories.admin,
|
category: CommandCategories.admin,
|
||||||
|
renderingTypes: [TimelineRenderingType.Room],
|
||||||
}),
|
}),
|
||||||
new Command({
|
new Command({
|
||||||
command: 'verify',
|
command: 'verify',
|
||||||
|
@ -903,6 +924,7 @@ export const Commands = [
|
||||||
return reject(this.getUsage());
|
return reject(this.getUsage());
|
||||||
},
|
},
|
||||||
category: CommandCategories.advanced,
|
category: CommandCategories.advanced,
|
||||||
|
renderingTypes: [TimelineRenderingType.Room],
|
||||||
}),
|
}),
|
||||||
new Command({
|
new Command({
|
||||||
command: 'discardsession',
|
command: 'discardsession',
|
||||||
|
@ -916,6 +938,7 @@ export const Commands = [
|
||||||
return success();
|
return success();
|
||||||
},
|
},
|
||||||
category: CommandCategories.advanced,
|
category: CommandCategories.advanced,
|
||||||
|
renderingTypes: [TimelineRenderingType.Room],
|
||||||
}),
|
}),
|
||||||
new Command({
|
new Command({
|
||||||
command: "rainbow",
|
command: "rainbow",
|
||||||
|
@ -1053,6 +1076,7 @@ export const Commands = [
|
||||||
call.setRemoteOnHold(true);
|
call.setRemoteOnHold(true);
|
||||||
return success();
|
return success();
|
||||||
},
|
},
|
||||||
|
renderingTypes: [TimelineRenderingType.Room],
|
||||||
}),
|
}),
|
||||||
new Command({
|
new Command({
|
||||||
command: "unholdcall",
|
command: "unholdcall",
|
||||||
|
@ -1066,6 +1090,7 @@ export const Commands = [
|
||||||
call.setRemoteOnHold(false);
|
call.setRemoteOnHold(false);
|
||||||
return success();
|
return success();
|
||||||
},
|
},
|
||||||
|
renderingTypes: [TimelineRenderingType.Room],
|
||||||
}),
|
}),
|
||||||
new Command({
|
new Command({
|
||||||
command: "converttodm",
|
command: "converttodm",
|
||||||
|
@ -1075,6 +1100,7 @@ export const Commands = [
|
||||||
const room = MatrixClientPeg.get().getRoom(roomId);
|
const room = MatrixClientPeg.get().getRoom(roomId);
|
||||||
return success(guessAndSetDMRoom(room, true));
|
return success(guessAndSetDMRoom(room, true));
|
||||||
},
|
},
|
||||||
|
renderingTypes: [TimelineRenderingType.Room],
|
||||||
}),
|
}),
|
||||||
new Command({
|
new Command({
|
||||||
command: "converttoroom",
|
command: "converttoroom",
|
||||||
|
@ -1084,6 +1110,7 @@ export const Commands = [
|
||||||
const room = MatrixClientPeg.get().getRoom(roomId);
|
const room = MatrixClientPeg.get().getRoom(roomId);
|
||||||
return success(guessAndSetDMRoom(room, false));
|
return success(guessAndSetDMRoom(room, false));
|
||||||
},
|
},
|
||||||
|
renderingTypes: [TimelineRenderingType.Room],
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// Command definitions for autocompletion ONLY:
|
// Command definitions for autocompletion ONLY:
|
||||||
|
@ -1117,6 +1144,7 @@ export const Commands = [
|
||||||
})());
|
})());
|
||||||
},
|
},
|
||||||
category: CommandCategories.effects,
|
category: CommandCategories.effects,
|
||||||
|
renderingTypes: [TimelineRenderingType.Room],
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { TimelineRenderingType } from '../contexts/RoomContext';
|
||||||
import type { ICompletion, ISelectionRange } from './Autocompleter';
|
import type { ICompletion, ISelectionRange } from './Autocompleter';
|
||||||
|
|
||||||
export interface ICommand {
|
export interface ICommand {
|
||||||
|
@ -27,11 +28,19 @@ export interface ICommand {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IAutocompleteOptions {
|
||||||
|
commandRegex?: RegExp;
|
||||||
|
forcedCommandRegex?: RegExp;
|
||||||
|
renderingType?: TimelineRenderingType;
|
||||||
|
}
|
||||||
|
|
||||||
export default abstract class AutocompleteProvider {
|
export default abstract class AutocompleteProvider {
|
||||||
commandRegex: RegExp;
|
commandRegex: RegExp;
|
||||||
forcedCommandRegex: RegExp;
|
forcedCommandRegex: RegExp;
|
||||||
|
|
||||||
protected constructor(commandRegex?: RegExp, forcedCommandRegex?: RegExp) {
|
protected renderingType: TimelineRenderingType = TimelineRenderingType.Room;
|
||||||
|
|
||||||
|
protected constructor({ commandRegex, forcedCommandRegex, renderingType }: IAutocompleteOptions) {
|
||||||
if (commandRegex) {
|
if (commandRegex) {
|
||||||
if (!commandRegex.global) {
|
if (!commandRegex.global) {
|
||||||
throw new Error('commandRegex must have global flag set');
|
throw new Error('commandRegex must have global flag set');
|
||||||
|
@ -44,6 +53,9 @@ export default abstract class AutocompleteProvider {
|
||||||
}
|
}
|
||||||
this.forcedCommandRegex = forcedCommandRegex;
|
this.forcedCommandRegex = forcedCommandRegex;
|
||||||
}
|
}
|
||||||
|
if (renderingType) {
|
||||||
|
this.renderingType = renderingType;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
|
|
|
@ -28,6 +28,7 @@ import { timeout } from "../utils/promise";
|
||||||
import AutocompleteProvider, { ICommand } from "./AutocompleteProvider";
|
import AutocompleteProvider, { ICommand } from "./AutocompleteProvider";
|
||||||
import SpaceProvider from "./SpaceProvider";
|
import SpaceProvider from "./SpaceProvider";
|
||||||
import SpaceStore from "../stores/spaces/SpaceStore";
|
import SpaceStore from "../stores/spaces/SpaceStore";
|
||||||
|
import { TimelineRenderingType } from '../contexts/RoomContext';
|
||||||
|
|
||||||
export interface ISelectionRange {
|
export interface ISelectionRange {
|
||||||
beginning?: boolean; // whether the selection is in the first block of the editor or not
|
beginning?: boolean; // whether the selection is in the first block of the editor or not
|
||||||
|
@ -75,10 +76,10 @@ export default class Autocompleter {
|
||||||
room: Room;
|
room: Room;
|
||||||
providers: AutocompleteProvider[];
|
providers: AutocompleteProvider[];
|
||||||
|
|
||||||
constructor(room: Room) {
|
constructor(room: Room, renderingType: TimelineRenderingType = TimelineRenderingType.Room) {
|
||||||
this.room = room;
|
this.room = room;
|
||||||
this.providers = PROVIDERS.map((Prov) => {
|
this.providers = PROVIDERS.map((Prov) => {
|
||||||
return new Prov(room);
|
return new Prov(room, renderingType);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,17 +24,20 @@ import QueryMatcher from './QueryMatcher';
|
||||||
import { TextualCompletion } from './Components';
|
import { TextualCompletion } from './Components';
|
||||||
import { ICompletion, ISelectionRange } from "./Autocompleter";
|
import { ICompletion, ISelectionRange } from "./Autocompleter";
|
||||||
import { Command, Commands, CommandMap } from '../SlashCommands';
|
import { Command, Commands, CommandMap } from '../SlashCommands';
|
||||||
|
import { Room } from 'matrix-js-sdk/src/models/room';
|
||||||
|
import { TimelineRenderingType } from '../contexts/RoomContext';
|
||||||
|
|
||||||
const COMMAND_RE = /(^\/\w*)(?: .*)?/g;
|
const COMMAND_RE = /(^\/\w*)(?: .*)?/g;
|
||||||
|
|
||||||
export default class CommandProvider extends AutocompleteProvider {
|
export default class CommandProvider extends AutocompleteProvider {
|
||||||
matcher: QueryMatcher<Command>;
|
matcher: QueryMatcher<Command>;
|
||||||
|
|
||||||
constructor() {
|
constructor(room: Room, renderingType?: TimelineRenderingType) {
|
||||||
super(COMMAND_RE);
|
super({ commandRegex: COMMAND_RE, renderingType });
|
||||||
this.matcher = new QueryMatcher(Commands, {
|
this.matcher = new QueryMatcher(Commands, {
|
||||||
keys: ['command', 'args', 'description'],
|
keys: ['command', 'args', 'description'],
|
||||||
funcs: [({ aliases }) => aliases.join(" ")], // aliases
|
funcs: [({ aliases }) => aliases.join(" ")], // aliases
|
||||||
|
context: renderingType,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +50,7 @@ export default class CommandProvider extends AutocompleteProvider {
|
||||||
const { command, range } = this.getCurrentCommand(query, selection);
|
const { command, range } = this.getCurrentCommand(query, selection);
|
||||||
if (!command) return [];
|
if (!command) return [];
|
||||||
|
|
||||||
let matches = [];
|
let matches: Command[] = [];
|
||||||
// check if the full match differs from the first word (i.e. returns false if the command has args)
|
// check if the full match differs from the first word (i.e. returns false if the command has args)
|
||||||
if (command[0] !== command[1]) {
|
if (command[0] !== command[1]) {
|
||||||
// The input looks like a command with arguments, perform exact match
|
// The input looks like a command with arguments, perform exact match
|
||||||
|
@ -68,7 +71,10 @@ export default class CommandProvider extends AutocompleteProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return matches.filter(cmd => cmd.isEnabled()).map((result) => {
|
return matches.filter(cmd => {
|
||||||
|
const display = !cmd.renderingTypes || cmd.renderingTypes.includes(this.renderingType);
|
||||||
|
return cmd.isEnabled() && display;
|
||||||
|
}).map((result) => {
|
||||||
let completion = result.getCommand() + ' ';
|
let completion = result.getCommand() + ' ';
|
||||||
const usedAlias = result.aliases.find(alias => `/${alias}` === command[1]);
|
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 the command (or an alias) is the same as the one they entered, we don't want to discard their arguments
|
||||||
|
|
|
@ -28,6 +28,8 @@ import { ICompletion, ISelectionRange } from "./Autocompleter";
|
||||||
import FlairStore from "../stores/FlairStore";
|
import FlairStore from "../stores/FlairStore";
|
||||||
import { mediaFromMxc } from "../customisations/Media";
|
import { mediaFromMxc } from "../customisations/Media";
|
||||||
import BaseAvatar from '../components/views/avatars/BaseAvatar';
|
import BaseAvatar from '../components/views/avatars/BaseAvatar';
|
||||||
|
import { Room } from 'matrix-js-sdk/src/models/room';
|
||||||
|
import { TimelineRenderingType } from '../contexts/RoomContext';
|
||||||
|
|
||||||
const COMMUNITY_REGEX = /\B\+\S*/g;
|
const COMMUNITY_REGEX = /\B\+\S*/g;
|
||||||
|
|
||||||
|
@ -43,8 +45,8 @@ function score(query, space) {
|
||||||
export default class CommunityProvider extends AutocompleteProvider {
|
export default class CommunityProvider extends AutocompleteProvider {
|
||||||
matcher: QueryMatcher<Group>;
|
matcher: QueryMatcher<Group>;
|
||||||
|
|
||||||
constructor() {
|
constructor(room: Room, renderingType?: TimelineRenderingType) {
|
||||||
super(COMMUNITY_REGEX);
|
super({ commandRegex: COMMUNITY_REGEX, renderingType });
|
||||||
this.matcher = new QueryMatcher([], {
|
this.matcher = new QueryMatcher([], {
|
||||||
keys: ['groupId', 'name', 'shortDescription'],
|
keys: ['groupId', 'name', 'shortDescription'],
|
||||||
});
|
});
|
||||||
|
|
|
@ -28,6 +28,8 @@ import SettingsStore from "../settings/SettingsStore";
|
||||||
import { EMOJI, IEmoji } from '../emoji';
|
import { EMOJI, IEmoji } from '../emoji';
|
||||||
|
|
||||||
import EMOTICON_REGEX from 'emojibase-regex/emoticon';
|
import EMOTICON_REGEX from 'emojibase-regex/emoticon';
|
||||||
|
import { Room } from 'matrix-js-sdk/src/models/room';
|
||||||
|
import { TimelineRenderingType } from '../contexts/RoomContext';
|
||||||
|
|
||||||
const LIMIT = 20;
|
const LIMIT = 20;
|
||||||
|
|
||||||
|
@ -64,8 +66,8 @@ export default class EmojiProvider extends AutocompleteProvider {
|
||||||
matcher: QueryMatcher<ISortedEmoji>;
|
matcher: QueryMatcher<ISortedEmoji>;
|
||||||
nameMatcher: QueryMatcher<ISortedEmoji>;
|
nameMatcher: QueryMatcher<ISortedEmoji>;
|
||||||
|
|
||||||
constructor() {
|
constructor(room: Room, renderingType?: TimelineRenderingType) {
|
||||||
super(EMOJI_REGEX);
|
super({ commandRegex: EMOJI_REGEX, renderingType });
|
||||||
this.matcher = new QueryMatcher<ISortedEmoji>(SORTED_EMOJI, {
|
this.matcher = new QueryMatcher<ISortedEmoji>(SORTED_EMOJI, {
|
||||||
keys: [],
|
keys: [],
|
||||||
funcs: [o => o.emoji.shortcodes.map(s => `:${s}:`)],
|
funcs: [o => o.emoji.shortcodes.map(s => `:${s}:`)],
|
||||||
|
|
|
@ -23,15 +23,13 @@ import { MatrixClientPeg } from '../MatrixClientPeg';
|
||||||
import { PillCompletion } from './Components';
|
import { PillCompletion } from './Components';
|
||||||
import { ICompletion, ISelectionRange } from "./Autocompleter";
|
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 AT_ROOM_REGEX = /@\S*/g;
|
const AT_ROOM_REGEX = /@\S*/g;
|
||||||
|
|
||||||
export default class NotifProvider extends AutocompleteProvider {
|
export default class NotifProvider extends AutocompleteProvider {
|
||||||
room: Room;
|
constructor(public room: Room, renderingType?: TimelineRenderingType) {
|
||||||
|
super({ commandRegex: AT_ROOM_REGEX, renderingType });
|
||||||
constructor(room) {
|
|
||||||
super(AT_ROOM_REGEX);
|
|
||||||
this.room = room;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCompletions(
|
async getCompletions(
|
||||||
|
|
|
@ -18,6 +18,7 @@ limitations under the License.
|
||||||
|
|
||||||
import { at, uniq } from 'lodash';
|
import { at, uniq } from 'lodash';
|
||||||
import { removeHiddenChars } from "matrix-js-sdk/src/utils";
|
import { removeHiddenChars } from "matrix-js-sdk/src/utils";
|
||||||
|
import { TimelineRenderingType } from '../contexts/RoomContext';
|
||||||
|
|
||||||
interface IOptions<T extends {}> {
|
interface IOptions<T extends {}> {
|
||||||
keys: Array<string | keyof T>;
|
keys: Array<string | keyof T>;
|
||||||
|
@ -25,6 +26,7 @@ interface IOptions<T extends {}> {
|
||||||
shouldMatchWordsOnly?: boolean;
|
shouldMatchWordsOnly?: boolean;
|
||||||
// whether to apply unhomoglyph and strip diacritics to fuzz up the search. Defaults to true
|
// whether to apply unhomoglyph and strip diacritics to fuzz up the search. Defaults to true
|
||||||
fuzzy?: boolean;
|
fuzzy?: boolean;
|
||||||
|
context?: TimelineRenderingType;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -29,6 +29,7 @@ import { makeRoomPermalink } from "../utils/permalinks/Permalinks";
|
||||||
import { ICompletion, ISelectionRange } from "./Autocompleter";
|
import { ICompletion, ISelectionRange } from "./Autocompleter";
|
||||||
import RoomAvatar from '../components/views/avatars/RoomAvatar';
|
import RoomAvatar from '../components/views/avatars/RoomAvatar';
|
||||||
import SpaceStore from "../stores/spaces/SpaceStore";
|
import SpaceStore from "../stores/spaces/SpaceStore";
|
||||||
|
import { TimelineRenderingType } from "../contexts/RoomContext";
|
||||||
|
|
||||||
const ROOM_REGEX = /\B#\S*/g;
|
const ROOM_REGEX = /\B#\S*/g;
|
||||||
|
|
||||||
|
@ -48,8 +49,8 @@ function matcherObject(room: Room, displayedAlias: string, matchName = "") {
|
||||||
export default class RoomProvider extends AutocompleteProvider {
|
export default class RoomProvider extends AutocompleteProvider {
|
||||||
protected matcher: QueryMatcher<Room>;
|
protected matcher: QueryMatcher<Room>;
|
||||||
|
|
||||||
constructor() {
|
constructor(room: Room, renderingType?: TimelineRenderingType) {
|
||||||
super(ROOM_REGEX);
|
super({ commandRegex: ROOM_REGEX, renderingType });
|
||||||
this.matcher = new QueryMatcher([], {
|
this.matcher = new QueryMatcher([], {
|
||||||
keys: ['displayedAlias', 'matchName'],
|
keys: ['displayedAlias', 'matchName'],
|
||||||
});
|
});
|
||||||
|
|
|
@ -33,6 +33,7 @@ import { EventTimeline } from "matrix-js-sdk/src/models/event-timeline";
|
||||||
import { makeUserPermalink } from "../utils/permalinks/Permalinks";
|
import { makeUserPermalink } from "../utils/permalinks/Permalinks";
|
||||||
import { ICompletion, ISelectionRange } from "./Autocompleter";
|
import { ICompletion, ISelectionRange } from "./Autocompleter";
|
||||||
import MemberAvatar from '../components/views/avatars/MemberAvatar';
|
import MemberAvatar from '../components/views/avatars/MemberAvatar';
|
||||||
|
import { TimelineRenderingType } from '../contexts/RoomContext';
|
||||||
|
|
||||||
const USER_REGEX = /\B@\S*/g;
|
const USER_REGEX = /\B@\S*/g;
|
||||||
|
|
||||||
|
@ -50,8 +51,12 @@ export default class UserProvider extends AutocompleteProvider {
|
||||||
users: RoomMember[];
|
users: RoomMember[];
|
||||||
room: Room;
|
room: Room;
|
||||||
|
|
||||||
constructor(room: Room) {
|
constructor(room: Room, renderingType?: TimelineRenderingType) {
|
||||||
super(USER_REGEX, FORCED_USER_REGEX);
|
super({
|
||||||
|
commandRegex: USER_REGEX,
|
||||||
|
forcedCommandRegex: FORCED_USER_REGEX,
|
||||||
|
renderingType,
|
||||||
|
});
|
||||||
this.room = room;
|
this.room = room;
|
||||||
this.matcher = new QueryMatcher([], {
|
this.matcher = new QueryMatcher([], {
|
||||||
keys: ['name'],
|
keys: ['name'],
|
||||||
|
|
|
@ -957,7 +957,11 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||||
|
|
||||||
CHAT_EFFECTS.forEach(effect => {
|
CHAT_EFFECTS.forEach(effect => {
|
||||||
if (containsEmoji(ev.getContent(), effect.emojis) || ev.getContent().msgtype === effect.msgType) {
|
if (containsEmoji(ev.getContent(), effect.emojis) || ev.getContent().msgtype === effect.msgType) {
|
||||||
dis.dispatch({ action: `effects.${effect.command}` });
|
// For initial threads launch, chat effects are disabled
|
||||||
|
// see #19731
|
||||||
|
if (!SettingsStore.getValue("feature_thread") || !ev.isThreadRelation) {
|
||||||
|
dis.dispatch({ action: `effects.${effect.command}` });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -24,6 +24,7 @@ import { Room } from 'matrix-js-sdk/src/models/room';
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import Autocompleter from '../../../autocomplete/Autocompleter';
|
import Autocompleter from '../../../autocomplete/Autocompleter';
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
import RoomContext from '../../../contexts/RoomContext';
|
||||||
|
|
||||||
const MAX_PROVIDER_MATCHES = 20;
|
const MAX_PROVIDER_MATCHES = 20;
|
||||||
|
|
||||||
|
@ -57,11 +58,11 @@ export default class Autocomplete extends React.PureComponent<IProps, IState> {
|
||||||
debounceCompletionsRequest: number;
|
debounceCompletionsRequest: number;
|
||||||
private containerRef = createRef<HTMLDivElement>();
|
private containerRef = createRef<HTMLDivElement>();
|
||||||
|
|
||||||
|
public static contextType = RoomContext;
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.autocompleter = new Autocompleter(props.room);
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
// list of completionResults, each containing completions
|
// list of completionResults, each containing completions
|
||||||
completions: [],
|
completions: [],
|
||||||
|
@ -82,6 +83,7 @@ export default class Autocomplete extends React.PureComponent<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
this.autocompleter = new Autocompleter(this.props.room, this.context.timelineRenderingType);
|
||||||
this.applyNewProps();
|
this.applyNewProps();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -492,7 +492,12 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
|
||||||
dis.dispatch({ action: "message_sent" });
|
dis.dispatch({ action: "message_sent" });
|
||||||
CHAT_EFFECTS.forEach((effect) => {
|
CHAT_EFFECTS.forEach((effect) => {
|
||||||
if (containsEmoji(content, effect.emojis)) {
|
if (containsEmoji(content, effect.emojis)) {
|
||||||
dis.dispatch({ action: `effects.${effect.command}` });
|
// For initial threads launch, chat effects are disabled
|
||||||
|
// see #19731
|
||||||
|
const isNotThread = this.props.relation?.rel_type !== RelationType.Thread;
|
||||||
|
if (!SettingsStore.getValue("feature_thread") || !isNotThread) {
|
||||||
|
dis.dispatch({ action: `effects.${effect.command}` });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (SettingsStore.getValue("Performance.addSendMessageTimingMetadata")) {
|
if (SettingsStore.getValue("Performance.addSendMessageTimingMetadata")) {
|
||||||
|
|
|
@ -14,11 +14,14 @@
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { IContent } from "matrix-js-sdk/src/models/event";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks a message if it contains one of the provided emojis
|
* Checks a message if it contains one of the provided emojis
|
||||||
* @param {Object} content The message
|
* @param {Object} content The message
|
||||||
* @param {Array<string>} emojis The list of emojis to check for
|
* @param {Array<string>} emojis The list of emojis to check for
|
||||||
*/
|
*/
|
||||||
export const containsEmoji = (content: { msgtype: string, body: string }, emojis: Array<string>): boolean => {
|
export const containsEmoji = (content: IContent, emojis: Array<string>): boolean => {
|
||||||
return emojis.some((emoji) => content.body && content.body.includes(emoji));
|
return emojis.some((emoji) => content.body && content.body.includes(emoji));
|
||||||
};
|
};
|
||||||
|
|
|
@ -38,15 +38,16 @@ import WidgetCapabilitiesPromptDialog from "../../components/views/dialogs/Widge
|
||||||
import { WidgetPermissionCustomisations } from "../../customisations/WidgetPermissions";
|
import { WidgetPermissionCustomisations } from "../../customisations/WidgetPermissions";
|
||||||
import { OIDCState, WidgetPermissionStore } from "./WidgetPermissionStore";
|
import { OIDCState, WidgetPermissionStore } from "./WidgetPermissionStore";
|
||||||
import { WidgetType } from "../../widgets/WidgetType";
|
import { WidgetType } from "../../widgets/WidgetType";
|
||||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
import { EventType, RelationType } from "matrix-js-sdk/src/@types/event";
|
||||||
import { CHAT_EFFECTS } from "../../effects";
|
import { CHAT_EFFECTS } from "../../effects";
|
||||||
import { containsEmoji } from "../../effects/utils";
|
import { containsEmoji } from "../../effects/utils";
|
||||||
import dis from "../../dispatcher/dispatcher";
|
import dis from "../../dispatcher/dispatcher";
|
||||||
import { tryTransformPermalinkToLocalHref } from "../../utils/permalinks/Permalinks";
|
import { tryTransformPermalinkToLocalHref } from "../../utils/permalinks/Permalinks";
|
||||||
import { IEvent, MatrixEvent } from "matrix-js-sdk/src/models/event";
|
import { IContent, IEvent, MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
|
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
import SettingsStore from "../../settings/SettingsStore";
|
||||||
|
|
||||||
// TODO: Purge this from the universe
|
// TODO: Purge this from the universe
|
||||||
|
|
||||||
|
@ -141,7 +142,7 @@ export class StopGapWidgetDriver extends WidgetDriver {
|
||||||
|
|
||||||
public async sendEvent(
|
public async sendEvent(
|
||||||
eventType: string,
|
eventType: string,
|
||||||
content: any,
|
content: IContent,
|
||||||
stateKey: string = null,
|
stateKey: string = null,
|
||||||
targetRoomId: string = null,
|
targetRoomId: string = null,
|
||||||
): Promise<ISendEventDetails> {
|
): Promise<ISendEventDetails> {
|
||||||
|
@ -164,7 +165,12 @@ export class StopGapWidgetDriver extends WidgetDriver {
|
||||||
if (eventType === EventType.RoomMessage) {
|
if (eventType === EventType.RoomMessage) {
|
||||||
CHAT_EFFECTS.forEach((effect) => {
|
CHAT_EFFECTS.forEach((effect) => {
|
||||||
if (containsEmoji(content, effect.emojis)) {
|
if (containsEmoji(content, effect.emojis)) {
|
||||||
dis.dispatch({ action: `effects.${effect.command}` });
|
// For initial threads launch, chat effects are disabled
|
||||||
|
// see #19731
|
||||||
|
const isNotThread = content["m.relates_to"].rel_type !== RelationType.Thread;
|
||||||
|
if (!SettingsStore.getValue("feature_thread") || !isNotThread) {
|
||||||
|
dis.dispatch({ action: `effects.${effect.command}` });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue