diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index f77a0dd41e..55b893b784 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -21,7 +21,6 @@ import { EventStatus, MatrixEvent } from 'matrix-js-sdk/src/models/event'; import { EventType, RelationType } from "matrix-js-sdk/src/@types/event"; import { Relations } from 'matrix-js-sdk/src/models/relations'; import { RoomMemberEvent } from "matrix-js-sdk/src/models/room-member"; -import { M_LOCATION } from 'matrix-js-sdk/src/@types/location'; import { M_POLL_START } from "matrix-events-sdk"; import { MatrixClientPeg } from '../../../MatrixClientPeg'; @@ -31,7 +30,7 @@ import Modal from '../../../Modal'; import Resend from '../../../Resend'; import SettingsStore from '../../../settings/SettingsStore'; import { isUrlPermitted } from '../../../HtmlUtils'; -import { canEditContent, editEvent, isContentActionable } from '../../../utils/EventUtils'; +import { canEditContent, canForward, editEvent, isContentActionable, isLocationEvent } from '../../../utils/EventUtils'; import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOptionList } from './IconizedContextMenu'; import { ReadPinsEventId } from "../right_panel/types"; import { Action } from "../../../dispatcher/actions"; @@ -49,20 +48,11 @@ import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInse import EndPollDialog from '../dialogs/EndPollDialog'; import { isPollEnded } from '../messages/MPollBody'; import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; -import { GetRelationsForEvent } from "../rooms/EventTile"; +import { GetRelationsForEvent, IEventTileOps } from "../rooms/EventTile"; import { OpenForwardDialogPayload } from "../../../dispatcher/payloads/OpenForwardDialogPayload"; import { OpenReportEventDialogPayload } from "../../../dispatcher/payloads/OpenReportEventDialogPayload"; import { createMapSiteLink } from '../../../utils/location'; -export interface IEventTileOps { - isWidgetHidden(): boolean; - unhideWidget(): void; -} - -export interface IOperableEventTile { - getEventTileOps(): IEventTileOps; -} - interface IProps extends IPosition { chevronFace: ChevronFace; /* the MatrixEvent associated with the context menu */ @@ -343,28 +333,7 @@ export default class MessageContextMenu extends React.Component ); const isThreadRootEvent = isThread && mxEvent?.getThread()?.rootEvent === mxEvent; - let openInMapSiteButton: JSX.Element; - let endPollButton: JSX.Element; let resendReactionsButton: JSX.Element; - let redactButton: JSX.Element; - let forwardButton: JSX.Element; - let pinButton: JSX.Element; - let unhidePreviewButton: JSX.Element; - let externalURLButton: JSX.Element; - let quoteButton: JSX.Element; - let redactItemList: JSX.Element; - let reportEventButton: JSX.Element; - let copyButton: JSX.Element; - let editButton: JSX.Element; - let replyButton: JSX.Element; - let reactButton: JSX.Element; - let reactionPicker: JSX.Element; - let quickItemsList: JSX.Element; - let nativeItemsList: JSX.Element; - let permalinkButton: JSX.Element; - let collapseReplyChainButton: JSX.Element; - let viewInRoomButton: JSX.Element; - if (!mxEvent.isRedacted() && unsentReactionsCount !== 0) { resendReactionsButton = ( ); } + let redactButton: JSX.Element; if (isSent && this.state.canRedact) { redactButton = ( ); } + let openInMapSiteButton: JSX.Element; if (this.canOpenInMapSite(mxEvent)) { const mapSiteLink = createMapSiteLink(mxEvent); openInMapSiteButton = ( @@ -404,6 +375,7 @@ export default class MessageContextMenu extends React.Component ); } + let forwardButton: JSX.Element; if (contentActionable && canForward(mxEvent)) { forwardButton = ( ); } + let pinButton: JSX.Element; if (contentActionable && this.state.canPin) { pinButton = ( ); } + let unhidePreviewButton: JSX.Element; if (eventTileOps?.isWidgetHidden()) { unhidePreviewButton = ( ); } + let permalinkButton: JSX.Element; if (permalink) { permalinkButton = ( ); } + let endPollButton: JSX.Element; if (this.canEndPoll(mxEvent)) { endPollButton = ( ); } + let quoteButton: JSX.Element; if (eventTileOps) { // this event is rendered using TextualBody quoteButton = ( } // Bridges can provide a 'external_url' to link back to the source. + let externalURLButton: JSX.Element; if ( typeof (mxEvent.getContent().external_url) === "string" && isUrlPermitted(mxEvent.getContent().external_url) @@ -511,6 +489,7 @@ export default class MessageContextMenu extends React.Component ); } + let collapseReplyChainButton: JSX.Element; if (collapseReplyChain) { collapseReplyChainButton = ( ); } + let reportEventButton: JSX.Element; if (mxEvent.getSender() !== me) { reportEventButton = ( ); } + let copyButton: JSX.Element; if (rightClick && getSelectedText()) { copyButton = ( ); } + let editButton: JSX.Element; if (rightClick && canEditContent(mxEvent)) { editButton = ( ); } + let replyButton: JSX.Element; if (rightClick && contentActionable && canSendMessages) { replyButton = ( ); } + let reactButton; if (rightClick && contentActionable && canReact) { reactButton = ( ); } + let viewInRoomButton: JSX.Element; if (isThreadRootEvent) { viewInRoomButton = ( ); } + let nativeItemsList: JSX.Element; if (copyButton) { nativeItemsList = ( @@ -591,6 +577,7 @@ export default class MessageContextMenu extends React.Component ); } + let quickItemsList: JSX.Element; if (editButton || replyButton || reactButton) { quickItemsList = ( @@ -619,6 +606,7 @@ export default class MessageContextMenu extends React.Component ); + let redactItemList: JSX.Element; if (redactButton) { redactItemList = ( @@ -627,6 +615,7 @@ export default class MessageContextMenu extends React.Component ); } + let reactionPicker: JSX.Element; if (this.state.reactionPickerDisplayed) { const buttonRect = (this.reactButtonRef.current as HTMLElement)?.getBoundingClientRect(); reactionPicker = ( @@ -662,20 +651,3 @@ export default class MessageContextMenu extends React.Component } } -function canForward(event: MatrixEvent): boolean { - return !( - isLocationEvent(event) || - M_POLL_START.matches(event.getType()) - ); -} - -function isLocationEvent(event: MatrixEvent): boolean { - const eventType = event.getType(); - return ( - M_LOCATION.matches(eventType) || - ( - eventType === EventType.RoomMessage && - M_LOCATION.matches(event.getContent().msgtype) - ) - ); -} diff --git a/src/components/views/messages/MessageEvent.tsx b/src/components/views/messages/MessageEvent.tsx index 2bafadf517..d52629b56d 100644 --- a/src/components/views/messages/MessageEvent.tsx +++ b/src/components/views/messages/MessageEvent.tsx @@ -26,7 +26,6 @@ import { Mjolnir } from "../../../mjolnir/Mjolnir"; import RedactedBody from "./RedactedBody"; import UnknownBody from "./UnknownBody"; import { IMediaBody } from "./IMediaBody"; -import { IOperableEventTile } from "../context_menus/MessageContextMenu"; import { MediaEventHelper } from "../../../utils/MediaEventHelper"; import { ReactAnyComponent } from "../../../@types/common"; import { IBodyProps } from "./IBodyProps"; @@ -41,6 +40,7 @@ import MPollBody from "./MPollBody"; import MLocationBody from "./MLocationBody"; import MjolnirBody from "./MjolnirBody"; import MBeaconBody from "./MBeaconBody"; +import { IEventTileOps } from "../rooms/EventTile"; // onMessageAllowed is handled internally interface IProps extends Omit { @@ -54,6 +54,10 @@ interface IProps extends Omit implements IMediaBody, IOperableEventTile { private body: React.RefObject = createRef(); private mediaHelper: MediaEventHelper; diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 9d5dc2cefe..4c68ee532e 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -38,7 +38,7 @@ import MatrixClientContext from "../../../contexts/MatrixClientContext"; import { E2EState } from "./E2EIcon"; import { toRem } from "../../../utils/units"; import RoomAvatar from "../avatars/RoomAvatar"; -import MessageContextMenu, { IEventTileOps } from "../context_menus/MessageContextMenu"; +import MessageContextMenu from "../context_menus/MessageContextMenu"; import { aboveRightOf } from '../../structures/ContextMenu'; import { objectHasDiff } from "../../../utils/objects"; import Tooltip from "../elements/Tooltip"; @@ -99,6 +99,11 @@ export interface IReadReceiptProps { ts: number; } +export interface IEventTileOps { + isWidgetHidden(): boolean; + unhideWidget(): void; +} + export interface IEventTileType extends React.Component { getEventTileOps?(): IEventTileOps; } diff --git a/src/utils/EventUtils.ts b/src/utils/EventUtils.ts index 3aef3eb369..51ab6cbed0 100644 --- a/src/utils/EventUtils.ts +++ b/src/utils/EventUtils.ts @@ -19,6 +19,7 @@ import { EventType, EVENT_VISIBILITY_CHANGE_TYPE, MsgType, RelationType } from " import { MatrixClient } from 'matrix-js-sdk/src/client'; import { logger } from 'matrix-js-sdk/src/logger'; import { M_POLL_START } from "matrix-events-sdk"; +import { M_LOCATION } from "matrix-js-sdk/src/@types/location"; import { MatrixClientPeg } from '../MatrixClientPeg'; import shouldHideEvent from "../shouldHideEvent"; @@ -262,3 +263,21 @@ export function editEvent( export function canCancel(status: EventStatus): boolean { return status === EventStatus.QUEUED || status === EventStatus.NOT_SENT || status === EventStatus.ENCRYPTING; } + +export const isLocationEvent = (event: MatrixEvent): boolean => { + const eventType = event.getType(); + return ( + M_LOCATION.matches(eventType) || + ( + eventType === EventType.RoomMessage && + M_LOCATION.matches(event.getContent().msgtype) + ) + ); +}; + +export function canForward(event: MatrixEvent): boolean { + return !( + isLocationEvent(event) || + M_POLL_START.matches(event.getType()) + ); +}