mirror of https://github.com/vector-im/riot-web
Step 6: Refactor event rendering to stop using `getComponent`
We move all of the event tile rendering into a factory manager for a couple reasons: 1. `EventTile` is uncomfortably large for a file 2. A simple map isn't possible anymore (can't convert the existing maps like `eventTileTypes` to `Record<string, typeof React.Component>` because the types are actually incompatible) So, by having a factory manager place we can more easily render components without having to use `getComponent()` all over the place, and without lying to ourselves about how simple the event rendering path is. This change also moves quite a bit of the rendering path into the new `EventTileFactory` file so it can be easily seen by future developers.pull/21833/head
parent
115ae198c8
commit
9350c50f87
|
@ -20,7 +20,7 @@ import { EventType } from "matrix-js-sdk/src/@types/event";
|
|||
|
||||
import { MatrixClientPeg } from "./MatrixClientPeg";
|
||||
import shouldHideEvent from './shouldHideEvent';
|
||||
import { haveTileForEvent } from "./components/views/rooms/EventTile";
|
||||
import { haveRendererForEvent } from "./events/EventTileFactory";
|
||||
|
||||
/**
|
||||
* Returns true if this event arriving in a room should affect the room's
|
||||
|
@ -46,7 +46,7 @@ export function eventTriggersUnreadCount(ev: MatrixEvent): boolean {
|
|||
}
|
||||
|
||||
if (ev.isRedacted()) return false;
|
||||
return haveTileForEvent(ev);
|
||||
return haveRendererForEvent(ev);
|
||||
}
|
||||
|
||||
export function doesRoomHaveUnreadMessages(room: Room): boolean {
|
||||
|
|
|
@ -31,7 +31,7 @@ import SettingsStore from '../../settings/SettingsStore';
|
|||
import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
|
||||
import { Layout } from "../../settings/enums/Layout";
|
||||
import { _t } from "../../languageHandler";
|
||||
import EventTile, { UnwrappedEventTile, haveTileForEvent, IReadReceiptProps } from "../views/rooms/EventTile";
|
||||
import EventTile, { UnwrappedEventTile, IReadReceiptProps } from "../views/rooms/EventTile";
|
||||
import { hasText } from "../../TextForEvent";
|
||||
import IRCTimelineProfileResizer from "../views/elements/IRCTimelineProfileResizer";
|
||||
import DMRoomMap from "../../utils/DMRoomMap";
|
||||
|
@ -52,6 +52,7 @@ import EditorStateTransfer from "../../utils/EditorStateTransfer";
|
|||
import { Action } from '../../dispatcher/actions';
|
||||
import { getEventDisplayInfo } from "../../utils/EventUtils";
|
||||
import { IReadReceiptInfo } from "../views/rooms/ReadReceiptMarker";
|
||||
import { haveRendererForEvent } from "../../events/EventTileFactory";
|
||||
|
||||
const CONTINUATION_MAX_INTERVAL = 5 * 60 * 1000; // 5 minutes
|
||||
const continuedTypes = [EventType.Sticker, EventType.RoomMessage];
|
||||
|
@ -95,7 +96,7 @@ export function shouldFormContinuation(
|
|||
timelineRenderingType !== TimelineRenderingType.Thread) return false;
|
||||
|
||||
// if we don't have tile for previous event then it was shown by showHiddenEvents and has no SenderProfile
|
||||
if (!haveTileForEvent(prevEvent, showHiddenEvents)) return false;
|
||||
if (!haveRendererForEvent(prevEvent, showHiddenEvents)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -488,7 +489,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
|||
return true;
|
||||
}
|
||||
|
||||
if (!haveTileForEvent(mxEv, this.showHiddenEvents)) {
|
||||
if (!haveRendererForEvent(mxEv, this.showHiddenEvents)) {
|
||||
return false; // no tile = no show
|
||||
}
|
||||
|
||||
|
|
|
@ -55,7 +55,6 @@ import SettingsStore from "../../settings/SettingsStore";
|
|||
import { Layout } from "../../settings/enums/Layout";
|
||||
import AccessibleButton from "../views/elements/AccessibleButton";
|
||||
import RightPanelStore from "../../stores/right-panel/RightPanelStore";
|
||||
import { haveTileForEvent } from "../views/rooms/EventTile";
|
||||
import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
|
||||
import MatrixClientContext, { MatrixClientProps, withMatrixClientHOC } from "../../contexts/MatrixClientContext";
|
||||
import { E2EStatus, shieldStatusForRoom } from '../../utils/ShieldUtils';
|
||||
|
@ -108,6 +107,7 @@ import { DoAfterSyncPreparedPayload } from '../../dispatcher/payloads/DoAfterSyn
|
|||
import FileDropTarget from './FileDropTarget';
|
||||
import Measured from '../views/elements/Measured';
|
||||
import { FocusComposerPayload } from '../../dispatcher/payloads/FocusComposerPayload';
|
||||
import { haveRendererForEvent } from "../../events/EventTileFactory";
|
||||
|
||||
const DEBUG = false;
|
||||
let debuglog = function(msg: string) {};
|
||||
|
@ -1451,7 +1451,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (!haveTileForEvent(mxEv, this.state.showHiddenEventsInTimeline)) {
|
||||
if (!haveRendererForEvent(mxEv, this.state.showHiddenEventsInTimeline)) {
|
||||
// XXX: can this ever happen? It will make the result count
|
||||
// not match the displayed count.
|
||||
continue;
|
||||
|
|
|
@ -40,7 +40,6 @@ import dis from "../../dispatcher/dispatcher";
|
|||
import { Action } from '../../dispatcher/actions';
|
||||
import Timer from '../../utils/Timer';
|
||||
import shouldHideEvent from '../../shouldHideEvent';
|
||||
import { haveTileForEvent } from "../views/rooms/EventTile";
|
||||
import { arrayFastClone } from "../../utils/arrays";
|
||||
import MessagePanel from "./MessagePanel";
|
||||
import { IScrollState } from "./ScrollPanel";
|
||||
|
@ -54,6 +53,7 @@ import CallEventGrouper, { buildCallEventGroupers } from "./CallEventGrouper";
|
|||
import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { getKeyBindingsManager } from "../../KeyBindingsManager";
|
||||
import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
|
||||
import { haveRendererForEvent } from "../../events/EventTileFactory";
|
||||
|
||||
const PAGINATE_SIZE = 20;
|
||||
const INITIAL_SIZE = 20;
|
||||
|
@ -1473,7 +1473,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
|
||||
const shouldIgnore = !!ev.status || // local echo
|
||||
(ignoreOwn && ev.getSender() === myUserId); // own message
|
||||
const isWithoutTile = !haveTileForEvent(ev, this.context?.showHiddenEventsInTimeline) ||
|
||||
const isWithoutTile = !haveRendererForEvent(ev, this.context?.showHiddenEventsInTimeline) ||
|
||||
shouldHideEvent(ev, this.context);
|
||||
|
||||
if (isWithoutTile || !node) {
|
||||
|
|
|
@ -41,10 +41,10 @@ import MLocationBody from "./MLocationBody";
|
|||
import MjolnirBody from "./MjolnirBody";
|
||||
|
||||
// onMessageAllowed is handled internally
|
||||
interface IProps extends Omit<IBodyProps, "onMessageAllowed"> {
|
||||
interface IProps extends Omit<IBodyProps, "onMessageAllowed" | "mediaEventHelper"> {
|
||||
/* overrides for the msgtype-specific components, used by ReplyTile to override file rendering */
|
||||
overrideBodyTypes?: Record<string, React.Component>;
|
||||
overrideEventTypes?: Record<string, React.Component>;
|
||||
overrideBodyTypes?: Record<string, typeof React.Component>;
|
||||
overrideEventTypes?: Record<string, typeof React.Component>;
|
||||
|
||||
// helper function to access relations for this event
|
||||
getRelationsForEvent?: (eventId: string, relationType: string, eventType: string) => Relations;
|
||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
|||
|
||||
import React, { createRef, forwardRef, MouseEvent, RefObject } from 'react';
|
||||
import classNames from "classnames";
|
||||
import { EventType, MsgType, RelationType } from "matrix-js-sdk/src/@types/event";
|
||||
import { EventType, MsgType } from "matrix-js-sdk/src/@types/event";
|
||||
import { EventStatus, MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { Relations } from "matrix-js-sdk/src/models/relations";
|
||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||
|
@ -25,25 +25,19 @@ import { Thread, ThreadEvent } from 'matrix-js-sdk/src/models/thread';
|
|||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { NotificationCountType, Room, RoomEvent } from 'matrix-js-sdk/src/models/room';
|
||||
import { CallErrorCode } from "matrix-js-sdk/src/webrtc/call";
|
||||
import { M_POLL_START } from "matrix-events-sdk";
|
||||
import { CryptoEvent } from "matrix-js-sdk/src/crypto";
|
||||
import { UserTrustLevel } from 'matrix-js-sdk/src/crypto/CrossSigning';
|
||||
|
||||
import ReplyChain from "../elements/ReplyChain";
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { hasText } from "../../../TextForEvent";
|
||||
import * as sdk from "../../../index";
|
||||
import dis from '../../../dispatcher/dispatcher';
|
||||
import { Layout } from "../../../settings/enums/Layout";
|
||||
import { formatTime } from "../../../DateUtils";
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import { ALL_RULE_TYPES } from "../../../mjolnir/BanList";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import { E2EState } from "./E2EIcon";
|
||||
import { toRem } from "../../../utils/units";
|
||||
import { WidgetType } from "../../../widgets/WidgetType";
|
||||
import RoomAvatar from "../avatars/RoomAvatar";
|
||||
import { WIDGET_LAYOUT_EVENT_TYPE } from "../../../stores/widgets/WidgetLayoutStore";
|
||||
import { objectHasDiff } from "../../../utils/objects";
|
||||
import Tooltip from "../elements/Tooltip";
|
||||
import EditorStateTransfer from "../../../utils/EditorStateTransfer";
|
||||
|
@ -62,7 +56,6 @@ import MessageActionBar from "../messages/MessageActionBar";
|
|||
import ReactionsRow from '../messages/ReactionsRow';
|
||||
import { getEventDisplayInfo } from '../../../utils/EventUtils';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import MKeyVerificationConclusion from "../messages/MKeyVerificationConclusion";
|
||||
import { showThread } from '../../../dispatcher/dispatch-actions/threads';
|
||||
import { MessagePreviewStore } from '../../../stores/room-list/MessagePreviewStore';
|
||||
import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
|
||||
|
@ -81,123 +74,11 @@ import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
|||
import { shouldDisplayReply } from '../../../utils/Reply';
|
||||
import PosthogTrackers from "../../../PosthogTrackers";
|
||||
import TileErrorBoundary from '../messages/TileErrorBoundary';
|
||||
import ThreadSummary, { ThreadMessagePreview } from './ThreadSummary';
|
||||
import { haveRendererForEvent, isMessageEvent, renderTile } from "../../../events/EventTileFactory";
|
||||
import ThreadSummary, { ThreadMessagePreview } from "./ThreadSummary";
|
||||
|
||||
export type GetRelationsForEvent = (eventId: string, relationType: string, eventType: string) => Relations;
|
||||
|
||||
const eventTileTypes = {
|
||||
[EventType.RoomMessage]: 'messages.MessageEvent',
|
||||
[EventType.Sticker]: 'messages.MessageEvent',
|
||||
[M_POLL_START.name]: 'messages.MessageEvent',
|
||||
[M_POLL_START.altName]: 'messages.MessageEvent',
|
||||
[EventType.KeyVerificationCancel]: 'messages.MKeyVerificationConclusion',
|
||||
[EventType.KeyVerificationDone]: 'messages.MKeyVerificationConclusion',
|
||||
[EventType.CallInvite]: 'messages.CallEvent',
|
||||
};
|
||||
|
||||
const stateEventTileTypes = {
|
||||
[EventType.RoomEncryption]: 'messages.EncryptionEvent',
|
||||
[EventType.RoomCanonicalAlias]: 'messages.TextualEvent',
|
||||
[EventType.RoomCreate]: 'messages.RoomCreate',
|
||||
[EventType.RoomMember]: 'messages.TextualEvent',
|
||||
[EventType.RoomName]: 'messages.TextualEvent',
|
||||
[EventType.RoomAvatar]: 'messages.RoomAvatarEvent',
|
||||
[EventType.RoomThirdPartyInvite]: 'messages.TextualEvent',
|
||||
[EventType.RoomHistoryVisibility]: 'messages.TextualEvent',
|
||||
[EventType.RoomTopic]: 'messages.TextualEvent',
|
||||
[EventType.RoomPowerLevels]: 'messages.TextualEvent',
|
||||
[EventType.RoomPinnedEvents]: 'messages.TextualEvent',
|
||||
[EventType.RoomServerAcl]: 'messages.TextualEvent',
|
||||
// TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111)
|
||||
'im.vector.modular.widgets': 'messages.TextualEvent',
|
||||
[WIDGET_LAYOUT_EVENT_TYPE]: 'messages.TextualEvent',
|
||||
[EventType.RoomTombstone]: 'messages.TextualEvent',
|
||||
[EventType.RoomJoinRules]: 'messages.TextualEvent',
|
||||
[EventType.RoomGuestAccess]: 'messages.TextualEvent',
|
||||
};
|
||||
|
||||
const stateEventSingular = new Set([
|
||||
EventType.RoomEncryption,
|
||||
EventType.RoomCanonicalAlias,
|
||||
EventType.RoomCreate,
|
||||
EventType.RoomName,
|
||||
EventType.RoomAvatar,
|
||||
EventType.RoomHistoryVisibility,
|
||||
EventType.RoomTopic,
|
||||
EventType.RoomPowerLevels,
|
||||
EventType.RoomPinnedEvents,
|
||||
EventType.RoomServerAcl,
|
||||
WIDGET_LAYOUT_EVENT_TYPE,
|
||||
EventType.RoomTombstone,
|
||||
EventType.RoomJoinRules,
|
||||
EventType.RoomGuestAccess,
|
||||
]);
|
||||
|
||||
// Add all the Mjolnir stuff to the renderer
|
||||
for (const evType of ALL_RULE_TYPES) {
|
||||
stateEventTileTypes[evType] = 'messages.TextualEvent';
|
||||
}
|
||||
|
||||
export function getHandlerTile(ev: MatrixEvent): string {
|
||||
const type = ev.getType();
|
||||
|
||||
// don't show verification requests we're not involved in,
|
||||
// not even when showing hidden events
|
||||
if (type === EventType.RoomMessage) {
|
||||
const content = ev.getContent();
|
||||
if (content && content.msgtype === MsgType.KeyVerificationRequest) {
|
||||
const me = MatrixClientPeg.get()?.getUserId();
|
||||
if (ev.getSender() !== me && content.to !== me) {
|
||||
return undefined;
|
||||
} else {
|
||||
return "messages.MKeyVerificationRequest";
|
||||
}
|
||||
}
|
||||
}
|
||||
// these events are sent by both parties during verification, but we only want to render one
|
||||
// tile once the verification concludes, so filter out the one from the other party.
|
||||
if (type === EventType.KeyVerificationDone) {
|
||||
const me = MatrixClientPeg.get()?.getUserId();
|
||||
if (ev.getSender() !== me) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// sometimes MKeyVerificationConclusion declines to render. Jankily decline to render and
|
||||
// fall back to showing hidden events, if we're viewing hidden events
|
||||
// XXX: This is extremely a hack. Possibly these components should have an interface for
|
||||
// declining to render?
|
||||
if (type === EventType.KeyVerificationCancel || type === EventType.KeyVerificationDone) {
|
||||
if (!MKeyVerificationConclusion.shouldRender(ev, ev.verificationRequest)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111)
|
||||
if (type === "im.vector.modular.widgets") {
|
||||
let type = ev.getContent()['type'];
|
||||
if (!type) {
|
||||
// deleted/invalid widget - try the past widget type
|
||||
type = ev.getPrevContent()['type'];
|
||||
}
|
||||
|
||||
if (WidgetType.JITSI.matches(type)) {
|
||||
return "messages.MJitsiWidgetEvent";
|
||||
}
|
||||
}
|
||||
|
||||
if (ev.isState()) {
|
||||
if (stateEventSingular.has(type) && ev.getStateKey() !== "") return undefined;
|
||||
return stateEventTileTypes[type];
|
||||
}
|
||||
|
||||
if (ev.isRedacted()) {
|
||||
return "messages.MessageEvent";
|
||||
}
|
||||
|
||||
return eventTileTypes[type];
|
||||
}
|
||||
|
||||
// Our component structure for EventTiles on the timeline is:
|
||||
//
|
||||
// .-EventTile------------------------------------------------.
|
||||
|
@ -1054,7 +935,7 @@ export class UnwrappedEventTile extends React.Component<IProps, IState> {
|
|||
const msgtype = this.props.mxEvent.getContent().msgtype;
|
||||
const eventType = this.props.mxEvent.getType() as EventType;
|
||||
const {
|
||||
tileHandler,
|
||||
hasRenderer,
|
||||
isBubbleMessage,
|
||||
isInfoMessage,
|
||||
isLeftAlignedBubbleMessage,
|
||||
|
@ -1065,7 +946,7 @@ export class UnwrappedEventTile extends React.Component<IProps, IState> {
|
|||
|
||||
// This shouldn't happen: the caller should check we support this type
|
||||
// before trying to instantiate us
|
||||
if (!tileHandler) {
|
||||
if (!hasRenderer) {
|
||||
const { mxEvent } = this.props;
|
||||
logger.warn(`Event type not supported: type:${eventType} isState:${mxEvent.isState()}`);
|
||||
return <div className="mx_EventTile mx_EventTile_info mx_MNoticeBody">
|
||||
|
@ -1075,7 +956,6 @@ export class UnwrappedEventTile extends React.Component<IProps, IState> {
|
|||
</div>;
|
||||
}
|
||||
|
||||
const EventTileType = sdk.getComponent(tileHandler);
|
||||
const isProbablyMedia = MediaEventHelper.isEligible(this.props.mxEvent);
|
||||
|
||||
const lineClasses = classNames("mx_EventTile_line", {
|
||||
|
@ -1154,7 +1034,7 @@ export class UnwrappedEventTile extends React.Component<IProps, IState> {
|
|||
) {
|
||||
avatarSize = 24;
|
||||
needsSenderProfile = true;
|
||||
} else if (tileHandler === 'messages.RoomCreate' || isBubbleMessage) {
|
||||
} else if (eventType === EventType.RoomCreate || isBubbleMessage) {
|
||||
avatarSize = 0;
|
||||
needsSenderProfile = false;
|
||||
} else if (isInfoMessage) {
|
||||
|
@ -1322,20 +1202,21 @@ export class UnwrappedEventTile extends React.Component<IProps, IState> {
|
|||
msgOption = readAvatars;
|
||||
}
|
||||
|
||||
const replyChain = haveTileForEvent(this.props.mxEvent) && shouldDisplayReply(this.props.mxEvent)
|
||||
? <ReplyChain
|
||||
parentEv={this.props.mxEvent}
|
||||
onHeightChanged={this.props.onHeightChanged}
|
||||
ref={this.replyChain}
|
||||
forExport={this.props.forExport}
|
||||
permalinkCreator={this.props.permalinkCreator}
|
||||
layout={this.props.layout}
|
||||
alwaysShowTimestamps={this.props.alwaysShowTimestamps || this.state.hover}
|
||||
isQuoteExpanded={isQuoteExpanded}
|
||||
setQuoteExpanded={this.setQuoteExpanded}
|
||||
getRelationsForEvent={this.props.getRelationsForEvent}
|
||||
/>
|
||||
: null;
|
||||
const replyChain =
|
||||
(haveRendererForEvent(this.props.mxEvent) && shouldDisplayReply(this.props.mxEvent))
|
||||
? <ReplyChain
|
||||
parentEv={this.props.mxEvent}
|
||||
onHeightChanged={this.props.onHeightChanged}
|
||||
ref={this.replyChain}
|
||||
forExport={this.props.forExport}
|
||||
permalinkCreator={this.props.permalinkCreator}
|
||||
layout={this.props.layout}
|
||||
alwaysShowTimestamps={this.props.alwaysShowTimestamps || this.state.hover}
|
||||
isQuoteExpanded={isQuoteExpanded}
|
||||
setQuoteExpanded={this.setQuoteExpanded}
|
||||
getRelationsForEvent={this.props.getRelationsForEvent}
|
||||
/>
|
||||
: null;
|
||||
|
||||
const isOwnEvent = this.props.mxEvent?.sender?.userId === MatrixClientPeg.get().getUserId();
|
||||
|
||||
|
@ -1362,16 +1243,19 @@ export class UnwrappedEventTile extends React.Component<IProps, IState> {
|
|||
</a>
|
||||
</div>,
|
||||
<div className={lineClasses} key="mx_EventTile_line">
|
||||
<EventTileType ref={this.tile}
|
||||
mxEvent={this.props.mxEvent}
|
||||
highlights={this.props.highlights}
|
||||
highlightLink={this.props.highlightLink}
|
||||
showUrlPreview={this.props.showUrlPreview}
|
||||
onHeightChanged={this.props.onHeightChanged}
|
||||
editState={this.props.editState}
|
||||
getRelationsForEvent={this.props.getRelationsForEvent}
|
||||
isSeeingThroughMessageHiddenForModeration={isSeeingThroughMessageHiddenForModeration}
|
||||
/>
|
||||
{ renderTile(TimelineRenderingType.Notification, {
|
||||
...this.props,
|
||||
|
||||
// overrides
|
||||
ref: this.tile,
|
||||
isSeeingThroughMessageHiddenForModeration,
|
||||
|
||||
// appease TS
|
||||
highlights: this.props.highlights,
|
||||
highlightLink: this.props.highlightLink,
|
||||
onHeightChanged: this.props.onHeightChanged,
|
||||
permalinkCreator: this.props.permalinkCreator,
|
||||
}) }
|
||||
</div>,
|
||||
]);
|
||||
}
|
||||
|
@ -1403,17 +1287,19 @@ export class UnwrappedEventTile extends React.Component<IProps, IState> {
|
|||
</div>,
|
||||
<div className={lineClasses} key="mx_EventTile_line">
|
||||
{ replyChain }
|
||||
<EventTileType ref={this.tile}
|
||||
mxEvent={this.props.mxEvent}
|
||||
highlights={this.props.highlights}
|
||||
highlightLink={this.props.highlightLink}
|
||||
showUrlPreview={this.props.showUrlPreview}
|
||||
onHeightChanged={this.props.onHeightChanged}
|
||||
editState={this.props.editState}
|
||||
replacingEventId={this.props.replacingEventId}
|
||||
getRelationsForEvent={this.props.getRelationsForEvent}
|
||||
isSeeingThroughMessageHiddenForModeration={isSeeingThroughMessageHiddenForModeration}
|
||||
/>
|
||||
{ renderTile(TimelineRenderingType.Thread, {
|
||||
...this.props,
|
||||
|
||||
// overrides
|
||||
ref: this.tile,
|
||||
isSeeingThroughMessageHiddenForModeration,
|
||||
|
||||
// appease TS
|
||||
highlights: this.props.highlights,
|
||||
highlightLink: this.props.highlightLink,
|
||||
onHeightChanged: this.props.onHeightChanged,
|
||||
permalinkCreator: this.props.permalinkCreator,
|
||||
}) }
|
||||
{ actionBar }
|
||||
{ timestamp }
|
||||
</div>,
|
||||
|
@ -1485,16 +1371,19 @@ export class UnwrappedEventTile extends React.Component<IProps, IState> {
|
|||
"data-scroll-tokens": scrollToken,
|
||||
}, [
|
||||
<div className={lineClasses} key="mx_EventTile_line">
|
||||
<EventTileType ref={this.tile}
|
||||
mxEvent={this.props.mxEvent}
|
||||
highlights={this.props.highlights}
|
||||
highlightLink={this.props.highlightLink}
|
||||
showUrlPreview={this.props.showUrlPreview}
|
||||
onHeightChanged={this.props.onHeightChanged}
|
||||
editState={this.props.editState}
|
||||
getRelationsForEvent={this.props.getRelationsForEvent}
|
||||
isSeeingThroughMessageHiddenForModeration={isSeeingThroughMessageHiddenForModeration}
|
||||
/>
|
||||
{ renderTile(TimelineRenderingType.File, {
|
||||
...this.props,
|
||||
|
||||
// overrides
|
||||
ref: this.tile,
|
||||
isSeeingThroughMessageHiddenForModeration,
|
||||
|
||||
// appease TS
|
||||
highlights: this.props.highlights,
|
||||
highlightLink: this.props.highlightLink,
|
||||
onHeightChanged: this.props.onHeightChanged,
|
||||
permalinkCreator: this.props.permalinkCreator,
|
||||
}) }
|
||||
</div>,
|
||||
<a
|
||||
className="mx_EventTile_senderDetailsLink"
|
||||
|
@ -1534,22 +1423,20 @@ export class UnwrappedEventTile extends React.Component<IProps, IState> {
|
|||
{ groupTimestamp }
|
||||
{ groupPadlock }
|
||||
{ replyChain }
|
||||
<EventTileType
|
||||
ref={this.tile}
|
||||
mxEvent={this.props.mxEvent}
|
||||
forExport={this.props.forExport}
|
||||
replacingEventId={this.props.replacingEventId}
|
||||
editState={this.props.editState}
|
||||
highlights={this.props.highlights}
|
||||
highlightLink={this.props.highlightLink}
|
||||
showUrlPreview={this.props.showUrlPreview}
|
||||
permalinkCreator={this.props.permalinkCreator}
|
||||
onHeightChanged={this.props.onHeightChanged}
|
||||
callEventGrouper={this.props.callEventGrouper}
|
||||
getRelationsForEvent={this.props.getRelationsForEvent}
|
||||
isSeeingThroughMessageHiddenForModeration={isSeeingThroughMessageHiddenForModeration}
|
||||
timestamp={bubbleTimestamp}
|
||||
/>
|
||||
{ renderTile(this.context.timelineRenderingType, {
|
||||
...this.props,
|
||||
|
||||
// overrides
|
||||
ref: this.tile,
|
||||
isSeeingThroughMessageHiddenForModeration,
|
||||
timestamp: bubbleTimestamp,
|
||||
|
||||
// appease TS
|
||||
highlights: this.props.highlights,
|
||||
highlightLink: this.props.highlightLink,
|
||||
onHeightChanged: this.props.onHeightChanged,
|
||||
permalinkCreator: this.props.permalinkCreator,
|
||||
}) }
|
||||
{ keyRequestInfo }
|
||||
{ actionBar }
|
||||
{ this.props.layout === Layout.IRC && <>
|
||||
|
@ -1577,31 +1464,6 @@ const SafeEventTile = forwardRef((props: IProps, ref: RefObject<UnwrappedEventTi
|
|||
});
|
||||
export default SafeEventTile;
|
||||
|
||||
// XXX this'll eventually be dynamic based on the fields once we have extensible event types
|
||||
const messageTypes = [EventType.RoomMessage, EventType.Sticker];
|
||||
function isMessageEvent(ev: MatrixEvent): boolean {
|
||||
return (messageTypes.includes(ev.getType() as EventType)) || M_POLL_START.matches(ev.getType());
|
||||
}
|
||||
|
||||
export function haveTileForEvent(e: MatrixEvent, showHiddenEvents?: boolean): boolean {
|
||||
// Only show "Message deleted" tile for plain message events, encrypted events,
|
||||
// and state events as they'll likely still contain enough keys to be relevant.
|
||||
if (e.isRedacted() && !e.isEncrypted() && !isMessageEvent(e) && !e.isState()) return false;
|
||||
|
||||
// No tile for replacement events since they update the original tile
|
||||
if (e.isRelation(RelationType.Replace)) return false;
|
||||
|
||||
const handler = getHandlerTile(e);
|
||||
if (handler === undefined) return false;
|
||||
if (handler === 'messages.TextualEvent') {
|
||||
return hasText(e, showHiddenEvents);
|
||||
} else if (handler === 'messages.RoomCreate') {
|
||||
return Boolean(e.getContent()['predecessor']);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function E2ePadlockUndecryptable(props) {
|
||||
return (
|
||||
<E2ePadlock title={_t("This message cannot be decrypted")} icon={E2ePadlockIcon.Warning} {...props} />
|
||||
|
|
|
@ -27,11 +27,11 @@ import { Action } from '../../../dispatcher/actions';
|
|||
import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
|
||||
import SenderProfile from "../messages/SenderProfile";
|
||||
import MImageReplyBody from "../messages/MImageReplyBody";
|
||||
import * as sdk from '../../../index';
|
||||
import { getEventDisplayInfo, isVoiceMessage } from '../../../utils/EventUtils';
|
||||
import MFileBody from "../messages/MFileBody";
|
||||
import MVoiceMessageBody from "../messages/MVoiceMessageBody";
|
||||
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { renderReplyTile } from "../../../events/EventTileFactory";
|
||||
|
||||
interface IProps {
|
||||
mxEvent: MatrixEvent;
|
||||
|
@ -109,10 +109,10 @@ export default class ReplyTile extends React.PureComponent<IProps> {
|
|||
const msgType = mxEvent.getContent().msgtype;
|
||||
const evType = mxEvent.getType() as EventType;
|
||||
|
||||
const { tileHandler, isInfoMessage, isSeeingThroughMessageHiddenForModeration } = getEventDisplayInfo(mxEvent);
|
||||
const { hasRenderer, isInfoMessage, isSeeingThroughMessageHiddenForModeration } = getEventDisplayInfo(mxEvent);
|
||||
// This shouldn't happen: the caller should check we support this type
|
||||
// before trying to instantiate us
|
||||
if (!tileHandler) {
|
||||
if (!hasRenderer) {
|
||||
const { mxEvent } = this.props;
|
||||
logger.warn(`Event type not supported: type:${mxEvent.getType()} isState:${mxEvent.isState()}`);
|
||||
return <div className="mx_ReplyTile mx_ReplyTile_info mx_MNoticeBody">
|
||||
|
@ -120,8 +120,6 @@ export default class ReplyTile extends React.PureComponent<IProps> {
|
|||
</div>;
|
||||
}
|
||||
|
||||
const EventTileType = sdk.getComponent(tileHandler);
|
||||
|
||||
const classes = classNames("mx_ReplyTile", {
|
||||
mx_ReplyTile_info: isInfoMessage && !mxEvent.isRedacted(),
|
||||
mx_ReplyTile_audio: msgType === MsgType.Audio,
|
||||
|
@ -135,10 +133,10 @@ export default class ReplyTile extends React.PureComponent<IProps> {
|
|||
|
||||
let sender;
|
||||
const needsSenderProfile = (
|
||||
!isInfoMessage &&
|
||||
msgType !== MsgType.Image &&
|
||||
tileHandler !== EventType.RoomCreate &&
|
||||
evType !== EventType.Sticker
|
||||
!isInfoMessage
|
||||
&& msgType !== MsgType.Image
|
||||
&& evType !== EventType.Sticker
|
||||
&& evType !== EventType.RoomCreate
|
||||
);
|
||||
|
||||
if (needsSenderProfile) {
|
||||
|
@ -147,13 +145,13 @@ export default class ReplyTile extends React.PureComponent<IProps> {
|
|||
/>;
|
||||
}
|
||||
|
||||
const msgtypeOverrides = {
|
||||
const msgtypeOverrides: Record<string, typeof React.Component> = {
|
||||
[MsgType.Image]: MImageReplyBody,
|
||||
// Override audio and video body with file body. We also hide the download/decrypt button using CSS
|
||||
[MsgType.Audio]: isVoiceMessage(mxEvent) ? MVoiceMessageBody : MFileBody,
|
||||
[MsgType.Video]: MFileBody,
|
||||
};
|
||||
const evOverrides = {
|
||||
const evOverrides: Record<string, typeof React.Component> = {
|
||||
// Use MImageReplyBody so that the sticker isn't taking up a lot of space
|
||||
[EventType.Sticker]: MImageReplyBody,
|
||||
};
|
||||
|
@ -162,20 +160,23 @@ export default class ReplyTile extends React.PureComponent<IProps> {
|
|||
<div className={classes}>
|
||||
<a href={permalink} onClick={this.onClick} ref={this.anchorElement}>
|
||||
{ sender }
|
||||
<EventTileType
|
||||
ref="tile"
|
||||
mxEvent={mxEvent}
|
||||
highlights={this.props.highlights}
|
||||
highlightLink={this.props.highlightLink}
|
||||
onHeightChanged={this.props.onHeightChanged}
|
||||
showUrlPreview={false}
|
||||
overrideBodyTypes={msgtypeOverrides}
|
||||
overrideEventTypes={evOverrides}
|
||||
replacingEventId={mxEvent.replacingEventId()}
|
||||
maxImageHeight={96}
|
||||
getRelationsForEvent={this.props.getRelationsForEvent}
|
||||
isSeeingThroughMessageHiddenForModeration={isSeeingThroughMessageHiddenForModeration}
|
||||
/>
|
||||
{ renderReplyTile({
|
||||
...this.props,
|
||||
|
||||
// overrides
|
||||
ref: null,
|
||||
showUrlPreview: false,
|
||||
overrideBodyTypes: msgtypeOverrides,
|
||||
overrideEventTypes: evOverrides,
|
||||
maxImageHeight: 96,
|
||||
isSeeingThroughMessageHiddenForModeration,
|
||||
|
||||
// appease TS
|
||||
highlights: this.props.highlights,
|
||||
highlightLink: this.props.highlightLink,
|
||||
onHeightChanged: this.props.onHeightChanged,
|
||||
permalinkCreator: this.props.permalinkCreator,
|
||||
}) }
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -23,10 +23,11 @@ import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContex
|
|||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
|
||||
import DateSeparator from "../messages/DateSeparator";
|
||||
import EventTile, { haveTileForEvent } from "./EventTile";
|
||||
import EventTile from "./EventTile";
|
||||
import { shouldFormContinuation } from "../../structures/MessagePanel";
|
||||
import { wantsDateSeparator } from "../../../DateUtils";
|
||||
import CallEventGrouper, { buildCallEventGroupers } from "../../structures/CallEventGrouper";
|
||||
import { haveRendererForEvent } from "../../../events/EventTileFactory";
|
||||
|
||||
interface IProps {
|
||||
// a matrix-js-sdk SearchResult containing the details of this result
|
||||
|
@ -77,7 +78,7 @@ export default class SearchResultTile extends React.Component<IProps> {
|
|||
highlights = this.props.searchHighlights;
|
||||
}
|
||||
|
||||
if (haveTileForEvent(mxEv, this.context?.showHiddenEventsInTimeline)) {
|
||||
if (haveRendererForEvent(mxEv, this.context?.showHiddenEventsInTimeline)) {
|
||||
// do we need a date separator since the last event?
|
||||
const prevEv = timeline[j - 1];
|
||||
// is this a continuation of the previous message?
|
||||
|
|
|
@ -0,0 +1,376 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { EventType, MsgType, RelationType } from "matrix-js-sdk/src/@types/event";
|
||||
import { M_POLL_START, Optional } from "matrix-events-sdk";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
|
||||
import EditorStateTransfer from "../utils/EditorStateTransfer";
|
||||
import { RoomPermalinkCreator } from "../utils/permalinks/Permalinks";
|
||||
import CallEventGrouper from "../components/structures/CallEventGrouper";
|
||||
import { GetRelationsForEvent } from "../components/views/rooms/EventTile";
|
||||
import { TimelineRenderingType } from "../contexts/RoomContext";
|
||||
import MessageEvent from "../components/views/messages/MessageEvent";
|
||||
import MKeyVerificationConclusion from "../components/views/messages/MKeyVerificationConclusion";
|
||||
import CallEvent from "../components/views/messages/CallEvent";
|
||||
import TextualEvent from "../components/views/messages/TextualEvent";
|
||||
import EncryptionEvent from "../components/views/messages/EncryptionEvent";
|
||||
import RoomCreate from "../components/views/messages/RoomCreate";
|
||||
import RoomAvatarEvent from "../components/views/messages/RoomAvatarEvent";
|
||||
import { WIDGET_LAYOUT_EVENT_TYPE } from "../stores/widgets/WidgetLayoutStore";
|
||||
import { ALL_RULE_TYPES } from "../mjolnir/BanList";
|
||||
import { MatrixClientPeg } from "../MatrixClientPeg";
|
||||
import MKeyVerificationRequest from "../components/views/messages/MKeyVerificationRequest";
|
||||
import { WidgetType } from "../widgets/WidgetType";
|
||||
import MJitsiWidgetEvent from "../components/views/messages/MJitsiWidgetEvent";
|
||||
import { hasText } from "../TextForEvent";
|
||||
import { getMessageModerationState, MessageModerationState } from "../utils/EventUtils";
|
||||
import HiddenBody from "../components/views/messages/HiddenBody";
|
||||
import SettingsStore from "../settings/SettingsStore";
|
||||
import ViewSourceEvent from "../components/views/messages/ViewSourceEvent";
|
||||
|
||||
// Subset of EventTile's IProps plus some mixins
|
||||
export interface EventTileTypeProps {
|
||||
ref?: React.RefObject<any>; // `any` because it's effectively impossible to convince TS of a reasonable type
|
||||
mxEvent: MatrixEvent;
|
||||
highlights: string[];
|
||||
highlightLink: string;
|
||||
showUrlPreview?: boolean;
|
||||
onHeightChanged: () => void;
|
||||
forExport?: boolean;
|
||||
getRelationsForEvent?: GetRelationsForEvent;
|
||||
editState?: EditorStateTransfer;
|
||||
replacingEventId?: string;
|
||||
permalinkCreator: RoomPermalinkCreator;
|
||||
callEventGrouper?: CallEventGrouper;
|
||||
isSeeingThroughMessageHiddenForModeration?: boolean;
|
||||
timestamp?: JSX.Element;
|
||||
maxImageHeight?: number; // pixels
|
||||
overrideBodyTypes?: Record<string, typeof React.Component>;
|
||||
overrideEventTypes?: Record<string, typeof React.Component>;
|
||||
}
|
||||
|
||||
type FactoryProps = Omit<EventTileTypeProps, "ref">;
|
||||
type Factory<X = FactoryProps> = (ref: Optional<React.RefObject<any>>, props: X) => JSX.Element;
|
||||
type FactoryMap = Record<string, Factory>;
|
||||
|
||||
const MessageEventFactory: Factory = (ref, props) => <MessageEvent ref={ref} {...props} />;
|
||||
const KeyVerificationConclFactory: Factory = (ref, props) => <MKeyVerificationConclusion ref={ref} {...props} />;
|
||||
const CallEventFactory: Factory<FactoryProps & { callEventGrouper: CallEventGrouper }> = (ref, props) => (
|
||||
<CallEvent ref={ref} {...props} />
|
||||
);
|
||||
const TextualEventFactory: Factory = (ref, props) => <TextualEvent ref={ref} {...props} />;
|
||||
const VerificationReqFactory: Factory = (ref, props) => <MKeyVerificationRequest ref={ref} {...props} />;
|
||||
const HiddenEventFactory: Factory = (ref, props) => <HiddenBody ref={ref} {...props} />;
|
||||
|
||||
// These factories are exported for reference comparison against pickFactory()
|
||||
export const JitsiEventFactory: Factory = (ref, props) => <MJitsiWidgetEvent ref={ref} {...props} />;
|
||||
export const JSONEventFactory: Factory = (ref, props) => <ViewSourceEvent ref={ref} {...props} />;
|
||||
|
||||
const EVENT_TILE_TYPES: FactoryMap = {
|
||||
[EventType.RoomMessage]: MessageEventFactory, // note that verification requests are handled in pickFactory()
|
||||
[EventType.Sticker]: MessageEventFactory,
|
||||
[M_POLL_START.name]: MessageEventFactory,
|
||||
[M_POLL_START.altName]: MessageEventFactory,
|
||||
[EventType.KeyVerificationCancel]: KeyVerificationConclFactory,
|
||||
[EventType.KeyVerificationDone]: KeyVerificationConclFactory,
|
||||
[EventType.CallInvite]: CallEventFactory, // note that this requires a special factory type
|
||||
};
|
||||
|
||||
const STATE_EVENT_TILE_TYPES: FactoryMap = {
|
||||
[EventType.RoomEncryption]: (ref, props) => <EncryptionEvent ref={ref} {...props} />,
|
||||
[EventType.RoomCanonicalAlias]: TextualEventFactory,
|
||||
[EventType.RoomCreate]: (ref, props) => <RoomCreate ref={ref} {...props} />,
|
||||
[EventType.RoomMember]: TextualEventFactory,
|
||||
[EventType.RoomName]: TextualEventFactory,
|
||||
[EventType.RoomAvatar]: (ref, props) => <RoomAvatarEvent ref={ref} {...props} />,
|
||||
[EventType.RoomThirdPartyInvite]: TextualEventFactory,
|
||||
[EventType.RoomHistoryVisibility]: TextualEventFactory,
|
||||
[EventType.RoomTopic]: TextualEventFactory,
|
||||
[EventType.RoomPowerLevels]: TextualEventFactory,
|
||||
[EventType.RoomPinnedEvents]: TextualEventFactory,
|
||||
[EventType.RoomServerAcl]: TextualEventFactory,
|
||||
// TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111)
|
||||
'im.vector.modular.widgets': TextualEventFactory, // note that Jitsi widgets are special in pickFactory()
|
||||
[WIDGET_LAYOUT_EVENT_TYPE]: TextualEventFactory,
|
||||
[EventType.RoomTombstone]: TextualEventFactory,
|
||||
[EventType.RoomJoinRules]: TextualEventFactory,
|
||||
[EventType.RoomGuestAccess]: TextualEventFactory,
|
||||
};
|
||||
|
||||
// Add all the Mjolnir stuff to the renderer too
|
||||
for (const evType of ALL_RULE_TYPES) {
|
||||
STATE_EVENT_TILE_TYPES[evType] = TextualEventFactory;
|
||||
}
|
||||
|
||||
// These events should be recorded in the STATE_EVENT_TILE_TYPES
|
||||
const SINGULAR_STATE_EVENTS = new Set([
|
||||
EventType.RoomEncryption,
|
||||
EventType.RoomCanonicalAlias,
|
||||
EventType.RoomCreate,
|
||||
EventType.RoomName,
|
||||
EventType.RoomAvatar,
|
||||
EventType.RoomHistoryVisibility,
|
||||
EventType.RoomTopic,
|
||||
EventType.RoomPowerLevels,
|
||||
EventType.RoomPinnedEvents,
|
||||
EventType.RoomServerAcl,
|
||||
WIDGET_LAYOUT_EVENT_TYPE,
|
||||
EventType.RoomTombstone,
|
||||
EventType.RoomJoinRules,
|
||||
EventType.RoomGuestAccess,
|
||||
]);
|
||||
|
||||
/**
|
||||
* Find an event tile factory for the given conditions.
|
||||
* @param mxEvent The event.
|
||||
* @param cli The matrix client to reference when needed.
|
||||
* @param asHiddenEv When true, treat the event as always hidden.
|
||||
* @returns The factory, or falsy if not possible.
|
||||
*/
|
||||
export function pickFactory(mxEvent: MatrixEvent, cli: MatrixClient, asHiddenEv?: boolean): Optional<Factory> {
|
||||
const evType = mxEvent.getType(); // cache this to reduce call stack execution hits
|
||||
|
||||
// Note: we avoid calling SettingsStore unless absolutely necessary - this code is on the critical path.
|
||||
|
||||
if (asHiddenEv && SettingsStore.getValue("showHiddenEventsInTimeline")) {
|
||||
return JSONEventFactory;
|
||||
}
|
||||
|
||||
const noEventFactoryFactory: (() => Optional<Factory>) = () => SettingsStore.getValue("showHiddenEventsInTimeline")
|
||||
? JSONEventFactory
|
||||
: undefined; // just don't render things that we shouldn't render
|
||||
|
||||
// We run all the event type checks first as they might override the factory entirely.
|
||||
|
||||
const moderationState = getMessageModerationState(mxEvent, cli);
|
||||
if (moderationState === MessageModerationState.HIDDEN_TO_CURRENT_USER) {
|
||||
return HiddenEventFactory;
|
||||
}
|
||||
|
||||
if (evType === EventType.RoomMessage) {
|
||||
// don't show verification requests we're not involved in,
|
||||
// not even when showing hidden events
|
||||
const content = mxEvent.getContent();
|
||||
if (content?.msgtype === MsgType.KeyVerificationRequest) {
|
||||
const me = cli.getUserId();
|
||||
if (mxEvent.getSender() !== me && content['to'] !== me) {
|
||||
return noEventFactoryFactory(); // not for/from us
|
||||
} else {
|
||||
// override the factory
|
||||
return VerificationReqFactory;
|
||||
}
|
||||
}
|
||||
} else if (evType === EventType.KeyVerificationDone) {
|
||||
// these events are sent by both parties during verification, but we only want to render one
|
||||
// tile once the verification concludes, so filter out the one from the other party.
|
||||
const me = cli.getUserId();
|
||||
if (mxEvent.getSender() !== me) {
|
||||
return noEventFactoryFactory();
|
||||
}
|
||||
}
|
||||
|
||||
if (evType === EventType.KeyVerificationCancel || evType === EventType.KeyVerificationDone) {
|
||||
// sometimes MKeyVerificationConclusion declines to render. Jankily decline to render and
|
||||
// fall back to showing hidden events, if we're viewing hidden events
|
||||
// XXX: This is extremely a hack. Possibly these components should have an interface for
|
||||
// declining to render?
|
||||
if (!MKeyVerificationConclusion.shouldRender(mxEvent, mxEvent.verificationRequest)) {
|
||||
return noEventFactoryFactory();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111)
|
||||
if (evType === "im.vector.modular.widgets") {
|
||||
let type = mxEvent.getContent()['type'];
|
||||
if (!type) {
|
||||
// deleted/invalid widget - try the past widget type
|
||||
type = mxEvent.getPrevContent()['type'];
|
||||
}
|
||||
|
||||
if (WidgetType.JITSI.matches(type)) {
|
||||
// override the factory
|
||||
return JitsiEventFactory;
|
||||
}
|
||||
}
|
||||
|
||||
// Try and pick a state event factory, if we can.
|
||||
if (mxEvent.isState()) {
|
||||
if (SINGULAR_STATE_EVENTS.has(evType) && mxEvent.getStateKey() !== '') {
|
||||
return noEventFactoryFactory(); // improper event type to render
|
||||
}
|
||||
return STATE_EVENT_TILE_TYPES[evType] ?? noEventFactoryFactory();
|
||||
}
|
||||
|
||||
// Blanket override for all events. The MessageEvent component handles redacted states for us.
|
||||
if (mxEvent.isRedacted()) {
|
||||
return MessageEventFactory;
|
||||
}
|
||||
|
||||
return EVENT_TILE_TYPES[evType] ?? noEventFactoryFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render an event as a tile
|
||||
* @param renderType The render type. Used to inform properties given to the eventual component.
|
||||
* @param props The properties to provide to the eventual component.
|
||||
* @param cli Optional client instance to use, otherwise the default MatrixClientPeg will be used.
|
||||
* @returns The tile as JSX, or falsy if unable to render.
|
||||
*/
|
||||
export function renderTile(
|
||||
renderType: TimelineRenderingType,
|
||||
props: EventTileTypeProps,
|
||||
cli?: MatrixClient,
|
||||
): Optional<JSX.Element> {
|
||||
cli = cli ?? MatrixClientPeg.get(); // because param defaults don't do the correct thing
|
||||
|
||||
const factory = pickFactory(props.mxEvent, cli);
|
||||
if (!factory) return undefined;
|
||||
|
||||
// Note that we split off the ones we actually care about here just to be sure that we're
|
||||
// not going to accidentally send things we shouldn't from lazy callers. Eg: EventTile's
|
||||
// lazy calls of `renderTile(..., this.props)` will have a lot more than we actually care
|
||||
// about.
|
||||
const {
|
||||
ref,
|
||||
mxEvent,
|
||||
forExport,
|
||||
replacingEventId,
|
||||
editState,
|
||||
highlights,
|
||||
highlightLink,
|
||||
showUrlPreview,
|
||||
permalinkCreator,
|
||||
onHeightChanged,
|
||||
callEventGrouper,
|
||||
getRelationsForEvent,
|
||||
isSeeingThroughMessageHiddenForModeration,
|
||||
timestamp,
|
||||
} = props;
|
||||
|
||||
switch (renderType) {
|
||||
case TimelineRenderingType.File:
|
||||
case TimelineRenderingType.Notification:
|
||||
case TimelineRenderingType.Thread:
|
||||
// We only want a subset of props, so we don't end up causing issues for downstream components.
|
||||
return factory(props.ref, {
|
||||
mxEvent,
|
||||
highlights,
|
||||
highlightLink,
|
||||
showUrlPreview,
|
||||
onHeightChanged,
|
||||
editState,
|
||||
replacingEventId,
|
||||
getRelationsForEvent,
|
||||
isSeeingThroughMessageHiddenForModeration,
|
||||
permalinkCreator,
|
||||
});
|
||||
default:
|
||||
// NEARLY ALL THE OPTIONS!
|
||||
return factory(ref, {
|
||||
mxEvent,
|
||||
forExport,
|
||||
replacingEventId,
|
||||
editState,
|
||||
highlights,
|
||||
highlightLink,
|
||||
showUrlPreview,
|
||||
permalinkCreator,
|
||||
onHeightChanged,
|
||||
callEventGrouper,
|
||||
getRelationsForEvent,
|
||||
isSeeingThroughMessageHiddenForModeration,
|
||||
timestamp,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A version of renderTile() specifically for replies.
|
||||
* @param props The properties to specify on the eventual object.
|
||||
* @param cli Optional client instance to use, otherwise the default MatrixClientPeg will be used.
|
||||
* @returns The tile as JSX, or falsy if unable to render.
|
||||
*/
|
||||
export function renderReplyTile(
|
||||
props: EventTileTypeProps,
|
||||
cli?: MatrixClient,
|
||||
): Optional<JSX.Element> {
|
||||
cli = cli ?? MatrixClientPeg.get(); // because param defaults don't do the correct thing
|
||||
|
||||
const factory = pickFactory(props.mxEvent, cli);
|
||||
if (!factory) return undefined;
|
||||
|
||||
// See renderTile() for why we split off so much
|
||||
const {
|
||||
ref,
|
||||
mxEvent,
|
||||
highlights,
|
||||
highlightLink,
|
||||
onHeightChanged,
|
||||
showUrlPreview,
|
||||
overrideBodyTypes,
|
||||
overrideEventTypes,
|
||||
replacingEventId,
|
||||
maxImageHeight,
|
||||
getRelationsForEvent,
|
||||
isSeeingThroughMessageHiddenForModeration,
|
||||
permalinkCreator,
|
||||
} = props;
|
||||
|
||||
return factory(ref, {
|
||||
mxEvent,
|
||||
highlights,
|
||||
highlightLink,
|
||||
onHeightChanged,
|
||||
showUrlPreview,
|
||||
overrideBodyTypes,
|
||||
overrideEventTypes,
|
||||
replacingEventId,
|
||||
maxImageHeight,
|
||||
getRelationsForEvent,
|
||||
isSeeingThroughMessageHiddenForModeration,
|
||||
permalinkCreator,
|
||||
});
|
||||
}
|
||||
|
||||
// XXX: this'll eventually be dynamic based on the fields once we have extensible event types
|
||||
const messageTypes = [EventType.RoomMessage, EventType.Sticker];
|
||||
export function isMessageEvent(ev: MatrixEvent): boolean {
|
||||
return (messageTypes.includes(ev.getType() as EventType)) || M_POLL_START.matches(ev.getType());
|
||||
}
|
||||
|
||||
export function haveRendererForEvent(mxEvent: MatrixEvent, showHiddenEvents?: boolean): boolean {
|
||||
// Only show "Message deleted" tile for plain message events, encrypted events,
|
||||
// and state events as they'll likely still contain enough keys to be relevant.
|
||||
if (mxEvent.isRedacted() && !mxEvent.isEncrypted() && !isMessageEvent(mxEvent) && !mxEvent.isState()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// No tile for replacement events since they update the original tile
|
||||
if (mxEvent.isRelation(RelationType.Replace)) return false;
|
||||
|
||||
const handler = pickFactory(mxEvent, MatrixClientPeg.get());
|
||||
if (!handler) return false;
|
||||
if (handler === TextualEventFactory) {
|
||||
return hasText(mxEvent, showHiddenEvents);
|
||||
} else if (handler === STATE_EVENT_TILE_TYPES[EventType.RoomCreate]) {
|
||||
return Boolean(mxEvent.getContent()['predecessor']);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -18,8 +18,3 @@ limitations under the License.
|
|||
// Import the js-sdk so the proper `request` object can be set. This does some
|
||||
// magic with the browser injection to make all subsequent imports work fine.
|
||||
import "matrix-js-sdk/src/browser-index";
|
||||
|
||||
export function getComponent(componentName: string): any {
|
||||
// return Skinner.getComponent(componentName);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019 - 2021 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2019 - 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -23,12 +23,13 @@ import { M_POLL_START } from "matrix-events-sdk";
|
|||
|
||||
import { MatrixClientPeg } from '../MatrixClientPeg';
|
||||
import shouldHideEvent from "../shouldHideEvent";
|
||||
import { getHandlerTile, GetRelationsForEvent, haveTileForEvent } from "../components/views/rooms/EventTile";
|
||||
import { GetRelationsForEvent } from "../components/views/rooms/EventTile";
|
||||
import SettingsStore from "../settings/SettingsStore";
|
||||
import defaultDispatcher from "../dispatcher/dispatcher";
|
||||
import { TimelineRenderingType } from "../contexts/RoomContext";
|
||||
import { launchPollEditor } from "../components/views/messages/MPollBody";
|
||||
import { Action } from "../dispatcher/actions";
|
||||
import { haveRendererForEvent, JitsiEventFactory, JSONEventFactory, pickFactory } from "../events/EventTileFactory";
|
||||
|
||||
/**
|
||||
* Returns whether an event should allow actions like reply, reactions, edit, etc.
|
||||
|
@ -134,7 +135,7 @@ export function findEditableEvent({
|
|||
/**
|
||||
* How we should render a message depending on its moderation state.
|
||||
*/
|
||||
enum MessageModerationState {
|
||||
export enum MessageModerationState {
|
||||
/**
|
||||
* The message is visible to all.
|
||||
*/
|
||||
|
@ -158,7 +159,9 @@ enum MessageModerationState {
|
|||
* If MSC3531 is deactivated in settings, all messages are considered visible
|
||||
* to all.
|
||||
*/
|
||||
export function getMessageModerationState(mxEvent: MatrixEvent): MessageModerationState {
|
||||
export function getMessageModerationState(mxEvent: MatrixEvent, client?: MatrixClient): MessageModerationState {
|
||||
client = client ?? MatrixClientPeg.get(); // because param defaults don't do the correct thing
|
||||
|
||||
if (!SettingsStore.getValue("feature_msc3531_hide_messages_pending_moderation")) {
|
||||
return MessageModerationState.VISIBLE_FOR_ALL;
|
||||
}
|
||||
|
@ -171,7 +174,6 @@ export function getMessageModerationState(mxEvent: MatrixEvent): MessageModerati
|
|||
// pending moderation. However, if we're the author or a moderator,
|
||||
// we still need to display it.
|
||||
|
||||
const client = MatrixClientPeg.get();
|
||||
if (mxEvent.sender?.userId === client.getUserId()) {
|
||||
// We're the author, show the message.
|
||||
return MessageModerationState.SEE_THROUGH_FOR_CURRENT_USER;
|
||||
|
@ -196,7 +198,7 @@ export function getMessageModerationState(mxEvent: MatrixEvent): MessageModerati
|
|||
|
||||
export function getEventDisplayInfo(mxEvent: MatrixEvent, hideEvent?: boolean): {
|
||||
isInfoMessage: boolean;
|
||||
tileHandler: string;
|
||||
hasRenderer: boolean;
|
||||
isBubbleMessage: boolean;
|
||||
isLeftAlignedBubbleMessage: boolean;
|
||||
noBubbleEvent: boolean;
|
||||
|
@ -207,15 +209,11 @@ export function getEventDisplayInfo(mxEvent: MatrixEvent, hideEvent?: boolean):
|
|||
const eventType = mxEvent.getType();
|
||||
|
||||
let isSeeingThroughMessageHiddenForModeration = false;
|
||||
let tileHandler;
|
||||
if (SettingsStore.getValue("feature_msc3531_hide_messages_pending_moderation")) {
|
||||
switch (getMessageModerationState(mxEvent)) {
|
||||
case MessageModerationState.VISIBLE_FOR_ALL:
|
||||
// Default behavior, nothing to do.
|
||||
break;
|
||||
case MessageModerationState.HIDDEN_TO_CURRENT_USER:
|
||||
// Hide message.
|
||||
tileHandler = "messages.HiddenBody";
|
||||
// Nothing specific to do here
|
||||
break;
|
||||
case MessageModerationState.SEE_THROUGH_FOR_CURRENT_USER:
|
||||
// Show message with a marker.
|
||||
|
@ -223,9 +221,9 @@ export function getEventDisplayInfo(mxEvent: MatrixEvent, hideEvent?: boolean):
|
|||
break;
|
||||
}
|
||||
}
|
||||
if (!tileHandler) {
|
||||
tileHandler = getHandlerTile(mxEvent);
|
||||
}
|
||||
|
||||
// TODO: Thread a MatrixClient through to here
|
||||
let factory = pickFactory(mxEvent, MatrixClientPeg.get());
|
||||
|
||||
// Info messages are basically information about commands processed on a room
|
||||
let isBubbleMessage = (
|
||||
|
@ -233,7 +231,7 @@ export function getEventDisplayInfo(mxEvent: MatrixEvent, hideEvent?: boolean):
|
|||
(eventType === EventType.RoomMessage && msgtype?.startsWith("m.key.verification")) ||
|
||||
(eventType === EventType.RoomCreate) ||
|
||||
(eventType === EventType.RoomEncryption) ||
|
||||
(tileHandler === "messages.MJitsiWidgetEvent")
|
||||
(factory === JitsiEventFactory)
|
||||
);
|
||||
const isLeftAlignedBubbleMessage = (
|
||||
!isBubbleMessage &&
|
||||
|
@ -263,15 +261,20 @@ export function getEventDisplayInfo(mxEvent: MatrixEvent, hideEvent?: boolean):
|
|||
// source tile when there's no regular tile for an event and also for
|
||||
// replace relations (which otherwise would display as a confusing
|
||||
// duplicate of the thing they are replacing).
|
||||
if ((hideEvent || !haveTileForEvent(mxEvent)) && SettingsStore.getValue("showHiddenEventsInTimeline")) {
|
||||
tileHandler = "messages.ViewSourceEvent";
|
||||
isBubbleMessage = false;
|
||||
// Reuse info message avatar and sender profile styling
|
||||
isInfoMessage = true;
|
||||
if (hideEvent || !haveRendererForEvent(mxEvent)) {
|
||||
// forcefully ask for a factory for a hidden event (hidden event
|
||||
// setting is checked internally)
|
||||
// TODO: Thread a MatrixClient through to here
|
||||
factory = pickFactory(mxEvent, MatrixClientPeg.get(), true);
|
||||
if (factory === JSONEventFactory) {
|
||||
isBubbleMessage = false;
|
||||
// Reuse info message avatar and sender profile styling
|
||||
isInfoMessage = true;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
tileHandler,
|
||||
hasRenderer: !!factory,
|
||||
isInfoMessage,
|
||||
isBubbleMessage,
|
||||
isLeftAlignedBubbleMessage,
|
||||
|
|
|
@ -31,7 +31,7 @@ import { formatFullDateNoDayNoTime, wantsDateSeparator } from "../../DateUtils";
|
|||
import { RoomPermalinkCreator } from "../permalinks/Permalinks";
|
||||
import { _t } from "../../languageHandler";
|
||||
import * as Avatar from "../../Avatar";
|
||||
import EventTile, { haveTileForEvent } from "../../components/views/rooms/EventTile";
|
||||
import EventTile from "../../components/views/rooms/EventTile";
|
||||
import DateSeparator from "../../components/views/messages/DateSeparator";
|
||||
import BaseAvatar from "../../components/views/avatars/BaseAvatar";
|
||||
import { ExportType } from "./exportUtils";
|
||||
|
@ -39,6 +39,7 @@ import { IExportOptions } from "./exportUtils";
|
|||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||
import getExportCSS from "./exportCSS";
|
||||
import { textForEvent } from "../../TextForEvent";
|
||||
import { haveRendererForEvent } from "../../events/EventTileFactory";
|
||||
|
||||
import exportJS from "!!raw-loader!./exportJS";
|
||||
|
||||
|
@ -406,7 +407,7 @@ export default class HTMLExporter extends Exporter {
|
|||
total: events.length,
|
||||
}), false, true);
|
||||
if (this.cancelled) return this.cleanUp();
|
||||
if (!haveTileForEvent(event)) continue;
|
||||
if (!haveRendererForEvent(event)) continue;
|
||||
|
||||
content += this.needsDateSeparator(event, prevEvent) ? this.getDateSeparator(event) : "";
|
||||
const shouldBeJoined = !this.needsDateSeparator(event, prevEvent) &&
|
||||
|
|
|
@ -21,9 +21,9 @@ import { logger } from "matrix-js-sdk/src/logger";
|
|||
|
||||
import Exporter from "./Exporter";
|
||||
import { formatFullDateNoDay, formatFullDateNoDayNoTime } from "../../DateUtils";
|
||||
import { haveTileForEvent } from "../../components/views/rooms/EventTile";
|
||||
import { ExportType, IExportOptions } from "./exportUtils";
|
||||
import { _t } from "../../languageHandler";
|
||||
import { haveRendererForEvent } from "../../events/EventTileFactory";
|
||||
|
||||
export default class JSONExporter extends Exporter {
|
||||
protected totalSize = 0;
|
||||
|
@ -85,7 +85,7 @@ export default class JSONExporter extends Exporter {
|
|||
total: events.length,
|
||||
}), false, true);
|
||||
if (this.cancelled) return this.cleanUp();
|
||||
if (!haveTileForEvent(event)) continue;
|
||||
if (!haveRendererForEvent(event)) continue;
|
||||
this.messages.push(await this.getJSONString(event));
|
||||
}
|
||||
return this.createJSONString();
|
||||
|
|
|
@ -21,9 +21,9 @@ import { logger } from "matrix-js-sdk/src/logger";
|
|||
import Exporter from "./Exporter";
|
||||
import { formatFullDateNoDay } from "../../DateUtils";
|
||||
import { _t } from "../../languageHandler";
|
||||
import { haveTileForEvent } from "../../components/views/rooms/EventTile";
|
||||
import { ExportType, IExportOptions } from "./exportUtils";
|
||||
import { textForEvent } from "../../TextForEvent";
|
||||
import { haveRendererForEvent } from "../../events/EventTileFactory";
|
||||
|
||||
export default class PlainTextExporter extends Exporter {
|
||||
protected totalSize: number;
|
||||
|
@ -112,7 +112,7 @@ export default class PlainTextExporter extends Exporter {
|
|||
total: events.length,
|
||||
}), false, true);
|
||||
if (this.cancelled) return this.cleanUp();
|
||||
if (!haveTileForEvent(event)) continue;
|
||||
if (!haveRendererForEvent(event)) continue;
|
||||
const textForEvent = await this.plainTextForEvent(event);
|
||||
content += textForEvent && `${new Date(event.getTs()).toLocaleString()} - ${textForEvent}\n`;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue