diff --git a/res/css/views/elements/_AccessibleButton.scss b/res/css/views/elements/_AccessibleButton.scss index 021e8ffc8c1..a6fb0a7e22b 100644 --- a/res/css/views/elements/_AccessibleButton.scss +++ b/res/css/views/elements/_AccessibleButton.scss @@ -18,7 +18,7 @@ limitations under the License. cursor: pointer; &.mx_AccessibleButton_disabled { - cursor: default; + cursor: not-allowed; &.mx_AccessibleButton_kind_primary, &.mx_AccessibleButton_kind_primary_outline, diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index ed5f60d3572..b0f3341e7af 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -168,6 +168,7 @@ export interface IRoomState { searchInProgress?: boolean; callState?: CallState; canPeek: boolean; + canSelfRedact: boolean; showApps: boolean; isPeeking: boolean; showRightPanel: boolean; @@ -251,6 +252,7 @@ export class RoomView extends React.Component { searchResults: null, callState: null, canPeek: false, + canSelfRedact: false, showApps: false, isPeeking: false, showRightPanel: false, @@ -1165,10 +1167,14 @@ export class RoomView extends React.Component { private updatePermissions(room: Room) { if (room) { const me = this.context.getUserId(); - const canReact = room.getMyMembership() === "join" && room.currentState.maySendEvent("m.reaction", me); + const canReact = ( + room.getMyMembership() === "join" && + room.currentState.maySendEvent(EventType.Reaction, me) + ); const canSendMessages = room.maySendMessage(); + const canSelfRedact = room.currentState.maySendEvent(EventType.RoomRedaction, me); - this.setState({ canReact, canSendMessages }); + this.setState({ canReact, canSendMessages, canSelfRedact }); } } diff --git a/src/components/views/emojipicker/Category.tsx b/src/components/views/emojipicker/Category.tsx index d4ea4e89ffb..45ed7144e0d 100644 --- a/src/components/views/emojipicker/Category.tsx +++ b/src/components/views/emojipicker/Category.tsx @@ -45,6 +45,7 @@ interface IProps { onClick(emoji: IEmoji): void; onMouseEnter(emoji: IEmoji): void; onMouseLeave(emoji: IEmoji): void; + isEmojiDisabled?: (unicode: string) => boolean; } class Category extends React.PureComponent { @@ -60,6 +61,7 @@ class Category extends React.PureComponent { onClick={onClick} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} + disabled={this.props.isEmojiDisabled?.(emoji.unicode)} /> )) }); diff --git a/src/components/views/emojipicker/Emoji.tsx b/src/components/views/emojipicker/Emoji.tsx index 91eada12b36..e36bd9ff5c7 100644 --- a/src/components/views/emojipicker/Emoji.tsx +++ b/src/components/views/emojipicker/Emoji.tsx @@ -26,6 +26,7 @@ interface IProps { onClick(emoji: IEmoji): void; onMouseEnter(emoji: IEmoji): void; onMouseLeave(emoji: IEmoji): void; + disabled?: boolean; } class Emoji extends React.PureComponent { @@ -40,6 +41,7 @@ class Emoji extends React.PureComponent { onMouseLeave={() => onMouseLeave(emoji)} className="mx_EmojiPicker_item_wrapper" label={emoji.unicode} + disabled={this.props.disabled} >
{ emoji.unicode } diff --git a/src/components/views/emojipicker/EmojiPicker.tsx b/src/components/views/emojipicker/EmojiPicker.tsx index 461ef4f079a..e32f20b78b6 100644 --- a/src/components/views/emojipicker/EmojiPicker.tsx +++ b/src/components/views/emojipicker/EmojiPicker.tsx @@ -37,6 +37,7 @@ interface IProps { selectedEmojis?: Set; showQuickReactions?: boolean; onChoose(unicode: string): boolean; + isEmojiDisabled?: (unicode: string) => boolean; } interface IState { @@ -261,6 +262,7 @@ class EmojiPicker extends React.Component { onClick={this.onClickEmoji} onMouseEnter={this.onHoverEmoji} onMouseLeave={this.onHoverEmojiEnd} + isEmojiDisabled={this.props.isEmojiDisabled} selectedEmojis={this.props.selectedEmojis} /> ); diff --git a/src/components/views/emojipicker/ReactionPicker.tsx b/src/components/views/emojipicker/ReactionPicker.tsx index 4a0d15b7292..464480ef5ae 100644 --- a/src/components/views/emojipicker/ReactionPicker.tsx +++ b/src/components/views/emojipicker/ReactionPicker.tsx @@ -73,7 +73,7 @@ class ReactionPicker extends React.Component { } } - private getReactions() { + private getReactions(): Record { if (!this.props.reactions) { return {}; } @@ -95,6 +95,8 @@ class ReactionPicker extends React.Component { this.props.onFinished(); const myReactions = this.getReactions(); if (myReactions.hasOwnProperty(reaction)) { + if (this.props.mxEvent.isRedacted() || !this.context.canSelfRedact) return; + MatrixClientPeg.get().redactEvent(this.props.mxEvent.getRoomId(), myReactions[reaction]); dis.dispatch({ action: Action.FocusAComposer, @@ -119,9 +121,17 @@ class ReactionPicker extends React.Component { } }; + private isEmojiDisabled = (unicode: string): boolean => { + if (!this.getReactions()[unicode]) return false; + if (this.context.canSelfRedact) return false; + + return true; + }; + render() { return { mxEvent={mxEvent} reactionEvents={events} myReactionEvent={myReactionEvent} - disabled={!this.context.canReact} + disabled={ + !this.context.canReact || + (myReactionEvent && !myReactionEvent.isRedacted() && !this.context.canSelfRedact) + } />; }).filter(item => !!item); diff --git a/src/components/views/messages/ReactionsRowButton.tsx b/src/components/views/messages/ReactionsRowButton.tsx index d3fcb2faa62..9188fd2ff73 100644 --- a/src/components/views/messages/ReactionsRowButton.tsx +++ b/src/components/views/messages/ReactionsRowButton.tsx @@ -47,6 +47,7 @@ interface IState { export default class ReactionsRowButton extends React.PureComponent { static contextType = MatrixClientContext; + public context!: React.ContextType; state = { tooltipRendered: false, diff --git a/src/contexts/RoomContext.ts b/src/contexts/RoomContext.ts index 6dd705870a1..6ca5ed60c22 100644 --- a/src/contexts/RoomContext.ts +++ b/src/contexts/RoomContext.ts @@ -43,6 +43,7 @@ const RoomContext = createContext({ showTopUnreadMessagesBar: false, statusBarVisible: false, canReact: false, + canSelfRedact: false, canSendMessages: false, resizing: false, layout: Layout.Group, diff --git a/test/components/views/rooms/MessageComposerButtons-test.tsx b/test/components/views/rooms/MessageComposerButtons-test.tsx index d9f867b67e4..f0e736c032c 100644 --- a/test/components/views/rooms/MessageComposerButtons-test.tsx +++ b/test/components/views/rooms/MessageComposerButtons-test.tsx @@ -209,6 +209,7 @@ function createRoomState(room: Room, narrow: boolean): IRoomState { shouldPeek: true, membersLoaded: false, numUnreadMessages: 0, + canSelfRedact: false, canPeek: false, showApps: false, isPeeking: false, diff --git a/test/components/views/rooms/SendMessageComposer-test.tsx b/test/components/views/rooms/SendMessageComposer-test.tsx index 5ab7a1705e5..014f5af66ea 100644 --- a/test/components/views/rooms/SendMessageComposer-test.tsx +++ b/test/components/views/rooms/SendMessageComposer-test.tsx @@ -48,21 +48,18 @@ const WrapWithProviders: React.FC<{ ; describe('', () => { - const defaultRoomContext = { + const defaultRoomContext: IRoomState = { roomLoading: true, peekLoading: false, shouldPeek: true, membersLoaded: false, numUnreadMessages: 0, - searching: false, - guestsCanJoin: false, canPeek: false, showApps: false, isPeeking: false, showRightPanel: true, joining: false, atEndOfLiveTimeline: true, - atEndOfLiveTimelineInit: false, showTopUnreadMessagesBar: false, statusBarVisible: false, canReact: false, @@ -82,6 +79,9 @@ describe('', () => { matrixClientIsReady: false, timelineRenderingType: TimelineRenderingType.Room, liveTimeline: undefined, + canSelfRedact: false, + resizing: false, + narrow: false, }; describe("createMessageContent", () => { const permalinkCreator = jest.fn() as any;