Chat Effects & Commands in thread context (#7138)

This commit is contained in:
Germain 2021-11-18 12:47:11 +00:00 committed by GitHub
parent e549438e2a
commit 256c468c15
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 108 additions and 31 deletions

View file

@ -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],
}); });
}), }),
]; ];

View file

@ -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() {

View file

@ -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);
}); });
} }

View file

@ -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

View file

@ -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'],
}); });

View file

@ -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}:`)],

View file

@ -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(

View file

@ -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;
} }
/** /**

View file

@ -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'],
}); });

View file

@ -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'],

View file

@ -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}` });
}
} }
}); });
}; };

View file

@ -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();
} }

View file

@ -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")) {

View file

@ -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));
}; };

View file

@ -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}` });
}
} }
}); });
} }