You've already forked matrix-react-sdk
mirror of
https://github.com/matrix-org/matrix-react-sdk.git
synced 2025-08-07 21:23:00 +03:00
Remove legacy consumers of the RoomContext in favour of HOCs
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { createRef } from "react";
|
||||
import React, { createRef, forwardRef } from "react";
|
||||
import {
|
||||
Filter,
|
||||
EventTimelineSet,
|
||||
@@ -45,6 +45,7 @@ interface IProps {
|
||||
roomId: string;
|
||||
onClose: () => void;
|
||||
resizeNotifier: ResizeNotifier;
|
||||
context: React.ContextType<typeof RoomContext>;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
@@ -56,8 +57,6 @@ interface IState {
|
||||
* Component which shows the filtered file using a TimelinePanel
|
||||
*/
|
||||
class FilePanel extends React.Component<IProps, IState> {
|
||||
public static contextType = RoomContext;
|
||||
|
||||
// This is used to track if a decrypted event was a live event and should be
|
||||
// added to the timeline.
|
||||
private decryptingEvents = new Set<string>();
|
||||
@@ -267,7 +266,7 @@ class FilePanel extends React.Component<IProps, IState> {
|
||||
return (
|
||||
<RoomContext.Provider
|
||||
value={{
|
||||
...this.context,
|
||||
...this.props.context,
|
||||
timelineRenderingType: TimelineRenderingType.File,
|
||||
narrow: this.state.narrow,
|
||||
}}
|
||||
@@ -299,7 +298,7 @@ class FilePanel extends React.Component<IProps, IState> {
|
||||
return (
|
||||
<RoomContext.Provider
|
||||
value={{
|
||||
...this.context,
|
||||
...this.props.context,
|
||||
timelineRenderingType: TimelineRenderingType.File,
|
||||
}}
|
||||
>
|
||||
@@ -312,4 +311,6 @@ class FilePanel extends React.Component<IProps, IState> {
|
||||
}
|
||||
}
|
||||
|
||||
export default FilePanel;
|
||||
export default forwardRef<FilePanel, Omit<IProps, "context">>((props, ref) => (
|
||||
<RoomContext.Consumer>{(context) => <FilePanel {...props} context={context} ref={ref} />}</RoomContext.Consumer>
|
||||
));
|
||||
|
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { createRef, ReactNode, TransitionEvent } from "react";
|
||||
import React, { createRef, forwardRef, ReactNode, TransitionEvent } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import classNames from "classnames";
|
||||
import { Room, MatrixClient, RoomStateEvent, EventStatus, MatrixEvent, EventType } from "matrix-js-sdk/src/matrix";
|
||||
@@ -31,7 +31,6 @@ import EventTile, {
|
||||
GetRelationsForEvent,
|
||||
IReadReceiptProps,
|
||||
isEligibleForSpecialReceipt,
|
||||
UnwrappedEventTile,
|
||||
} from "../views/rooms/EventTile";
|
||||
import IRCTimelineProfileResizer from "../views/elements/IRCTimelineProfileResizer";
|
||||
import defaultDispatcher from "../../dispatcher/dispatcher";
|
||||
@@ -188,6 +187,7 @@ interface IProps {
|
||||
disableGrouping?: boolean;
|
||||
|
||||
callEventGroupers: Map<string, LegacyCallEventGrouper>;
|
||||
context: React.ContextType<typeof RoomContext>;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
@@ -203,10 +203,7 @@ interface IReadReceiptForUser {
|
||||
|
||||
/* (almost) stateless UI component which builds the event tiles in the room timeline.
|
||||
*/
|
||||
export default class MessagePanel extends React.Component<IProps, IState> {
|
||||
public static contextType = RoomContext;
|
||||
public context!: React.ContextType<typeof RoomContext>;
|
||||
|
||||
class MessagePanel extends React.Component<IProps, IState> {
|
||||
public static defaultProps = {
|
||||
disableGrouping: false,
|
||||
};
|
||||
@@ -256,13 +253,13 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
||||
private scrollPanel = createRef<ScrollPanel>();
|
||||
|
||||
private readonly showTypingNotificationsWatcherRef: string;
|
||||
private eventTiles: Record<string, UnwrappedEventTile> = {};
|
||||
private eventTiles: Record<string, React.ComponentRef<typeof EventTile>> = {};
|
||||
|
||||
// A map to allow groupers to maintain consistent keys even if their first event is uprooted due to back-pagination.
|
||||
public grouperKeyMap = new WeakMap<MatrixEvent, string>();
|
||||
|
||||
public constructor(props: IProps, context: React.ContextType<typeof RoomContext>) {
|
||||
super(props, context);
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
// previous positions the read marker has been in, so we can
|
||||
@@ -320,7 +317,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
||||
defaultDispatcher.dispatch({
|
||||
action: Action.EditEvent,
|
||||
event: !event?.isRedacted() ? event : null,
|
||||
timelineRenderingType: this.context.timelineRenderingType,
|
||||
timelineRenderingType: this.props.context.timelineRenderingType,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -354,7 +351,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
||||
return this.eventTiles[eventId]?.ref?.current ?? undefined;
|
||||
}
|
||||
|
||||
public getTileForEventId(eventId?: string): UnwrappedEventTile | undefined {
|
||||
public getTileForEventId(eventId?: string): React.ComponentRef<typeof EventTile> | undefined {
|
||||
if (!this.eventTiles || !eventId) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -454,7 +451,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
||||
};
|
||||
|
||||
public get showHiddenEvents(): boolean {
|
||||
return this.context?.showHiddenEvents ?? this._showHiddenEvents;
|
||||
return this.props.context?.showHiddenEvents ?? this._showHiddenEvents;
|
||||
}
|
||||
|
||||
// TODO: Implement granular (per-room) hide options
|
||||
@@ -481,7 +478,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
||||
// Always show highlighted event
|
||||
if (this.props.highlightedEventId === mxEv.getId()) return true;
|
||||
|
||||
return !shouldHideEvent(mxEv, this.context);
|
||||
return !shouldHideEvent(mxEv, this.props.context);
|
||||
}
|
||||
|
||||
public readMarkerForEvent(eventId: string, isLastEvent: boolean): ReactNode {
|
||||
@@ -592,7 +589,9 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
try {
|
||||
return localStorage.getItem(editorRoomKey(this.props.room.roomId, this.context.timelineRenderingType));
|
||||
return localStorage.getItem(
|
||||
editorRoomKey(this.props.room.roomId, this.props.context.timelineRenderingType),
|
||||
);
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
return null;
|
||||
@@ -775,13 +774,25 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
||||
willWantSeparator === SeparatorKind.Date ||
|
||||
mxEv.getSender() !== nextEv.getSender() ||
|
||||
getEventDisplayInfo(cli, nextEv, this.showHiddenEvents).isInfoMessage ||
|
||||
!shouldFormContinuation(mxEv, nextEv, cli, this.showHiddenEvents, this.context.timelineRenderingType);
|
||||
!shouldFormContinuation(
|
||||
mxEv,
|
||||
nextEv,
|
||||
cli,
|
||||
this.showHiddenEvents,
|
||||
this.props.context.timelineRenderingType,
|
||||
);
|
||||
}
|
||||
|
||||
// is this a continuation of the previous message?
|
||||
const continuation =
|
||||
wantsSeparator === SeparatorKind.None &&
|
||||
shouldFormContinuation(prevEvent, mxEv, cli, this.showHiddenEvents, this.context.timelineRenderingType);
|
||||
shouldFormContinuation(
|
||||
prevEvent,
|
||||
mxEv,
|
||||
cli,
|
||||
this.showHiddenEvents,
|
||||
this.props.context.timelineRenderingType,
|
||||
);
|
||||
|
||||
const eventId = mxEv.getId()!;
|
||||
const highlight = eventId === this.props.highlightedEventId;
|
||||
@@ -826,7 +837,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
public wantsSeparator(prevEvent: MatrixEvent | null, mxEvent: MatrixEvent): SeparatorKind {
|
||||
if (this.context.timelineRenderingType === TimelineRenderingType.ThreadsList) {
|
||||
if (this.props.context.timelineRenderingType === TimelineRenderingType.ThreadsList) {
|
||||
return SeparatorKind.None;
|
||||
}
|
||||
|
||||
@@ -863,13 +874,13 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
||||
return null;
|
||||
}
|
||||
|
||||
const receiptDestination = this.context.threadId ? room.getThread(this.context.threadId) : room;
|
||||
const receiptDestination = this.props.context.threadId ? room.getThread(this.props.context.threadId) : room;
|
||||
|
||||
const receipts: IReadReceiptProps[] = [];
|
||||
|
||||
if (!receiptDestination) {
|
||||
logger.debug(
|
||||
"Discarding request, could not find the receiptDestination for event: " + this.context.threadId,
|
||||
"Discarding request, could not find the receiptDestination for event: " + this.props.context.threadId,
|
||||
);
|
||||
return receipts;
|
||||
}
|
||||
@@ -950,7 +961,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
||||
return receiptsByEvent;
|
||||
}
|
||||
|
||||
private collectEventTile = (eventId: string, node: UnwrappedEventTile): void => {
|
||||
private collectEventTile = (eventId: string, node: React.ComponentRef<typeof EventTile>): void => {
|
||||
this.eventTiles[eventId] = node;
|
||||
};
|
||||
|
||||
@@ -1033,7 +1044,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
||||
if (
|
||||
this.props.room &&
|
||||
this.state.showTypingNotifications &&
|
||||
this.context.timelineRenderingType === TimelineRenderingType.Room
|
||||
this.props.context.timelineRenderingType === TimelineRenderingType.Room
|
||||
) {
|
||||
whoIsTyping = (
|
||||
<WhoIsTypingTile
|
||||
@@ -1053,7 +1064,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
const classes = classNames(this.props.className, {
|
||||
mx_MessagePanel_narrow: this.context.narrow,
|
||||
mx_MessagePanel_narrow: this.props.context.narrow,
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -1079,10 +1090,14 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
||||
}
|
||||
}
|
||||
|
||||
export default forwardRef<MessagePanel, Omit<IProps, "context">>((props, ref) => (
|
||||
<RoomContext.Consumer>{(context) => <MessagePanel {...props} context={context} ref={ref} />}</RoomContext.Consumer>
|
||||
));
|
||||
|
||||
/**
|
||||
* Holds on to an event, caching the information about it in the context of the current messages list.
|
||||
* Holds on to an event, caching the information about it in the props.context. of the current messages list.
|
||||
* Avoids calling shouldShowEvent more times than we need to.
|
||||
* Simplifies threading of event context like whether it's the last successful event we sent which cannot be determined
|
||||
* Simplifies threading of event props.context. like whether it's the last successful event we sent which cannot be determined
|
||||
* by a consumer from the event alone, so has to be done by the event list processing code earlier.
|
||||
*/
|
||||
export interface WrappedEvent {
|
||||
|
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { forwardRef } from "react";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { _t } from "../../languageHandler";
|
||||
@@ -29,6 +29,7 @@ import Heading from "../views/typography/Heading";
|
||||
|
||||
interface IProps {
|
||||
onClose(): void;
|
||||
context: React.ContextType<typeof RoomContext>;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
@@ -38,9 +39,7 @@ interface IState {
|
||||
/*
|
||||
* Component which shows the global notification list using a TimelinePanel
|
||||
*/
|
||||
export default class NotificationPanel extends React.PureComponent<IProps, IState> {
|
||||
public static contextType = RoomContext;
|
||||
|
||||
class NotificationPanel extends React.PureComponent<IProps, IState> {
|
||||
private card = React.createRef<HTMLDivElement>();
|
||||
|
||||
public constructor(props: IProps) {
|
||||
@@ -86,7 +85,7 @@ export default class NotificationPanel extends React.PureComponent<IProps, IStat
|
||||
return (
|
||||
<RoomContext.Provider
|
||||
value={{
|
||||
...this.context,
|
||||
...this.props.context,
|
||||
timelineRenderingType: TimelineRenderingType.Notification,
|
||||
narrow: this.state.narrow,
|
||||
}}
|
||||
@@ -114,3 +113,9 @@ export default class NotificationPanel extends React.PureComponent<IProps, IStat
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default forwardRef<NotificationPanel, Omit<IProps, "context">>((props, ref) => (
|
||||
<RoomContext.Consumer>
|
||||
{(context) => <NotificationPanel {...props} context={context} ref={ref} />}
|
||||
</RoomContext.Consumer>
|
||||
));
|
||||
|
@@ -323,7 +323,7 @@ function LocalRoomView(props: LocalRoomViewProps): ReactElement {
|
||||
<RoomHeader room={room} />
|
||||
) : (
|
||||
<LegacyRoomHeader
|
||||
room={context.room}
|
||||
room={context.room!}
|
||||
searchInfo={undefined}
|
||||
inRoom={true}
|
||||
onSearchClick={null}
|
||||
@@ -410,7 +410,7 @@ class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||
|
||||
private roomView = createRef<HTMLDivElement>();
|
||||
private searchResultsPanel = createRef<ScrollPanel>();
|
||||
private messagePanel: TimelinePanel | null = null;
|
||||
private messagePanel: React.ComponentRef<typeof TimelinePanel> | null = null;
|
||||
private roomViewBody = createRef<HTMLDivElement>();
|
||||
|
||||
public constructor(props: IRoomProps) {
|
||||
@@ -1958,7 +1958,7 @@ class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||
* We pass it down to the scroll panel.
|
||||
*/
|
||||
public handleScrollKey = (ev: React.KeyboardEvent | KeyboardEvent): void => {
|
||||
let panel: ScrollPanel | TimelinePanel | undefined;
|
||||
let panel: ScrollPanel | React.ComponentRef<typeof TimelinePanel> | undefined;
|
||||
if (this.searchResultsPanel.current) {
|
||||
panel = this.searchResultsPanel.current;
|
||||
} else if (this.messagePanel) {
|
||||
@@ -1980,7 +1980,7 @@ class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||
|
||||
// this has to be a proper method rather than an unnamed function,
|
||||
// otherwise react calls it with null on each update.
|
||||
private gatherTimelinePanelRef = (r: TimelinePanel | null): void => {
|
||||
private gatherTimelinePanelRef = (r: React.ComponentRef<typeof TimelinePanel> | null): void => {
|
||||
this.messagePanel = r;
|
||||
};
|
||||
|
||||
|
@@ -230,7 +230,7 @@ const EmptyThread: React.FC<EmptyThreadIProps> = ({ hasThreads, filterOption, sh
|
||||
const ThreadPanel: React.FC<IProps> = ({ roomId, onClose, permalinkCreator }) => {
|
||||
const mxClient = useContext(MatrixClientContext);
|
||||
const roomContext = useContext(RoomContext);
|
||||
const timelinePanel = useRef<TimelinePanel | null>(null);
|
||||
const timelinePanel = useRef<React.ComponentRef<typeof TimelinePanel> | null>(null);
|
||||
const card = useRef<HTMLDivElement | null>(null);
|
||||
const closeButonRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
|
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { createRef, KeyboardEvent } from "react";
|
||||
import React, { createRef, forwardRef, KeyboardEvent } from "react";
|
||||
import {
|
||||
Thread,
|
||||
THREAD_RELATION_TYPE,
|
||||
@@ -70,6 +70,7 @@ interface IProps {
|
||||
initialEvent?: MatrixEvent;
|
||||
isInitialEventHighlighted?: boolean;
|
||||
initialEventScrollIntoView?: boolean;
|
||||
context: React.ContextType<typeof RoomContext>;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
@@ -81,13 +82,10 @@ interface IState {
|
||||
narrow: boolean;
|
||||
}
|
||||
|
||||
export default class ThreadView extends React.Component<IProps, IState> {
|
||||
public static contextType = RoomContext;
|
||||
public context!: React.ContextType<typeof RoomContext>;
|
||||
|
||||
class ThreadView extends React.Component<IProps, IState> {
|
||||
private dispatcherRef: string | null = null;
|
||||
private readonly layoutWatcherRef: string;
|
||||
private timelinePanel = createRef<TimelinePanel>();
|
||||
private timelinePanel = createRef<React.ComponentRef<typeof TimelinePanel>>();
|
||||
private card = createRef<HTMLDivElement>();
|
||||
|
||||
// Set by setEventId in ctor.
|
||||
@@ -398,12 +396,12 @@ export default class ThreadView extends React.Component<IProps, IState> {
|
||||
<TimelinePanel
|
||||
key={this.state.thread.id}
|
||||
ref={this.timelinePanel}
|
||||
showReadReceipts={this.context.showReadReceipts}
|
||||
showReadReceipts={this.props.context.showReadReceipts}
|
||||
manageReadReceipts={true}
|
||||
manageReadMarkers={true}
|
||||
sendReadReceiptOnLoad={true}
|
||||
timelineSet={this.state.thread.timelineSet}
|
||||
showUrlPreview={this.context.showUrlPreview}
|
||||
showUrlPreview={this.props.context.showUrlPreview}
|
||||
// ThreadView doesn't support IRC layout at this time
|
||||
layout={this.state.layout === Layout.Bubble ? Layout.Bubble : Layout.Group}
|
||||
hideThreadedMessages={false}
|
||||
@@ -474,3 +472,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default forwardRef<ThreadView, Omit<IProps, "context">>((props, ref) => (
|
||||
<RoomContext.Consumer>{(context) => <ThreadView {...props} context={context} ref={ref} />}</RoomContext.Consumer>
|
||||
));
|
||||
|
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { createRef, ReactNode } from "react";
|
||||
import React, { createRef, forwardRef, ReactNode } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import {
|
||||
Room,
|
||||
@@ -167,6 +167,7 @@ interface IProps {
|
||||
|
||||
hideThreadedMessages?: boolean;
|
||||
disableGrouping?: boolean;
|
||||
context: React.ContextType<typeof RoomContext>;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
@@ -244,10 +245,7 @@ interface IEventIndexOpts {
|
||||
*
|
||||
* Also responsible for handling and sending read receipts.
|
||||
*/
|
||||
class TimelinePanel extends React.Component<IProps, IState> {
|
||||
public static contextType = RoomContext;
|
||||
public context!: React.ContextType<typeof RoomContext>;
|
||||
|
||||
export class TimelinePanel extends React.Component<IProps, IState> {
|
||||
// a map from room id to read marker event timestamp
|
||||
public static roomReadMarkerTsMap: Record<string, number> = {};
|
||||
|
||||
@@ -264,7 +262,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||
private lastRRSentEventId: string | null | undefined = undefined;
|
||||
private lastRMSentEventId: string | null | undefined = undefined;
|
||||
|
||||
private readonly messagePanel = createRef<MessagePanel>();
|
||||
private readonly messagePanel = createRef<React.ComponentRef<typeof MessagePanel>>();
|
||||
private readonly dispatcherRef: string;
|
||||
private timelineWindow?: TimelineWindow;
|
||||
private overlayTimelineWindow?: TimelineWindow;
|
||||
@@ -276,9 +274,8 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||
private callEventGroupers = new Map<string, LegacyCallEventGrouper>();
|
||||
private initialReadMarkerId: string | null = null;
|
||||
|
||||
public constructor(props: IProps, context: React.ContextType<typeof RoomContext>) {
|
||||
super(props, context);
|
||||
this.context = context;
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
debuglog("mounting");
|
||||
|
||||
@@ -500,7 +497,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`TimelinePanel(${this.context.timelineRenderingType}): Debugging info for ${room?.roomId}\n` +
|
||||
`TimelinePanel(${this.props.context.timelineRenderingType}): Debugging info for ${room?.roomId}\n` +
|
||||
`\tevents(${eventIdList.length})=${JSON.stringify(eventIdList)}\n` +
|
||||
`\trenderedEventIds(${renderedEventIds?.length ?? 0})=` +
|
||||
`${JSON.stringify(renderedEventIds)}\n` +
|
||||
@@ -736,7 +733,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Thread.hasServerSideSupport && this.context.timelineRenderingType === TimelineRenderingType.Thread) {
|
||||
if (!Thread.hasServerSideSupport && this.props.context.timelineRenderingType === TimelineRenderingType.Thread) {
|
||||
if (toStartOfTimeline && !this.state.canBackPaginate) {
|
||||
this.setState({
|
||||
canBackPaginate: true,
|
||||
@@ -1780,8 +1777,8 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||
pendingEvents,
|
||||
);
|
||||
|
||||
if (this.context.timelineRenderingType === TimelineRenderingType.Thread) {
|
||||
return threadId === this.context.threadId;
|
||||
if (this.props.context.timelineRenderingType === TimelineRenderingType.Thread) {
|
||||
return threadId === this.props.context.threadId;
|
||||
}
|
||||
{
|
||||
return shouldLiveInRoom;
|
||||
@@ -1812,7 +1809,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||
const room = this.props.timelineSet.room;
|
||||
|
||||
const isThreadTimeline = [TimelineRenderingType.Thread, TimelineRenderingType.ThreadsList].includes(
|
||||
this.context.timelineRenderingType,
|
||||
this.props.context.timelineRenderingType,
|
||||
);
|
||||
if (events.length === 0 || !room || !cli.isRoomEncrypted(room.roomId) || isThreadTimeline) {
|
||||
logger.debug("checkForPreJoinUISI: showing all messages, skipping check");
|
||||
@@ -1881,7 +1878,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||
/* Threads do not have server side support for read receipts and the concept
|
||||
is very tied to the main room timeline, we are forcing the timeline to
|
||||
send read receipts for threaded events */
|
||||
if (this.context.timelineRenderingType === TimelineRenderingType.Thread) {
|
||||
if (this.props.context.timelineRenderingType === TimelineRenderingType.Thread) {
|
||||
return 0;
|
||||
}
|
||||
const index = this.state.events.findIndex((ev) => ev.getId() === evId);
|
||||
@@ -2193,4 +2190,6 @@ function serializeEventIdsFromTimelineSets(timelineSets: EventTimelineSet[]): {
|
||||
return serializedEventIdsInTimelineSet;
|
||||
}
|
||||
|
||||
export default TimelinePanel;
|
||||
export default forwardRef<TimelinePanel, Omit<IProps, "context">>((props, ref) => (
|
||||
<RoomContext.Consumer>{(context) => <TimelinePanel {...props} context={context} ref={ref} />}</RoomContext.Consumer>
|
||||
));
|
||||
|
@@ -26,7 +26,7 @@ import RoomHeader from "../views/rooms/RoomHeader";
|
||||
import ScrollPanel from "./ScrollPanel";
|
||||
import EventTileBubble from "../views/messages/EventTileBubble";
|
||||
import NewRoomIntro from "../views/rooms/NewRoomIntro";
|
||||
import { UnwrappedEventTile } from "../views/rooms/EventTile";
|
||||
import EventTile from "../views/rooms/EventTile";
|
||||
import { _t } from "../../languageHandler";
|
||||
import SdkConfig from "../../SdkConfig";
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
@@ -53,7 +53,7 @@ export const WaitingForThirdPartyRoomView: React.FC<Props> = ({ roomView, resize
|
||||
<RoomHeader room={context.room!} />
|
||||
) : (
|
||||
<LegacyRoomHeader
|
||||
room={context.room}
|
||||
room={context.room!}
|
||||
inRoom={true}
|
||||
onSearchClick={null}
|
||||
onInviteClick={null}
|
||||
@@ -77,7 +77,7 @@ export const WaitingForThirdPartyRoomView: React.FC<Props> = ({ roomView, resize
|
||||
subtitle={_t("room|waiting_for_join_subtitle", { brand })}
|
||||
/>
|
||||
<NewRoomIntro />
|
||||
<UnwrappedEventTile mxEvent={inviteEvent} />
|
||||
<EventTile mxEvent={inviteEvent} />
|
||||
</ScrollPanel>
|
||||
</div>
|
||||
</main>
|
||||
|
@@ -31,7 +31,7 @@ import MessagePanel, { WrappedEvent } from "../MessagePanel";
|
||||
* when determining things such as whether a date separator is necessary
|
||||
*/
|
||||
export abstract class BaseGrouper {
|
||||
public static canStartGroup = (_panel: MessagePanel, _ev: WrappedEvent): boolean => true;
|
||||
public static canStartGroup = (_panel: React.ComponentRef<typeof MessagePanel>, _ev: WrappedEvent): boolean => true;
|
||||
|
||||
public events: WrappedEvent[] = [];
|
||||
// events that we include in the group but then eject out and place above the group.
|
||||
@@ -39,7 +39,7 @@ export abstract class BaseGrouper {
|
||||
public readMarker: ReactNode;
|
||||
|
||||
public constructor(
|
||||
public readonly panel: MessagePanel,
|
||||
public readonly panel: React.ComponentRef<typeof MessagePanel>,
|
||||
public readonly firstEventAndShouldShow: WrappedEvent,
|
||||
public readonly prevEvent: MatrixEvent | null,
|
||||
public readonly lastShownEvent: MatrixEvent | undefined,
|
||||
|
@@ -33,7 +33,10 @@ import { SeparatorKind } from "../../views/messages/TimelineSeparator";
|
||||
// the first non-state event, beacon_info event or membership event which is not regarding the sender of the `m.room.create` event
|
||||
|
||||
export class CreationGrouper extends BaseGrouper {
|
||||
public static canStartGroup = function (_panel: MessagePanel, { event }: WrappedEvent): boolean {
|
||||
public static canStartGroup = function (
|
||||
_panel: React.ComponentRef<typeof MessagePanel>,
|
||||
{ event }: WrappedEvent,
|
||||
): boolean {
|
||||
return event.getType() === EventType.RoomCreate;
|
||||
};
|
||||
|
||||
|
@@ -36,7 +36,10 @@ const groupedStateEvents = [
|
||||
|
||||
// Wrap consecutive grouped events in a ListSummary
|
||||
export class MainGrouper extends BaseGrouper {
|
||||
public static canStartGroup = function (panel: MessagePanel, { event: ev, shouldShow }: WrappedEvent): boolean {
|
||||
public static canStartGroup = function (
|
||||
panel: React.ComponentRef<typeof MessagePanel>,
|
||||
{ event: ev, shouldShow }: WrappedEvent,
|
||||
): boolean {
|
||||
if (!shouldShow) return false;
|
||||
|
||||
if (ev.isState() && groupedStateEvents.includes(ev.getType() as EventType)) {
|
||||
@@ -55,7 +58,7 @@ export class MainGrouper extends BaseGrouper {
|
||||
};
|
||||
|
||||
public constructor(
|
||||
public readonly panel: MessagePanel,
|
||||
public readonly panel: React.ComponentRef<typeof MessagePanel>,
|
||||
public readonly firstEventAndShouldShow: WrappedEvent,
|
||||
public readonly prevEvent: MatrixEvent | null,
|
||||
public readonly lastShownEvent: MatrixEvent | undefined,
|
||||
|
@@ -16,7 +16,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { createRef, useContext } from "react";
|
||||
import React, { createRef, forwardRef, useContext } from "react";
|
||||
import {
|
||||
EventStatus,
|
||||
MatrixEvent,
|
||||
@@ -123,6 +123,7 @@ interface IProps extends MenuProps {
|
||||
link?: string;
|
||||
|
||||
getRelationsForEvent?: GetRelationsForEvent;
|
||||
context: React.ContextType<typeof RoomContext>;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
@@ -131,11 +132,8 @@ interface IState {
|
||||
reactionPickerDisplayed: boolean;
|
||||
}
|
||||
|
||||
export default class MessageContextMenu extends React.Component<IProps, IState> {
|
||||
public static contextType = RoomContext;
|
||||
public context!: React.ContextType<typeof RoomContext>;
|
||||
|
||||
private reactButtonRef = createRef<any>(); // XXX Ref to a functional component
|
||||
class MessageContextMenu extends React.Component<IProps, IState> {
|
||||
private reactButtonRef = createRef<HTMLElement>();
|
||||
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
@@ -315,7 +313,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
||||
editEvent(
|
||||
MatrixClientPeg.safeGet(),
|
||||
this.props.mxEvent,
|
||||
this.context.timelineRenderingType,
|
||||
this.props.context.timelineRenderingType,
|
||||
this.props.getRelationsForEvent,
|
||||
);
|
||||
this.closeMenu();
|
||||
@@ -325,7 +323,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
||||
dis.dispatch({
|
||||
action: "reply_to_event",
|
||||
event: this.props.mxEvent,
|
||||
context: this.context.timelineRenderingType,
|
||||
context: this.props.context.timelineRenderingType,
|
||||
});
|
||||
this.closeMenu();
|
||||
};
|
||||
@@ -733,3 +731,9 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default forwardRef<MessageContextMenu, Omit<IProps, "context">>((props, ref) => (
|
||||
<RoomContext.Consumer>
|
||||
{(context) => <MessageContextMenu {...props} context={context} ref={ref} />}
|
||||
</RoomContext.Consumer>
|
||||
));
|
||||
|
@@ -16,7 +16,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { ComponentProps, ReactNode } from "react";
|
||||
import React, { ComponentProps, forwardRef, ReactNode } from "react";
|
||||
import { MatrixEvent, RoomMember, EventType } from "matrix-js-sdk/src/matrix";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
|
||||
@@ -43,7 +43,8 @@ interface IProps extends Omit<ComponentProps<typeof GenericEventListSummary>, "s
|
||||
// The maximum number of avatars to display in the summary
|
||||
avatarsMaxLength?: number;
|
||||
// The currently selected layout
|
||||
layout: Layout;
|
||||
layout?: Layout;
|
||||
context: React.ContextType<typeof RoomContext>;
|
||||
}
|
||||
|
||||
interface IUserEvents {
|
||||
@@ -77,12 +78,9 @@ enum TransitionType {
|
||||
|
||||
const SEP = ",";
|
||||
|
||||
export default class EventListSummary extends React.Component<
|
||||
class EventListSummary extends React.Component<
|
||||
IProps & Required<Pick<IProps, "summaryLength" | "threshold" | "avatarsMaxLength" | "layout">>
|
||||
> {
|
||||
public static contextType = RoomContext;
|
||||
public context!: React.ContextType<typeof RoomContext>;
|
||||
|
||||
public static defaultProps = {
|
||||
summaryLength: 1,
|
||||
threshold: 3,
|
||||
@@ -527,7 +525,7 @@ export default class EventListSummary extends React.Component<
|
||||
|
||||
let displayName = userKey;
|
||||
if (e.isRedacted()) {
|
||||
const sender = this.context?.room?.getMember(userKey);
|
||||
const sender = this.props.context?.room?.getMember(userKey);
|
||||
if (sender) {
|
||||
displayName = sender.name;
|
||||
latestUserAvatarMember.set(userKey, sender);
|
||||
@@ -569,3 +567,9 @@ export default class EventListSummary extends React.Component<
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default forwardRef<EventListSummary, Omit<IProps, "context">>((props, ref) => (
|
||||
<RoomContext.Consumer>
|
||||
{(context) => <EventListSummary {...props} context={context} ref={ref} />}
|
||||
</RoomContext.Consumer>
|
||||
));
|
||||
|
@@ -31,7 +31,6 @@ import ReplyTile from "../rooms/ReplyTile";
|
||||
import { Pill, PillType } from "./Pill";
|
||||
import AccessibleButton from "./AccessibleButton";
|
||||
import { getParentEventId, shouldDisplayReply } from "../../../utils/Reply";
|
||||
import RoomContext from "../../../contexts/RoomContext";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import { GetRelationsForEvent } from "../rooms/EventTile";
|
||||
|
||||
@@ -72,15 +71,12 @@ interface IState {
|
||||
// craft event_id's, using a homeserver that generates predictable event IDs; even then the impact would
|
||||
// be low as each event being loaded (after the first) is triggered by an explicit user action.
|
||||
export default class ReplyChain extends React.Component<IProps, IState> {
|
||||
public static contextType = RoomContext;
|
||||
public context!: React.ContextType<typeof RoomContext>;
|
||||
|
||||
private unmounted = false;
|
||||
private room: Room;
|
||||
private blockquoteRef = React.createRef<HTMLQuoteElement>();
|
||||
|
||||
public constructor(props: IProps, context: React.ContextType<typeof RoomContext>) {
|
||||
super(props, context);
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
events: [],
|
||||
|
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { forwardRef } from "react";
|
||||
import { MatrixEvent, EventType, RelationType, Relations, RelationsEvent } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import EmojiPicker from "./EmojiPicker";
|
||||
@@ -29,6 +29,7 @@ interface IProps {
|
||||
mxEvent: MatrixEvent;
|
||||
reactions?: Relations | null | undefined;
|
||||
onFinished(): void;
|
||||
context: React.ContextType<typeof RoomContext>;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
@@ -36,11 +37,8 @@ interface IState {
|
||||
}
|
||||
|
||||
class ReactionPicker extends React.Component<IProps, IState> {
|
||||
public static contextType = RoomContext;
|
||||
public context!: React.ContextType<typeof RoomContext>;
|
||||
|
||||
public constructor(props: IProps, context: React.ContextType<typeof RoomContext>) {
|
||||
super(props, context);
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
selectedEmojis: new Set(Object.keys(this.getReactions())),
|
||||
@@ -95,12 +93,12 @@ class ReactionPicker extends React.Component<IProps, IState> {
|
||||
this.props.onFinished();
|
||||
const myReactions = this.getReactions();
|
||||
if (myReactions.hasOwnProperty(reaction)) {
|
||||
if (this.props.mxEvent.isRedacted() || !this.context.canSelfRedact) return false;
|
||||
if (this.props.mxEvent.isRedacted() || !this.props.context.canSelfRedact) return false;
|
||||
|
||||
MatrixClientPeg.safeGet().redactEvent(this.props.mxEvent.getRoomId()!, myReactions[reaction]);
|
||||
dis.dispatch<FocusComposerPayload>({
|
||||
action: Action.FocusAComposer,
|
||||
context: this.context.timelineRenderingType,
|
||||
context: this.props.context.timelineRenderingType,
|
||||
});
|
||||
// Tell the emoji picker not to bump this in the more frequently used list.
|
||||
return false;
|
||||
@@ -115,7 +113,7 @@ class ReactionPicker extends React.Component<IProps, IState> {
|
||||
dis.dispatch({ action: "message_sent" });
|
||||
dis.dispatch<FocusComposerPayload>({
|
||||
action: Action.FocusAComposer,
|
||||
context: this.context.timelineRenderingType,
|
||||
context: this.props.context.timelineRenderingType,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
@@ -123,7 +121,7 @@ class ReactionPicker extends React.Component<IProps, IState> {
|
||||
|
||||
private isEmojiDisabled = (unicode: string): boolean => {
|
||||
if (!this.getReactions()[unicode]) return false;
|
||||
if (this.context.canSelfRedact) return false;
|
||||
if (this.props.context.canSelfRedact) return false;
|
||||
|
||||
return true;
|
||||
};
|
||||
@@ -140,4 +138,8 @@ class ReactionPicker extends React.Component<IProps, IState> {
|
||||
}
|
||||
}
|
||||
|
||||
export default ReactionPicker;
|
||||
export default forwardRef<ReactionPicker, Omit<IProps, "context">>((props, ref) => (
|
||||
<RoomContext.Consumer>
|
||||
{(context) => <ReactionPicker {...props} context={context} ref={ref} />}
|
||||
</RoomContext.Consumer>
|
||||
));
|
||||
|
@@ -163,7 +163,7 @@ interface CallEventProps {
|
||||
/**
|
||||
* An event tile representing an active or historical Element call.
|
||||
*/
|
||||
export const CallEvent = forwardRef<any, CallEventProps>(({ mxEvent }, ref) => {
|
||||
export const CallEvent = forwardRef<HTMLDivElement, CallEventProps>(({ mxEvent }, ref) => {
|
||||
const client = useContext(MatrixClientContext);
|
||||
const call = useCall(mxEvent.getRoomId()!);
|
||||
const latestEvent = client
|
||||
|
@@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { LegacyRef } from "react";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { MediaEventHelper } from "../../../utils/MediaEventHelper";
|
||||
@@ -54,8 +53,6 @@ export interface IBodyProps {
|
||||
// helper function to access relations for this event
|
||||
getRelationsForEvent?: GetRelationsForEvent;
|
||||
|
||||
ref?: React.RefObject<any> | LegacyRef<any>;
|
||||
|
||||
// Set to `true` to disable interactions (e.g. video controls) and to remove controls from the tab order.
|
||||
// This may be useful when displaying a preview of the event.
|
||||
inhibitInteraction?: boolean;
|
||||
|
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { forwardRef } from "react";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { IContent } from "matrix-js-sdk/src/matrix";
|
||||
import { MediaEventContent } from "matrix-js-sdk/src/types";
|
||||
@@ -31,16 +31,17 @@ import { PlaybackQueue } from "../../../audio/PlaybackQueue";
|
||||
import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
|
||||
import MediaProcessingError from "./shared/MediaProcessingError";
|
||||
|
||||
interface Props extends IBodyProps {
|
||||
context: React.ContextType<typeof RoomContext>;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
error?: boolean;
|
||||
playback?: Playback;
|
||||
}
|
||||
|
||||
export default class MAudioBody extends React.PureComponent<IBodyProps, IState> {
|
||||
public static contextType = RoomContext;
|
||||
public context!: React.ContextType<typeof RoomContext>;
|
||||
|
||||
public constructor(props: IBodyProps) {
|
||||
export class MAudioBody extends React.PureComponent<Props, IState> {
|
||||
public constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {};
|
||||
@@ -88,9 +89,9 @@ export default class MAudioBody extends React.PureComponent<IBodyProps, IState>
|
||||
|
||||
protected get showFileBody(): boolean {
|
||||
return (
|
||||
this.context.timelineRenderingType !== TimelineRenderingType.Room &&
|
||||
this.context.timelineRenderingType !== TimelineRenderingType.Pinned &&
|
||||
this.context.timelineRenderingType !== TimelineRenderingType.Search
|
||||
this.props.context.timelineRenderingType !== TimelineRenderingType.Room &&
|
||||
this.props.context.timelineRenderingType !== TimelineRenderingType.Pinned &&
|
||||
this.props.context.timelineRenderingType !== TimelineRenderingType.Search
|
||||
);
|
||||
}
|
||||
|
||||
@@ -131,3 +132,7 @@ export default class MAudioBody extends React.PureComponent<IBodyProps, IState>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default forwardRef<MAudioBody, Omit<Props, "context">>((props, ref) => (
|
||||
<RoomContext.Consumer>{(context) => <MAudioBody {...props} context={context} ref={ref} />}</RoomContext.Consumer>
|
||||
));
|
||||
|
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { AllHTMLAttributes, createRef } from "react";
|
||||
import React, { AllHTMLAttributes, createRef, forwardRef } from "react";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { MediaEventContent } from "matrix-js-sdk/src/types";
|
||||
|
||||
@@ -97,17 +97,15 @@ export function computedStyle(element: HTMLElement | null): string {
|
||||
|
||||
interface IProps extends IBodyProps {
|
||||
/* whether or not to show the default placeholder for the file. Defaults to true. */
|
||||
showGenericPlaceholder: boolean;
|
||||
showGenericPlaceholder?: boolean;
|
||||
context: React.ContextType<typeof RoomContext>;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
decryptedBlob?: Blob;
|
||||
}
|
||||
|
||||
export default class MFileBody extends React.Component<IProps, IState> {
|
||||
public static contextType = RoomContext;
|
||||
public context!: React.ContextType<typeof RoomContext>;
|
||||
|
||||
class MFileBody extends React.Component<IProps, IState> {
|
||||
public static defaultProps = {
|
||||
showGenericPlaceholder: true,
|
||||
};
|
||||
@@ -226,11 +224,11 @@ export default class MFileBody extends React.Component<IProps, IState> {
|
||||
|
||||
let showDownloadLink =
|
||||
!this.props.showGenericPlaceholder ||
|
||||
(this.context.timelineRenderingType !== TimelineRenderingType.Room &&
|
||||
this.context.timelineRenderingType !== TimelineRenderingType.Search &&
|
||||
this.context.timelineRenderingType !== TimelineRenderingType.Pinned);
|
||||
(this.props.context.timelineRenderingType !== TimelineRenderingType.Room &&
|
||||
this.props.context.timelineRenderingType !== TimelineRenderingType.Search &&
|
||||
this.props.context.timelineRenderingType !== TimelineRenderingType.Pinned);
|
||||
|
||||
if (this.context.timelineRenderingType === TimelineRenderingType.Thread) {
|
||||
if (this.props.context.timelineRenderingType === TimelineRenderingType.Thread) {
|
||||
showDownloadLink = false;
|
||||
}
|
||||
|
||||
@@ -348,7 +346,7 @@ export default class MFileBody extends React.Component<IProps, IState> {
|
||||
<span className="mx_MFileBody_download_icon" />
|
||||
{_t("timeline|m.file|download_label", { text: this.linkText })}
|
||||
</a>
|
||||
{this.context.timelineRenderingType === TimelineRenderingType.File && (
|
||||
{this.props.context.timelineRenderingType === TimelineRenderingType.File && (
|
||||
<div className="mx_MImageBody_size">
|
||||
{this.content.info?.size ? fileSize(this.content.info.size) : ""}
|
||||
</div>
|
||||
@@ -368,3 +366,7 @@ export default class MFileBody extends React.Component<IProps, IState> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default forwardRef<MFileBody, Omit<IProps, "context">>((props, ref) => (
|
||||
<RoomContext.Consumer>{(context) => <MFileBody {...props} context={context} ref={ref} />}</RoomContext.Consumer>
|
||||
));
|
||||
|
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { ComponentProps, createRef, ReactNode } from "react";
|
||||
import React, { ComponentProps, createRef, forwardRef, ReactNode } from "react";
|
||||
import { Blurhash } from "react-blurhash";
|
||||
import classNames from "classnames";
|
||||
import { CSSTransition, SwitchTransition } from "react-transition-group";
|
||||
@@ -47,6 +47,10 @@ enum Placeholder {
|
||||
Blurhash,
|
||||
}
|
||||
|
||||
interface Props extends IBodyProps {
|
||||
context: React.ContextType<typeof RoomContext>;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
contentUrl: string | null;
|
||||
thumbUrl: string | null;
|
||||
@@ -63,17 +67,14 @@ interface IState {
|
||||
placeholder: Placeholder;
|
||||
}
|
||||
|
||||
export default class MImageBody extends React.Component<IBodyProps, IState> {
|
||||
public static contextType = RoomContext;
|
||||
public context!: React.ContextType<typeof RoomContext>;
|
||||
|
||||
export class MImageBody extends React.Component<Props, IState> {
|
||||
private unmounted = true;
|
||||
private image = createRef<HTMLImageElement>();
|
||||
private timeout?: number;
|
||||
private sizeWatcher?: string;
|
||||
private reconnectedListener: ClientEventHandlerMap[ClientEvent.Sync];
|
||||
|
||||
public constructor(props: IBodyProps) {
|
||||
public constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.reconnectedListener = createReconnectedListener(this.clearError);
|
||||
@@ -391,7 +392,9 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
||||
protected getBanner(content: ImageContent): ReactNode {
|
||||
// Hide it for the threads list & the file panel where we show it as text anyway.
|
||||
if (
|
||||
[TimelineRenderingType.ThreadsList, TimelineRenderingType.File].includes(this.context.timelineRenderingType)
|
||||
[TimelineRenderingType.ThreadsList, TimelineRenderingType.File].includes(
|
||||
this.props.context.timelineRenderingType,
|
||||
)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
@@ -601,11 +604,11 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
||||
* link as the message action bar will fulfill that
|
||||
*/
|
||||
const hasMessageActionBar =
|
||||
this.context.timelineRenderingType === TimelineRenderingType.Room ||
|
||||
this.context.timelineRenderingType === TimelineRenderingType.Pinned ||
|
||||
this.context.timelineRenderingType === TimelineRenderingType.Search ||
|
||||
this.context.timelineRenderingType === TimelineRenderingType.Thread ||
|
||||
this.context.timelineRenderingType === TimelineRenderingType.ThreadsList;
|
||||
this.props.context.timelineRenderingType === TimelineRenderingType.Room ||
|
||||
this.props.context.timelineRenderingType === TimelineRenderingType.Pinned ||
|
||||
this.props.context.timelineRenderingType === TimelineRenderingType.Search ||
|
||||
this.props.context.timelineRenderingType === TimelineRenderingType.Thread ||
|
||||
this.props.context.timelineRenderingType === TimelineRenderingType.ThreadsList;
|
||||
if (!hasMessageActionBar) {
|
||||
return <MFileBody {...this.props} showGenericPlaceholder={false} />;
|
||||
}
|
||||
@@ -648,6 +651,10 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
||||
}
|
||||
}
|
||||
|
||||
export default forwardRef<MImageBody, Omit<Props, "context">>((props, ref) => (
|
||||
<RoomContext.Consumer>{(context) => <MImageBody {...props} context={context} ref={ref} />}</RoomContext.Consumer>
|
||||
));
|
||||
|
||||
interface PlaceholderIProps {
|
||||
hover?: boolean;
|
||||
maxWidth?: number;
|
||||
|
@@ -14,14 +14,20 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { forwardRef } from "react";
|
||||
import { ImageContent } from "matrix-js-sdk/src/types";
|
||||
|
||||
import MImageBody from "./MImageBody";
|
||||
import { MImageBody } from "./MImageBody";
|
||||
import { IBodyProps } from "./IBodyProps";
|
||||
import RoomContext from "../../../contexts/RoomContext";
|
||||
|
||||
const FORCED_IMAGE_HEIGHT = 44;
|
||||
|
||||
export default class MImageReplyBody extends MImageBody {
|
||||
interface Props extends IBodyProps {
|
||||
context: React.ContextType<typeof RoomContext>;
|
||||
}
|
||||
|
||||
class MImageReplyBody extends MImageBody {
|
||||
public onClick = (ev: React.MouseEvent): void => {
|
||||
ev.preventDefault();
|
||||
};
|
||||
@@ -43,3 +49,9 @@ export default class MImageReplyBody extends MImageBody {
|
||||
return <div className="mx_MImageReplyBody">{thumbnail}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export default forwardRef<MImageReplyBody, Omit<Props, "context">>((props, ref) => (
|
||||
<RoomContext.Consumer>
|
||||
{(context) => <MImageReplyBody {...props} context={context} ref={ref} />}
|
||||
</RoomContext.Consumer>
|
||||
));
|
||||
|
@@ -14,14 +14,20 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { ComponentProps, ReactNode } from "react";
|
||||
import React, { ComponentProps, forwardRef, ReactNode } from "react";
|
||||
import { Tooltip } from "@vector-im/compound-web";
|
||||
import { MediaEventContent } from "matrix-js-sdk/src/types";
|
||||
|
||||
import MImageBody from "./MImageBody";
|
||||
import { MImageBody } from "./MImageBody";
|
||||
import { BLURHASH_FIELD } from "../../../utils/image-media";
|
||||
import RoomContext from "../../../contexts/RoomContext";
|
||||
import { IBodyProps } from "./IBodyProps";
|
||||
|
||||
export default class MStickerBody extends MImageBody {
|
||||
interface Props extends IBodyProps {
|
||||
context: React.ContextType<typeof RoomContext>;
|
||||
}
|
||||
|
||||
class MStickerBody extends MImageBody {
|
||||
// Mostly empty to prevent default behaviour of MImageBody
|
||||
protected onClick = (ev: React.MouseEvent): void => {
|
||||
ev.preventDefault();
|
||||
@@ -83,3 +89,7 @@ export default class MStickerBody extends MImageBody {
|
||||
return null; // we don't need a banner, we have a tooltip
|
||||
}
|
||||
}
|
||||
|
||||
export default forwardRef<MStickerBody, Omit<Props, "context">>((props, ref) => (
|
||||
<RoomContext.Consumer>{(context) => <MStickerBody {...props} context={context} ref={ref} />}</RoomContext.Consumer>
|
||||
));
|
||||
|
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { ReactNode } from "react";
|
||||
import React, { forwardRef, ReactNode } from "react";
|
||||
import { decode } from "blurhash";
|
||||
import { MediaEventContent } from "matrix-js-sdk/src/types";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
@@ -30,6 +30,10 @@ import { ImageSize, suggestedSize as suggestedVideoSize } from "../../../setting
|
||||
import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
|
||||
import MediaProcessingError from "./shared/MediaProcessingError";
|
||||
|
||||
interface Props extends IBodyProps {
|
||||
context: React.ContextType<typeof RoomContext>;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
decryptedUrl: string | null;
|
||||
decryptedThumbnailUrl: string | null;
|
||||
@@ -40,14 +44,11 @@ interface IState {
|
||||
blurhashUrl: string | null;
|
||||
}
|
||||
|
||||
export default class MVideoBody extends React.PureComponent<IBodyProps, IState> {
|
||||
public static contextType = RoomContext;
|
||||
public context!: React.ContextType<typeof RoomContext>;
|
||||
|
||||
class MVideoBody extends React.PureComponent<Props, IState> {
|
||||
private videoRef = React.createRef<HTMLVideoElement>();
|
||||
private sizeWatcher?: string;
|
||||
|
||||
public constructor(props: IBodyProps) {
|
||||
public constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
@@ -221,9 +222,9 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
|
||||
|
||||
protected get showFileBody(): boolean {
|
||||
return (
|
||||
this.context.timelineRenderingType !== TimelineRenderingType.Room &&
|
||||
this.context.timelineRenderingType !== TimelineRenderingType.Pinned &&
|
||||
this.context.timelineRenderingType !== TimelineRenderingType.Search
|
||||
this.props.context.timelineRenderingType !== TimelineRenderingType.Room &&
|
||||
this.props.context.timelineRenderingType !== TimelineRenderingType.Pinned &&
|
||||
this.props.context.timelineRenderingType !== TimelineRenderingType.Search
|
||||
);
|
||||
}
|
||||
|
||||
@@ -306,3 +307,7 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default forwardRef<MVideoBody, Omit<Props, "context">>((props, ref) => (
|
||||
<RoomContext.Consumer>{(context) => <MVideoBody {...props} context={context} ref={ref} />}</RoomContext.Consumer>
|
||||
));
|
||||
|
@@ -14,16 +14,22 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { forwardRef } from "react";
|
||||
|
||||
import InlineSpinner from "../elements/InlineSpinner";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import RecordingPlayback from "../audio_messages/RecordingPlayback";
|
||||
import MAudioBody from "./MAudioBody";
|
||||
import { MAudioBody } from "./MAudioBody";
|
||||
import MFileBody from "./MFileBody";
|
||||
import MediaProcessingError from "./shared/MediaProcessingError";
|
||||
import { IBodyProps } from "./IBodyProps";
|
||||
import RoomContext from "../../../contexts/RoomContext";
|
||||
|
||||
export default class MVoiceMessageBody extends MAudioBody {
|
||||
interface Props extends IBodyProps {
|
||||
context: React.ContextType<typeof RoomContext>;
|
||||
}
|
||||
|
||||
class MVoiceMessageBody extends MAudioBody {
|
||||
// A voice message is an audio file but rendered in a special way.
|
||||
public render(): React.ReactNode {
|
||||
if (this.state.error) {
|
||||
@@ -51,3 +57,9 @@ export default class MVoiceMessageBody extends MAudioBody {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default forwardRef<MVoiceMessageBody, Omit<Props, "context">>((props, ref) => (
|
||||
<RoomContext.Consumer>
|
||||
{(context) => <MVoiceMessageBody {...props} context={context} ref={ref} />}
|
||||
</RoomContext.Consumer>
|
||||
));
|
||||
|
@@ -16,7 +16,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { ReactElement, useCallback, useContext, useEffect } from "react";
|
||||
import React, { forwardRef, ReactElement, useCallback, useContext, useEffect } from "react";
|
||||
import {
|
||||
EventStatus,
|
||||
MatrixEvent,
|
||||
@@ -261,11 +261,10 @@ interface IMessageActionBarProps {
|
||||
toggleThreadExpanded: () => void;
|
||||
isQuoteExpanded?: boolean;
|
||||
getRelationsForEvent?: GetRelationsForEvent;
|
||||
context: React.ContextType<typeof RoomContext>;
|
||||
}
|
||||
|
||||
export default class MessageActionBar extends React.PureComponent<IMessageActionBarProps> {
|
||||
public static contextType = RoomContext;
|
||||
|
||||
class MessageActionBar extends React.PureComponent<IMessageActionBarProps> {
|
||||
public componentDidMount(): void {
|
||||
if (this.props.mxEvent.status && this.props.mxEvent.status !== EventStatus.SENT) {
|
||||
this.props.mxEvent.on(MatrixEventEvent.Status, this.onSent);
|
||||
@@ -314,7 +313,7 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
|
||||
dis.dispatch({
|
||||
action: "reply_to_event",
|
||||
event: this.props.mxEvent,
|
||||
context: this.context.timelineRenderingType,
|
||||
context: this.props.context.timelineRenderingType,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -326,7 +325,7 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
|
||||
editEvent(
|
||||
MatrixClientPeg.safeGet(),
|
||||
this.props.mxEvent,
|
||||
this.context.timelineRenderingType,
|
||||
this.props.context.timelineRenderingType,
|
||||
this.props.getRelationsForEvent,
|
||||
);
|
||||
};
|
||||
@@ -334,7 +333,7 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
|
||||
private readonly forbiddenThreadHeadMsgType = [MsgType.KeyVerificationRequest];
|
||||
|
||||
private get showReplyInThreadAction(): boolean {
|
||||
const inNotThreadTimeline = this.context.timelineRenderingType !== TimelineRenderingType.Thread;
|
||||
const inNotThreadTimeline = this.props.context.timelineRenderingType !== TimelineRenderingType.Thread;
|
||||
|
||||
const isAllowedMessageType =
|
||||
!this.forbiddenThreadHeadMsgType.includes(this.props.mxEvent.getContent().msgtype as MsgType) &&
|
||||
@@ -448,7 +447,7 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
|
||||
// The only catch is we do the reply button first so that we can make sure the react
|
||||
// button is the very first button without having to do length checks for `splice()`.
|
||||
|
||||
if (this.context.canSendMessages) {
|
||||
if (this.props.context.canSendMessages) {
|
||||
if (this.showReplyInThreadAction) {
|
||||
toolbarOpts.splice(0, 0, threadTooltipButton);
|
||||
}
|
||||
@@ -467,7 +466,7 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
|
||||
);
|
||||
}
|
||||
// We hide the react button in search results as we don't show reactions in results
|
||||
if (this.context.canReact && !this.context.search) {
|
||||
if (this.props.context.canReact && !this.props.context.search) {
|
||||
toolbarOpts.splice(
|
||||
0,
|
||||
0,
|
||||
@@ -494,7 +493,7 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
|
||||
}
|
||||
} else if (
|
||||
// Show thread icon even for deleted messages, but only within main timeline
|
||||
this.context.timelineRenderingType === TimelineRenderingType.Room &&
|
||||
this.props.context.timelineRenderingType === TimelineRenderingType.Room &&
|
||||
this.props.mxEvent.getThread()
|
||||
) {
|
||||
toolbarOpts.unshift(threadTooltipButton);
|
||||
@@ -560,3 +559,9 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default forwardRef<MessageActionBar, Omit<IMessageActionBarProps, "context">>((props, ref) => (
|
||||
<RoomContext.Consumer>
|
||||
{(context) => <MessageActionBar {...props} context={context} ref={ref} />}
|
||||
</RoomContext.Consumer>
|
||||
));
|
||||
|
@@ -51,8 +51,8 @@ import { VoiceBroadcastBody, VoiceBroadcastInfoEventType, VoiceBroadcastInfoStat
|
||||
// onMessageAllowed is handled internally
|
||||
interface IProps extends Omit<IBodyProps, "onMessageAllowed" | "mediaEventHelper"> {
|
||||
/* overrides for the msgtype-specific components, used by ReplyTile to override file rendering */
|
||||
overrideBodyTypes?: Record<string, typeof React.Component>;
|
||||
overrideEventTypes?: Record<string, typeof React.Component>;
|
||||
overrideBodyTypes?: Record<string, ComponentType>;
|
||||
overrideEventTypes?: Record<string, ComponentType>;
|
||||
|
||||
// helper function to access relations for this event
|
||||
getRelationsForEvent?: GetRelationsForEvent;
|
||||
@@ -64,7 +64,9 @@ export interface IOperableEventTile {
|
||||
getEventTileOps(): IEventTileOps | null;
|
||||
}
|
||||
|
||||
const baseBodyTypes = new Map<string, typeof React.Component>([
|
||||
type ComponentType = React.ComponentType<IBodyProps & React.RefAttributes<any>>;
|
||||
|
||||
const baseBodyTypes = new Map<string, ComponentType>([
|
||||
[MsgType.Text, TextualBody],
|
||||
[MsgType.Notice, TextualBody],
|
||||
[MsgType.Emote, TextualBody],
|
||||
@@ -73,7 +75,7 @@ const baseBodyTypes = new Map<string, typeof React.Component>([
|
||||
[MsgType.Audio, MVoiceOrAudioBody],
|
||||
[MsgType.Video, MVideoBody],
|
||||
]);
|
||||
const baseEvTypes = new Map<string, React.ComponentType<IBodyProps>>([
|
||||
const baseEvTypes = new Map<string, ComponentType>([
|
||||
[EventType.Sticker, MStickerBody],
|
||||
[M_POLL_START.name, MPollBody],
|
||||
[M_POLL_START.altName, MPollBody],
|
||||
@@ -84,10 +86,10 @@ const baseEvTypes = new Map<string, React.ComponentType<IBodyProps>>([
|
||||
]);
|
||||
|
||||
export default class MessageEvent extends React.Component<IProps> implements IMediaBody, IOperableEventTile {
|
||||
private body: React.RefObject<React.Component | IOperableEventTile> = createRef();
|
||||
private body: React.RefObject<ComponentType | IOperableEventTile> = createRef();
|
||||
private mediaHelper?: MediaEventHelper;
|
||||
private bodyTypes = new Map<string, typeof React.Component>(baseBodyTypes.entries());
|
||||
private evTypes = new Map<string, React.ComponentType<IBodyProps>>(baseEvTypes.entries());
|
||||
private bodyTypes = new Map<string, ComponentType>(baseBodyTypes.entries());
|
||||
private evTypes = new Map<string, ComponentType>(baseEvTypes.entries());
|
||||
|
||||
public static contextType = MatrixClientContext;
|
||||
public context!: React.ContextType<typeof MatrixClientContext>;
|
||||
@@ -121,12 +123,12 @@ export default class MessageEvent extends React.Component<IProps> implements IMe
|
||||
}
|
||||
|
||||
private updateComponentMaps(): void {
|
||||
this.bodyTypes = new Map<string, typeof React.Component>(baseBodyTypes.entries());
|
||||
this.bodyTypes = new Map<string, ComponentType>(baseBodyTypes.entries());
|
||||
for (const [bodyType, bodyComponent] of Object.entries(this.props.overrideBodyTypes ?? {})) {
|
||||
this.bodyTypes.set(bodyType, bodyComponent);
|
||||
}
|
||||
|
||||
this.evTypes = new Map<string, React.ComponentType<IBodyProps>>(baseEvTypes.entries());
|
||||
this.evTypes = new Map<string, ComponentType>(baseEvTypes.entries());
|
||||
for (const [evType, evComponent] of Object.entries(this.props.overrideEventTypes ?? {})) {
|
||||
this.evTypes.set(evType, evComponent);
|
||||
}
|
||||
@@ -156,7 +158,7 @@ export default class MessageEvent extends React.Component<IProps> implements IMe
|
||||
const content = this.props.mxEvent.getContent();
|
||||
const type = this.props.mxEvent.getType();
|
||||
const msgtype = content.msgtype;
|
||||
let BodyType: React.ComponentType<IBodyProps> = RedactedBody;
|
||||
let BodyType: ComponentType = RedactedBody;
|
||||
if (!this.props.mxEvent.isRedacted()) {
|
||||
// only resolve BodyType if event is not redacted
|
||||
if (this.props.mxEvent.isDecryptionFailure()) {
|
||||
|
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { SyntheticEvent } from "react";
|
||||
import React, { forwardRef, SyntheticEvent } from "react";
|
||||
import classNames from "classnames";
|
||||
import { MatrixEvent, MatrixEventEvent, Relations, RelationsEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { uniqBy } from "lodash";
|
||||
@@ -35,7 +35,7 @@ const MAX_ITEMS_WHEN_LIMITED = 8;
|
||||
|
||||
export const REACTION_SHORTCODE_KEY = new UnstableValue("shortcode", "com.beeper.reaction.shortcode");
|
||||
|
||||
const ReactButton: React.FC<IProps> = ({ mxEvent, reactions }) => {
|
||||
const ReactButton: React.FC<Omit<IProps, "context">> = ({ mxEvent, reactions }) => {
|
||||
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
|
||||
|
||||
let contextMenu: JSX.Element | undefined;
|
||||
@@ -74,6 +74,7 @@ interface IProps {
|
||||
mxEvent: MatrixEvent;
|
||||
// The Relations model from the JS SDK for reactions to `mxEvent`
|
||||
reactions?: Relations | null | undefined;
|
||||
context: React.ContextType<typeof RoomContext>;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
@@ -81,13 +82,9 @@ interface IState {
|
||||
showAll: boolean;
|
||||
}
|
||||
|
||||
export default class ReactionsRow extends React.PureComponent<IProps, IState> {
|
||||
public static contextType = RoomContext;
|
||||
public context!: React.ContextType<typeof RoomContext>;
|
||||
|
||||
public constructor(props: IProps, context: React.ContextType<typeof RoomContext>) {
|
||||
super(props, context);
|
||||
this.context = context;
|
||||
class ReactionsRow extends React.PureComponent<IProps, IState> {
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
myReactions: this.getMyReactions(),
|
||||
@@ -151,7 +148,7 @@ export default class ReactionsRow extends React.PureComponent<IProps, IState> {
|
||||
if (!reactions) {
|
||||
return null;
|
||||
}
|
||||
const userId = this.context.room?.client.getUserId();
|
||||
const userId = this.props.context.room?.client.getUserId();
|
||||
if (!userId) return null;
|
||||
const myReactions = reactions.getAnnotationsBySender()?.[userId];
|
||||
if (!myReactions) {
|
||||
@@ -202,8 +199,8 @@ export default class ReactionsRow extends React.PureComponent<IProps, IState> {
|
||||
myReactionEvent={myReactionEvent}
|
||||
customReactionImagesEnabled={customReactionImagesEnabled}
|
||||
disabled={
|
||||
!this.context.canReact ||
|
||||
(myReactionEvent && !myReactionEvent.isRedacted() && !this.context.canSelfRedact)
|
||||
!this.props.context.canReact ||
|
||||
(myReactionEvent && !myReactionEvent.isRedacted() && !this.props.context.canSelfRedact)
|
||||
}
|
||||
/>
|
||||
);
|
||||
@@ -226,7 +223,7 @@ export default class ReactionsRow extends React.PureComponent<IProps, IState> {
|
||||
}
|
||||
|
||||
let addReactionButton: JSX.Element | undefined;
|
||||
if (this.context.canReact) {
|
||||
if (this.props.context.canReact) {
|
||||
addReactionButton = <ReactButton mxEvent={mxEvent} reactions={reactions} />;
|
||||
}
|
||||
|
||||
@@ -239,3 +236,7 @@ export default class ReactionsRow extends React.PureComponent<IProps, IState> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default forwardRef<ReactionsRow, Omit<IProps, "context">>((props, ref) => (
|
||||
<RoomContext.Consumer>{(context) => <ReactionsRow {...props} context={context} ref={ref} />}</RoomContext.Consumer>
|
||||
));
|
||||
|
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { createRef, SyntheticEvent, MouseEvent } from "react";
|
||||
import React, { createRef, SyntheticEvent, MouseEvent, forwardRef } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { MsgType } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
@@ -51,6 +51,10 @@ import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
|
||||
const MAX_HIGHLIGHT_LENGTH = 4096;
|
||||
|
||||
interface Props extends IBodyProps {
|
||||
context: React.ContextType<typeof RoomContext>;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
// the URLs (if any) to be previewed with a LinkPreviewWidget inside this TextualBody.
|
||||
links: string[];
|
||||
@@ -59,17 +63,14 @@ interface IState {
|
||||
widgetHidden: boolean;
|
||||
}
|
||||
|
||||
export default class TextualBody extends React.Component<IBodyProps, IState> {
|
||||
class TextualBody extends React.Component<Props, IState> {
|
||||
private readonly contentRef = createRef<HTMLSpanElement>();
|
||||
|
||||
private unmounted = false;
|
||||
private pills: Element[] = [];
|
||||
private tooltips: Element[] = [];
|
||||
|
||||
public static contextType = RoomContext;
|
||||
public context!: React.ContextType<typeof RoomContext>;
|
||||
|
||||
public constructor(props: IBodyProps) {
|
||||
public constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
@@ -429,7 +430,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
|
||||
dis.dispatch({
|
||||
action: Action.ComposerInsert,
|
||||
userId: mxEvent.getSender(),
|
||||
timelineRenderingType: this.context.timelineRenderingType,
|
||||
timelineRenderingType: this.props.context.timelineRenderingType,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -659,3 +660,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default forwardRef<TextualBody, Omit<Props, "context">>((props, ref) => (
|
||||
<RoomContext.Consumer>{(context) => <TextualBody {...props} context={context} ref={ref} />}</RoomContext.Consumer>
|
||||
));
|
||||
|
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { forwardRef } from "react";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import RoomContext from "../../../contexts/RoomContext";
|
||||
@@ -23,19 +23,22 @@ import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
|
||||
interface IProps {
|
||||
mxEvent: MatrixEvent;
|
||||
context: React.ContextType<typeof RoomContext>;
|
||||
}
|
||||
|
||||
export default class TextualEvent extends React.Component<IProps> {
|
||||
public static contextType = RoomContext;
|
||||
|
||||
class TextualEvent extends React.Component<IProps> {
|
||||
public render(): React.ReactNode {
|
||||
const text = TextForEvent.textForEvent(
|
||||
this.props.mxEvent,
|
||||
MatrixClientPeg.safeGet(),
|
||||
true,
|
||||
this.context?.showHiddenEvents,
|
||||
this.props.context?.showHiddenEvents,
|
||||
);
|
||||
if (!text) return null;
|
||||
return <div className="mx_TextualEvent">{text}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export default forwardRef<TextualEvent, Omit<IProps, "context">>((props, ref) => (
|
||||
<RoomContext.Consumer>{(context) => <TextualEvent {...props} context={context} ref={ref} />}</RoomContext.Consumer>
|
||||
));
|
||||
|
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { forwardRef } from "react";
|
||||
import {
|
||||
IEventRelation,
|
||||
MatrixEvent,
|
||||
@@ -59,6 +59,7 @@ interface IProps {
|
||||
timelineRenderingType?: TimelineRenderingType;
|
||||
showComposer?: boolean;
|
||||
composerRelation?: IEventRelation;
|
||||
context: React.ContextType<typeof RoomContext>;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
@@ -75,12 +76,10 @@ interface IState {
|
||||
showReadReceipts?: boolean;
|
||||
}
|
||||
|
||||
export default class TimelineCard extends React.Component<IProps, IState> {
|
||||
public static contextType = RoomContext;
|
||||
|
||||
class TimelineCard extends React.Component<IProps, IState> {
|
||||
private dispatcherRef?: string;
|
||||
private layoutWatcherRef?: string;
|
||||
private timelinePanel = React.createRef<TimelinePanel>();
|
||||
private timelinePanel = React.createRef<React.ComponentRef<typeof TimelinePanel>>();
|
||||
private card = React.createRef<HTMLDivElement>();
|
||||
private readReceiptsSettingWatcher: string | undefined;
|
||||
|
||||
@@ -224,7 +223,7 @@ export default class TimelineCard extends React.Component<IProps, IState> {
|
||||
<RoomContext.Provider
|
||||
value={{
|
||||
...this.context,
|
||||
timelineRenderingType: this.props.timelineRenderingType ?? this.context.timelineRenderingType,
|
||||
timelineRenderingType: this.props.timelineRenderingType ?? this.props.context.timelineRenderingType,
|
||||
liveTimeline: this.props.timelineSet?.getLiveTimeline(),
|
||||
narrow: this.state.narrow,
|
||||
}}
|
||||
@@ -246,7 +245,7 @@ export default class TimelineCard extends React.Component<IProps, IState> {
|
||||
manageReadMarkers={false} // No RM support in the TimelineCard
|
||||
sendReadReceiptOnLoad={true}
|
||||
timelineSet={this.props.timelineSet}
|
||||
showUrlPreview={this.context.showUrlPreview}
|
||||
showUrlPreview={this.props.context.showUrlPreview}
|
||||
// The right panel timeline (and therefore threads) don't support IRC layout at this time
|
||||
layout={this.state.layout === Layout.Bubble ? Layout.Bubble : Layout.Group}
|
||||
hideThreadedMessages={false}
|
||||
@@ -281,3 +280,7 @@ export default class TimelineCard extends React.Component<IProps, IState> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default forwardRef<TimelineCard, Omit<IProps, "context">>((props, ref) => (
|
||||
<RoomContext.Consumer>{(context) => <TimelineCard {...props} context={context} ref={ref} />}</RoomContext.Consumer>
|
||||
));
|
||||
|
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { createRef, KeyboardEvent } from "react";
|
||||
import React, { createRef, forwardRef, KeyboardEvent } from "react";
|
||||
import classNames from "classnames";
|
||||
import { flatMap } from "lodash";
|
||||
import { Room } from "matrix-js-sdk/src/matrix";
|
||||
@@ -38,6 +38,7 @@ interface IProps {
|
||||
selection: ISelectionRange;
|
||||
// The room in which we're autocompleting
|
||||
room: Room;
|
||||
context: React.ContextType<typeof RoomContext>;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
@@ -49,14 +50,12 @@ interface IState {
|
||||
forceComplete: boolean;
|
||||
}
|
||||
|
||||
export default class Autocomplete extends React.PureComponent<IProps, IState> {
|
||||
export class Autocomplete extends React.PureComponent<IProps, IState> {
|
||||
public autocompleter?: Autocompleter;
|
||||
public queryRequested?: string;
|
||||
public debounceCompletionsRequest?: number;
|
||||
private containerRef = createRef<HTMLDivElement>();
|
||||
|
||||
public static contextType = RoomContext;
|
||||
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
@@ -80,7 +79,7 @@ export default class Autocomplete extends React.PureComponent<IProps, IState> {
|
||||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
this.autocompleter = new Autocompleter(this.props.room, this.context.timelineRenderingType);
|
||||
this.autocompleter = new Autocompleter(this.props.room, this.props.context.timelineRenderingType);
|
||||
this.applyNewProps();
|
||||
}
|
||||
|
||||
@@ -320,3 +319,7 @@ export default class Autocomplete extends React.PureComponent<IProps, IState> {
|
||||
) : null;
|
||||
}
|
||||
}
|
||||
|
||||
export default forwardRef<Autocomplete, Omit<IProps, "context">>((props, ref) => (
|
||||
<RoomContext.Consumer>{(context) => <Autocomplete {...props} context={context} ref={ref} />}</RoomContext.Consumer>
|
||||
));
|
||||
|
@@ -123,7 +123,7 @@ interface IState {
|
||||
|
||||
export default class BasicMessageEditor extends React.Component<IProps, IState> {
|
||||
public readonly editorRef = createRef<HTMLDivElement>();
|
||||
private autocompleteRef = createRef<Autocomplete>();
|
||||
private autocompleteRef = createRef<React.ComponentRef<typeof Autocomplete>>();
|
||||
private formatBarRef = createRef<MessageComposerFormatBar>();
|
||||
|
||||
private modifiedFlag = false;
|
||||
|
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { createRef, KeyboardEvent } from "react";
|
||||
import React, { createRef, forwardRef, KeyboardEvent } from "react";
|
||||
import classNames from "classnames";
|
||||
import { EventStatus, MatrixEvent, Room, MsgType } from "matrix-js-sdk/src/matrix";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
@@ -122,28 +122,25 @@ export function createEditContent(
|
||||
interface IEditMessageComposerProps extends MatrixClientProps {
|
||||
editState: EditorStateTransfer;
|
||||
className?: string;
|
||||
context: React.ContextType<typeof RoomContext>;
|
||||
}
|
||||
interface IState {
|
||||
saveDisabled: boolean;
|
||||
}
|
||||
|
||||
class EditMessageComposer extends React.Component<IEditMessageComposerProps, IState> {
|
||||
public static contextType = RoomContext;
|
||||
public context!: React.ContextType<typeof RoomContext>;
|
||||
|
||||
private readonly editorRef = createRef<BasicMessageComposer>();
|
||||
private readonly dispatcherRef: string;
|
||||
private readonly replyToEvent?: MatrixEvent;
|
||||
private model!: EditorModel;
|
||||
|
||||
public constructor(props: IEditMessageComposerProps, context: React.ContextType<typeof RoomContext>) {
|
||||
public constructor(props: IEditMessageComposerProps) {
|
||||
super(props);
|
||||
this.context = context; // otherwise React will only set it prior to render due to type def above
|
||||
|
||||
const isRestored = this.createEditorModel();
|
||||
const ev = this.props.editState.getEvent();
|
||||
|
||||
this.replyToEvent = ev.replyEventId ? this.context.room?.findEventById(ev.replyEventId) : undefined;
|
||||
this.replyToEvent = ev.replyEventId ? this.props.context.room?.findEventById(ev.replyEventId) : undefined;
|
||||
|
||||
const editContent = createEditContent(this.model, ev, this.replyToEvent);
|
||||
this.state = {
|
||||
@@ -155,10 +152,10 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
|
||||
}
|
||||
|
||||
private getRoom(): Room {
|
||||
if (!this.context.room) {
|
||||
if (!this.props.context.room) {
|
||||
throw new Error(`Cannot render without room`);
|
||||
}
|
||||
return this.context.room;
|
||||
return this.props.context.room;
|
||||
}
|
||||
|
||||
private onKeyDown = (event: KeyboardEvent): void => {
|
||||
@@ -191,7 +188,7 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
|
||||
dis.dispatch({
|
||||
action: Action.EditEvent,
|
||||
event: previousEvent,
|
||||
timelineRenderingType: this.context.timelineRenderingType,
|
||||
timelineRenderingType: this.props.context.timelineRenderingType,
|
||||
});
|
||||
event.preventDefault();
|
||||
}
|
||||
@@ -211,7 +208,7 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
|
||||
dis.dispatch({
|
||||
action: Action.EditEvent,
|
||||
event: nextEvent,
|
||||
timelineRenderingType: this.context.timelineRenderingType,
|
||||
timelineRenderingType: this.props.context.timelineRenderingType,
|
||||
});
|
||||
} else {
|
||||
this.cancelEdit();
|
||||
@@ -230,16 +227,16 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
|
||||
dis.dispatch({
|
||||
action: Action.EditEvent,
|
||||
event: null,
|
||||
timelineRenderingType: this.context.timelineRenderingType,
|
||||
timelineRenderingType: this.props.context.timelineRenderingType,
|
||||
});
|
||||
dis.dispatch({
|
||||
action: Action.FocusSendMessageComposer,
|
||||
context: this.context.timelineRenderingType,
|
||||
context: this.props.context.timelineRenderingType,
|
||||
});
|
||||
}
|
||||
|
||||
private get editorRoomKey(): string {
|
||||
return editorRoomKey(this.props.editState.getEvent().getRoomId()!, this.context.timelineRenderingType);
|
||||
return editorRoomKey(this.props.editState.getEvent().getRoomId()!, this.props.context.timelineRenderingType);
|
||||
}
|
||||
|
||||
private get editorStateKey(): string {
|
||||
@@ -247,7 +244,7 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
|
||||
}
|
||||
|
||||
private get events(): MatrixEvent[] {
|
||||
const liveTimelineEvents = this.context.liveTimeline?.getEvents();
|
||||
const liveTimelineEvents = this.props.context.liveTimeline?.getEvents();
|
||||
const room = this.getRoom();
|
||||
if (!liveTimelineEvents || !room) return [];
|
||||
const pendingEvents = room.getPendingEvents();
|
||||
@@ -368,7 +365,7 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
|
||||
// re-focus the composer after QuestionDialog is closed
|
||||
dis.dispatch({
|
||||
action: Action.FocusAComposer,
|
||||
context: this.context.timelineRenderingType,
|
||||
context: this.props.context.timelineRenderingType,
|
||||
});
|
||||
// if !sendAnyway bail to let the user edit the composer and try again
|
||||
if (!sendAnyway) return;
|
||||
@@ -462,7 +459,7 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
|
||||
if (!this.editorRef.current) return;
|
||||
|
||||
if (payload.action === Action.ComposerInsert) {
|
||||
if (payload.timelineRenderingType !== this.context.timelineRenderingType) return;
|
||||
if (payload.timelineRenderingType !== this.props.context.timelineRenderingType) return;
|
||||
if (payload.composerType !== ComposerType.Edit) return;
|
||||
|
||||
if (payload.userId) {
|
||||
@@ -506,4 +503,10 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
|
||||
}
|
||||
|
||||
const EditMessageComposerWithMatrixClient = withMatrixClientHOC(EditMessageComposer);
|
||||
export default EditMessageComposerWithMatrixClient;
|
||||
export default forwardRef<EditMessageComposer, Omit<IEditMessageComposerProps, "context" | "mxClient">>(
|
||||
(props, ref) => (
|
||||
<RoomContext.Consumer>
|
||||
{(context) => <EditMessageComposerWithMatrixClient {...props} context={context} ref={ref} />}
|
||||
</RoomContext.Consumer>
|
||||
),
|
||||
);
|
||||
|
@@ -266,6 +266,10 @@ interface IState {
|
||||
threadNotification?: NotificationCountType;
|
||||
}
|
||||
|
||||
interface Props extends EventTileProps {
|
||||
context: React.ContextType<typeof RoomContext>;
|
||||
}
|
||||
|
||||
/**
|
||||
* When true, the tile qualifies for some sort of special read receipt.
|
||||
* This could be a 'sending' or 'sent' receipt, for example.
|
||||
@@ -282,7 +286,7 @@ export function isEligibleForSpecialReceipt(event: MatrixEvent): boolean {
|
||||
}
|
||||
|
||||
// MUST be rendered within a RoomContext with a set timelineRenderingType
|
||||
export class UnwrappedEventTile extends React.Component<EventTileProps, IState> {
|
||||
class UnwrappedEventTile extends React.Component<Props, IState> {
|
||||
private suppressReadReceiptAnimation: boolean;
|
||||
private isListeningForReceipts: boolean;
|
||||
private tile = createRef<IEventTileType>();
|
||||
@@ -297,13 +301,10 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||
layout: Layout.Group,
|
||||
};
|
||||
|
||||
public static contextType = RoomContext;
|
||||
public context!: React.ContextType<typeof RoomContext>;
|
||||
|
||||
private unmounted = false;
|
||||
|
||||
public constructor(props: EventTileProps, context: React.ContextType<typeof RoomContext>) {
|
||||
super(props, context);
|
||||
public constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
const thread = this.thread;
|
||||
|
||||
@@ -508,7 +509,10 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.context.timelineRenderingType === TimelineRenderingType.Search && this.props.mxEvent.threadRootId) {
|
||||
if (
|
||||
this.props.context.timelineRenderingType === TimelineRenderingType.Search &&
|
||||
this.props.mxEvent.threadRootId
|
||||
) {
|
||||
if (this.props.highlightLink) {
|
||||
return (
|
||||
<a className="mx_ThreadSummary_icon" href={this.props.highlightLink}>
|
||||
@@ -676,8 +680,8 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||
*/
|
||||
private shouldHighlight(): boolean {
|
||||
if (this.props.forExport) return false;
|
||||
if (this.context.timelineRenderingType === TimelineRenderingType.Notification) return false;
|
||||
if (this.context.timelineRenderingType === TimelineRenderingType.ThreadsList) return false;
|
||||
if (this.props.context.timelineRenderingType === TimelineRenderingType.Notification) return false;
|
||||
if (this.props.context.timelineRenderingType === TimelineRenderingType.ThreadsList) return false;
|
||||
|
||||
const cli = MatrixClientPeg.safeGet();
|
||||
const actions = cli.getPushActionsForEvent(this.props.mxEvent.replacingEvent() || this.props.mxEvent);
|
||||
@@ -701,7 +705,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||
dis.dispatch<ComposerInsertPayload>({
|
||||
action: Action.ComposerInsert,
|
||||
userId: this.props.mxEvent.getSender()!,
|
||||
timelineRenderingType: this.context.timelineRenderingType,
|
||||
timelineRenderingType: this.props.context.timelineRenderingType,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -715,7 +719,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||
highlighted: true,
|
||||
room_id: this.props.mxEvent.getRoomId(),
|
||||
metricsTrigger:
|
||||
this.context.timelineRenderingType === TimelineRenderingType.Search ? "MessageSearch" : undefined,
|
||||
this.props.context.timelineRenderingType === TimelineRenderingType.Search ? "MessageSearch" : undefined,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -923,7 +927,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||
} = getEventDisplayInfo(
|
||||
MatrixClientPeg.safeGet(),
|
||||
this.props.mxEvent,
|
||||
this.context.showHiddenEvents,
|
||||
this.props.context.showHiddenEvents,
|
||||
this.shouldHideEvent(),
|
||||
);
|
||||
const { isQuoteExpanded } = this.state;
|
||||
@@ -958,15 +962,15 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||
|
||||
let isContinuation = this.props.continuation;
|
||||
if (
|
||||
this.context.timelineRenderingType !== TimelineRenderingType.Room &&
|
||||
this.context.timelineRenderingType !== TimelineRenderingType.Search &&
|
||||
this.context.timelineRenderingType !== TimelineRenderingType.Thread &&
|
||||
this.props.context.timelineRenderingType !== TimelineRenderingType.Room &&
|
||||
this.props.context.timelineRenderingType !== TimelineRenderingType.Search &&
|
||||
this.props.context.timelineRenderingType !== TimelineRenderingType.Thread &&
|
||||
this.props.layout !== Layout.Bubble
|
||||
) {
|
||||
isContinuation = false;
|
||||
}
|
||||
|
||||
const isRenderingNotification = this.context.timelineRenderingType === TimelineRenderingType.Notification;
|
||||
const isRenderingNotification = this.props.context.timelineRenderingType === TimelineRenderingType.Notification;
|
||||
|
||||
const isEditing = !!this.props.editState;
|
||||
const classes = classNames({
|
||||
@@ -990,7 +994,8 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||
mx_EventTile_emote: msgtype === MsgType.Emote,
|
||||
mx_EventTile_noSender: this.props.hideSender,
|
||||
mx_EventTile_clamp:
|
||||
this.context.timelineRenderingType === TimelineRenderingType.ThreadsList || isRenderingNotification,
|
||||
this.props.context.timelineRenderingType === TimelineRenderingType.ThreadsList ||
|
||||
isRenderingNotification,
|
||||
mx_EventTile_noBubble: noBubbleEvent,
|
||||
});
|
||||
|
||||
@@ -1020,8 +1025,8 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||
avatarSize = "14px";
|
||||
needsSenderProfile = false;
|
||||
} else if (
|
||||
this.context.timelineRenderingType === TimelineRenderingType.ThreadsList ||
|
||||
(this.context.timelineRenderingType === TimelineRenderingType.Thread && !this.props.continuation)
|
||||
this.props.context.timelineRenderingType === TimelineRenderingType.ThreadsList ||
|
||||
(this.props.context.timelineRenderingType === TimelineRenderingType.Thread && !this.props.continuation)
|
||||
) {
|
||||
avatarSize = "32px";
|
||||
needsSenderProfile = true;
|
||||
@@ -1032,7 +1037,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||
avatarSize = "14px";
|
||||
needsSenderProfile = true;
|
||||
} else if (
|
||||
(this.props.continuation && this.context.timelineRenderingType !== TimelineRenderingType.File) ||
|
||||
(this.props.continuation && this.props.context.timelineRenderingType !== TimelineRenderingType.File) ||
|
||||
eventType === EventType.CallInvite ||
|
||||
ElementCall.CALL_EVENT_TYPE.matches(eventType)
|
||||
) {
|
||||
@@ -1058,7 +1063,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||
const viewUserOnClick =
|
||||
!this.props.inhibitInteraction &&
|
||||
![TimelineRenderingType.ThreadsList, TimelineRenderingType.Notification].includes(
|
||||
this.context.timelineRenderingType,
|
||||
this.props.context.timelineRenderingType,
|
||||
);
|
||||
avatar = (
|
||||
<div className="mx_EventTile_avatar">
|
||||
@@ -1074,13 +1079,13 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||
|
||||
if (needsSenderProfile && this.props.hideSender !== true) {
|
||||
if (
|
||||
this.context.timelineRenderingType === TimelineRenderingType.Room ||
|
||||
this.context.timelineRenderingType === TimelineRenderingType.Search ||
|
||||
this.context.timelineRenderingType === TimelineRenderingType.Pinned ||
|
||||
this.context.timelineRenderingType === TimelineRenderingType.Thread
|
||||
this.props.context.timelineRenderingType === TimelineRenderingType.Room ||
|
||||
this.props.context.timelineRenderingType === TimelineRenderingType.Search ||
|
||||
this.props.context.timelineRenderingType === TimelineRenderingType.Pinned ||
|
||||
this.props.context.timelineRenderingType === TimelineRenderingType.Thread
|
||||
) {
|
||||
sender = <SenderProfile onClick={this.onSenderProfileClick} mxEvent={this.props.mxEvent} />;
|
||||
} else if (this.context.timelineRenderingType === TimelineRenderingType.ThreadsList) {
|
||||
} else if (this.props.context.timelineRenderingType === TimelineRenderingType.ThreadsList) {
|
||||
sender = <SenderProfile mxEvent={this.props.mxEvent} withTooltip />;
|
||||
} else {
|
||||
sender = <SenderProfile mxEvent={this.props.mxEvent} />;
|
||||
@@ -1113,7 +1118,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||
|
||||
// Thread panel shows the timestamp of the last reply in that thread
|
||||
let ts =
|
||||
this.context.timelineRenderingType !== TimelineRenderingType.ThreadsList
|
||||
this.props.context.timelineRenderingType !== TimelineRenderingType.ThreadsList
|
||||
? this.props.mxEvent.getTs()
|
||||
: this.state.thread?.replyToEvent?.getTs();
|
||||
if (typeof ts !== "number") {
|
||||
@@ -1123,7 +1128,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||
|
||||
const messageTimestamp = (
|
||||
<MessageTimestamp
|
||||
showRelative={this.context.timelineRenderingType === TimelineRenderingType.ThreadsList}
|
||||
showRelative={this.props.context.timelineRenderingType === TimelineRenderingType.ThreadsList}
|
||||
showTwelveHour={this.props.isTwelveHour}
|
||||
ts={ts}
|
||||
receivedTs={getLateEventInfo(this.props.mxEvent)?.received_ts}
|
||||
@@ -1180,7 +1185,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||
|
||||
let replyChain: JSX.Element | undefined;
|
||||
if (
|
||||
haveRendererForEvent(this.props.mxEvent, MatrixClientPeg.safeGet(), this.context.showHiddenEvents) &&
|
||||
haveRendererForEvent(this.props.mxEvent, MatrixClientPeg.safeGet(), this.props.context.showHiddenEvents) &&
|
||||
shouldDisplayReply(this.props.mxEvent)
|
||||
) {
|
||||
replyChain = (
|
||||
@@ -1202,7 +1207,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||
// Use `getSender()` because searched events might not have a proper `sender`.
|
||||
const isOwnEvent = this.props.mxEvent?.getSender() === MatrixClientPeg.safeGet().getUserId();
|
||||
|
||||
switch (this.context.timelineRenderingType) {
|
||||
switch (this.props.context.timelineRenderingType) {
|
||||
case TimelineRenderingType.Thread: {
|
||||
return React.createElement(
|
||||
this.props.as || "li",
|
||||
@@ -1242,7 +1247,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||
onHeightChanged: () => this.props.onHeightChanged,
|
||||
permalinkCreator: this.props.permalinkCreator!,
|
||||
},
|
||||
this.context.showHiddenEvents,
|
||||
this.props.context.showHiddenEvents,
|
||||
)}
|
||||
{actionBar}
|
||||
<a href={permalink} onClick={this.onPermalinkClicked}>
|
||||
@@ -1268,7 +1273,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||
"aria-atomic": "true",
|
||||
"data-scroll-tokens": scrollToken,
|
||||
"data-layout": this.props.layout,
|
||||
"data-shape": this.context.timelineRenderingType,
|
||||
"data-shape": this.props.context.timelineRenderingType,
|
||||
"data-self": isOwnEvent,
|
||||
"data-has-reply": !!replyChain,
|
||||
"onMouseEnter": () => this.setState({ hover: true }),
|
||||
@@ -1277,7 +1282,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||
const target = ev.currentTarget as HTMLElement;
|
||||
let index = -1;
|
||||
if (target.parentElement) index = Array.from(target.parentElement.children).indexOf(target);
|
||||
switch (this.context.timelineRenderingType) {
|
||||
switch (this.props.context.timelineRenderingType) {
|
||||
case TimelineRenderingType.Notification:
|
||||
this.viewInRoom(ev);
|
||||
break;
|
||||
@@ -1333,7 +1338,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||
</div>
|
||||
{this.renderThreadPanelSummary()}
|
||||
</div>
|
||||
{this.context.timelineRenderingType === TimelineRenderingType.ThreadsList && (
|
||||
{this.props.context.timelineRenderingType === TimelineRenderingType.ThreadsList && (
|
||||
<EventTileThreadToolbar
|
||||
viewInRoom={this.viewInRoom}
|
||||
copyLinkToThread={this.copyLinkToThread}
|
||||
@@ -1371,7 +1376,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||
onHeightChanged: this.props.onHeightChanged,
|
||||
permalinkCreator: this.props.permalinkCreator,
|
||||
},
|
||||
this.context.showHiddenEvents,
|
||||
this.props.context.showHiddenEvents,
|
||||
)}
|
||||
</div>,
|
||||
<a
|
||||
@@ -1419,7 +1424,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||
{groupPadlock}
|
||||
{replyChain}
|
||||
{renderTile(
|
||||
this.context.timelineRenderingType,
|
||||
this.props.context.timelineRenderingType,
|
||||
{
|
||||
...this.props,
|
||||
|
||||
@@ -1434,7 +1439,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||
onHeightChanged: this.props.onHeightChanged,
|
||||
permalinkCreator: this.props.permalinkCreator,
|
||||
},
|
||||
this.context.showHiddenEvents,
|
||||
this.props.context.showHiddenEvents,
|
||||
)}
|
||||
{actionBar}
|
||||
{this.props.layout === Layout.IRC && (
|
||||
@@ -1463,7 +1468,9 @@ const SafeEventTile = forwardRef<UnwrappedEventTile, EventTileProps>((props, ref
|
||||
return (
|
||||
<>
|
||||
<TileErrorBoundary mxEvent={props.mxEvent} layout={props.layout ?? Layout.Group}>
|
||||
<UnwrappedEventTile ref={ref} {...props} />
|
||||
<RoomContext.Consumer>
|
||||
{(context) => <UnwrappedEventTile {...props} ref={ref} context={context} />}
|
||||
</RoomContext.Consumer>
|
||||
</TileErrorBoundary>
|
||||
</>
|
||||
);
|
||||
|
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { FC, useState, useMemo, useCallback } from "react";
|
||||
import React, { FC, useState, useMemo, useCallback, forwardRef } from "react";
|
||||
import classNames from "classnames";
|
||||
import { throttle } from "lodash";
|
||||
import { RoomStateEvent, ISearchResults } from "matrix-js-sdk/src/matrix";
|
||||
@@ -470,7 +470,7 @@ export interface ISearchInfo {
|
||||
count?: number;
|
||||
}
|
||||
|
||||
export interface IProps {
|
||||
interface IProps {
|
||||
room: Room;
|
||||
oobData?: IOOBData;
|
||||
inRoom: boolean;
|
||||
@@ -478,7 +478,7 @@ export interface IProps {
|
||||
onInviteClick: (() => void) | null;
|
||||
onForgetClick: (() => void) | null;
|
||||
onAppsClick: (() => void) | null;
|
||||
e2eStatus: E2EStatus;
|
||||
e2eStatus?: E2EStatus;
|
||||
appsShown: boolean;
|
||||
searchInfo?: ISearchInfo;
|
||||
excludedRightPanelPhaseButtons?: Array<RightPanelPhases>;
|
||||
@@ -487,6 +487,7 @@ export interface IProps {
|
||||
viewingCall: boolean;
|
||||
activeCall: Call | null;
|
||||
additionalButtons?: ViewRoomOpts["buttons"];
|
||||
context: React.ContextType<typeof RoomContext>;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
@@ -498,7 +499,7 @@ interface IState {
|
||||
/**
|
||||
* @deprecated use `src/components/views/rooms/RoomHeader.tsx` instead
|
||||
*/
|
||||
export default class RoomHeader extends React.Component<IProps, IState> {
|
||||
class LegacyRoomHeader extends React.Component<IProps, IState> {
|
||||
public static defaultProps: Partial<IProps> = {
|
||||
inRoom: false,
|
||||
excludedRightPanelPhaseButtons: [],
|
||||
@@ -506,13 +507,11 @@ export default class RoomHeader extends React.Component<IProps, IState> {
|
||||
enableRoomOptionsMenu: true,
|
||||
};
|
||||
|
||||
public static contextType = RoomContext;
|
||||
public context!: React.ContextType<typeof RoomContext>;
|
||||
private readonly client = this.props.room.client;
|
||||
private readonly featureAskToJoinWatcher: string;
|
||||
|
||||
public constructor(props: IProps, context: IState) {
|
||||
super(props, context);
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
const notiStore = RoomNotificationStateStore.instance.getRoomState(props.room);
|
||||
notiStore.on(NotificationStateEvents.Update, this.onNotificationUpdate);
|
||||
this.state = {
|
||||
@@ -590,7 +589,7 @@ export default class RoomHeader extends React.Component<IProps, IState> {
|
||||
private renderButtons(isVideoRoom: boolean): React.ReactNode {
|
||||
const startButtons: JSX.Element[] = [];
|
||||
|
||||
if (!this.props.viewingCall && this.props.inRoom && !this.context.tombstone) {
|
||||
if (!this.props.viewingCall && this.props.inRoom && !this.props.context.tombstone) {
|
||||
startButtons.push(<CallButtons key="calls" room={this.props.room} />);
|
||||
}
|
||||
|
||||
@@ -866,3 +865,9 @@ export default class RoomHeader extends React.Component<IProps, IState> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default forwardRef<LegacyRoomHeader, Omit<IProps, "context">>((props, ref) => (
|
||||
<RoomContext.Consumer>
|
||||
{(context) => <LegacyRoomHeader {...props} context={context} ref={ref} />}
|
||||
</RoomContext.Consumer>
|
||||
));
|
||||
|
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { createRef, ReactNode } from "react";
|
||||
import React, { createRef, forwardRef, ReactNode } from "react";
|
||||
import classNames from "classnames";
|
||||
import {
|
||||
IEventRelation,
|
||||
@@ -44,7 +44,7 @@ import { RecordingState } from "../../../audio/VoiceRecording";
|
||||
import Tooltip, { Alignment } from "../elements/Tooltip";
|
||||
import ResizeNotifier from "../../../utils/ResizeNotifier";
|
||||
import { E2EStatus } from "../../../utils/ShieldUtils";
|
||||
import SendMessageComposer, { SendMessageComposer as SendMessageComposerClass } from "./SendMessageComposer";
|
||||
import SendMessageComposer from "./SendMessageComposer";
|
||||
import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import EditorModel from "../../../editor/model";
|
||||
@@ -92,6 +92,7 @@ interface IProps extends MatrixClientProps {
|
||||
relation?: IEventRelation;
|
||||
e2eStatus?: E2EStatus;
|
||||
compact?: boolean;
|
||||
context: React.ContextType<typeof RoomContext>;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
@@ -113,16 +114,13 @@ interface IState {
|
||||
export class MessageComposer extends React.Component<IProps, IState> {
|
||||
private tooltipId = `mx_MessageComposer_${Math.random()}`;
|
||||
private dispatcherRef?: string;
|
||||
private messageComposerInput = createRef<SendMessageComposerClass>();
|
||||
private voiceRecordingButton = createRef<VoiceRecordComposerTile>();
|
||||
private messageComposerInput = createRef<React.ComponentRef<typeof SendMessageComposer>>();
|
||||
private voiceRecordingButton = createRef<React.ComponentRef<typeof VoiceRecordComposerTile>>();
|
||||
private ref: React.RefObject<HTMLDivElement> = createRef();
|
||||
private instanceId: number;
|
||||
|
||||
private _voiceRecording: Optional<VoiceMessageRecording>;
|
||||
|
||||
public static contextType = RoomContext;
|
||||
public context!: React.ContextType<typeof RoomContext>;
|
||||
|
||||
public static defaultProps = {
|
||||
compact: false,
|
||||
showVoiceBroadcastButton: false,
|
||||
@@ -189,7 +187,7 @@ export class MessageComposer extends React.Component<IProps, IState> {
|
||||
|
||||
private onResize = (type: UI_EVENTS, entry: ResizeObserverEntry): void => {
|
||||
if (type === UI_EVENTS.Resize) {
|
||||
const { narrow } = this.context;
|
||||
const { narrow } = this.props.context;
|
||||
this.setState({
|
||||
isMenuOpen: !narrow ? false : this.state.isMenuOpen,
|
||||
isStickerPickerOpen: false,
|
||||
@@ -200,7 +198,7 @@ export class MessageComposer extends React.Component<IProps, IState> {
|
||||
private onAction = (payload: ActionPayload): void => {
|
||||
switch (payload.action) {
|
||||
case "reply_to_event":
|
||||
if (payload.context === this.context.timelineRenderingType) {
|
||||
if (payload.context === this.props.context.timelineRenderingType) {
|
||||
// add a timeout for the reply preview to be rendered, so
|
||||
// that the ScrollPanel listening to the resizeNotifier can
|
||||
// correctly measure it's new height and scroll down to keep
|
||||
@@ -274,7 +272,7 @@ export class MessageComposer extends React.Component<IProps, IState> {
|
||||
private onTombstoneClick = (ev: ButtonEvent): void => {
|
||||
ev.preventDefault();
|
||||
|
||||
const replacementRoomId = this.context.tombstone?.getContent()["replacement_room"];
|
||||
const replacementRoomId = this.props.context.tombstone?.getContent()["replacement_room"];
|
||||
const replacementRoom = MatrixClientPeg.safeGet().getRoom(replacementRoomId);
|
||||
let createEventId: string | undefined;
|
||||
if (replacementRoom) {
|
||||
@@ -282,7 +280,7 @@ export class MessageComposer extends React.Component<IProps, IState> {
|
||||
if (createEvent?.getId()) createEventId = createEvent.getId();
|
||||
}
|
||||
|
||||
const sender = this.context.tombstone?.getSender();
|
||||
const sender = this.props.context.tombstone?.getSender();
|
||||
const viaServers = sender ? [sender.split(":").slice(1).join(":")] : undefined;
|
||||
|
||||
dis.dispatch<ViewRoomPayload>({
|
||||
@@ -324,7 +322,7 @@ export class MessageComposer extends React.Component<IProps, IState> {
|
||||
dis.dispatch<ComposerInsertPayload>({
|
||||
action: Action.ComposerInsert,
|
||||
text: emoji,
|
||||
timelineRenderingType: this.context.timelineRenderingType,
|
||||
timelineRenderingType: this.props.context.timelineRenderingType,
|
||||
});
|
||||
return true;
|
||||
};
|
||||
@@ -345,11 +343,11 @@ export class MessageComposer extends React.Component<IProps, IState> {
|
||||
this.setState({ composerContent: "", initialComposerContent: "" });
|
||||
dis.dispatch({
|
||||
action: Action.ClearAndFocusSendMessageComposer,
|
||||
timelineRenderingType: this.context.timelineRenderingType,
|
||||
timelineRenderingType: this.props.context.timelineRenderingType,
|
||||
});
|
||||
await sendMessage(composerContent, this.state.isRichTextEnabled, {
|
||||
mxClient: this.props.mxClient,
|
||||
roomContext: this.context,
|
||||
roomContext: this.props.context,
|
||||
permalinkCreator,
|
||||
relation,
|
||||
replyToEvent,
|
||||
@@ -467,7 +465,7 @@ export class MessageComposer extends React.Component<IProps, IState> {
|
||||
this.voiceRecordingButton.current?.onRecordStartEndClick();
|
||||
}
|
||||
|
||||
if (this.context.narrow) {
|
||||
if (this.props.context.narrow) {
|
||||
this.toggleButtonMenu();
|
||||
}
|
||||
};
|
||||
@@ -483,7 +481,7 @@ export class MessageComposer extends React.Component<IProps, IState> {
|
||||
const controls: ReactNode[] = [];
|
||||
const menuPosition = this.getMenuPosition();
|
||||
|
||||
const canSendMessages = this.context.canSendMessages && !this.context.tombstone;
|
||||
const canSendMessages = this.props.context.canSendMessages && !this.props.context.tombstone;
|
||||
let composer: ReactNode;
|
||||
if (canSendMessages) {
|
||||
if (this.state.isWysiwygLabEnabled && menuPosition) {
|
||||
@@ -528,8 +526,8 @@ export class MessageComposer extends React.Component<IProps, IState> {
|
||||
replyToEvent={this.props.replyToEvent}
|
||||
/>,
|
||||
);
|
||||
} else if (this.context.tombstone) {
|
||||
const replacementRoomId = this.context.tombstone.getContent()["replacement_room"];
|
||||
} else if (this.props.context.tombstone) {
|
||||
const replacementRoomId = this.props.context.tombstone.getContent()["replacement_room"];
|
||||
|
||||
const continuesLink = replacementRoomId ? (
|
||||
<a
|
||||
@@ -667,4 +665,8 @@ export class MessageComposer extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
const MessageComposerWithMatrixClient = withMatrixClientHOC(MessageComposer);
|
||||
export default MessageComposerWithMatrixClient;
|
||||
export default forwardRef<MessageComposer, Omit<IProps, "context" | "mxClient">>((props, ref) => (
|
||||
<RoomContext.Consumer>
|
||||
{(context) => <MessageComposerWithMatrixClient {...props} context={context} ref={ref} />}
|
||||
</RoomContext.Consumer>
|
||||
));
|
||||
|
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { forwardRef } from "react";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import dis from "../../../dispatcher/dispatcher";
|
||||
@@ -35,11 +35,10 @@ function cancelQuoting(context: TimelineRenderingType): void {
|
||||
interface IProps {
|
||||
permalinkCreator?: RoomPermalinkCreator;
|
||||
replyToEvent?: MatrixEvent;
|
||||
context: React.ContextType<typeof RoomContext>;
|
||||
}
|
||||
|
||||
export default class ReplyPreview extends React.Component<IProps> {
|
||||
public static contextType = RoomContext;
|
||||
|
||||
class ReplyPreview extends React.Component<IProps> {
|
||||
public render(): JSX.Element | null {
|
||||
if (!this.props.replyToEvent) return null;
|
||||
|
||||
@@ -50,7 +49,7 @@ export default class ReplyPreview extends React.Component<IProps> {
|
||||
<span>{_t("composer|replying_title")}</span>
|
||||
<AccessibleButton
|
||||
className="mx_ReplyPreview_header_cancel"
|
||||
onClick={() => cancelQuoting(this.context.timelineRenderingType)}
|
||||
onClick={() => cancelQuoting(this.props.context.timelineRenderingType)}
|
||||
/>
|
||||
</div>
|
||||
<ReplyTile mxEvent={this.props.replyToEvent} permalinkCreator={this.props.permalinkCreator} />
|
||||
@@ -59,3 +58,7 @@ export default class ReplyPreview extends React.Component<IProps> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default forwardRef<ReplyPreview, Omit<IProps, "context">>((props, ref) => (
|
||||
<RoomContext.Consumer>{(context) => <ReplyPreview {...props} context={context} ref={ref} />}</RoomContext.Consumer>
|
||||
));
|
||||
|
@@ -31,7 +31,7 @@ import MFileBody from "../messages/MFileBody";
|
||||
import MemberAvatar from "../avatars/MemberAvatar";
|
||||
import MVoiceMessageBody from "../messages/MVoiceMessageBody";
|
||||
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { renderReplyTile } from "../../../events/EventTileFactory";
|
||||
import { EventTileTypeProps, renderReplyTile } from "../../../events/EventTileFactory";
|
||||
import { GetRelationsForEvent } from "../rooms/EventTile";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
|
||||
@@ -147,13 +147,13 @@ export default class ReplyTile extends React.PureComponent<IProps> {
|
||||
);
|
||||
}
|
||||
|
||||
const msgtypeOverrides: Record<string, typeof React.Component> = {
|
||||
const msgtypeOverrides: Record<string, React.ComponentType<EventTileTypeProps>> = {
|
||||
[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: Record<string, typeof React.Component> = {
|
||||
const evOverrides: Record<string, React.ComponentType<EventTileTypeProps>> = {
|
||||
// Use MImageReplyBody so that the sticker isn't taking up a lot of space
|
||||
[EventType.Sticker]: MImageReplyBody,
|
||||
};
|
||||
|
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { forwardRef } from "react";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
|
||||
@@ -40,17 +40,15 @@ interface IProps {
|
||||
ourEventsIndexes: number[];
|
||||
onHeightChanged?: () => void;
|
||||
permalinkCreator?: RoomPermalinkCreator;
|
||||
context: React.ContextType<typeof RoomContext>;
|
||||
}
|
||||
|
||||
export default class SearchResultTile extends React.Component<IProps> {
|
||||
public static contextType = RoomContext;
|
||||
public context!: React.ContextType<typeof RoomContext>;
|
||||
|
||||
class SearchResultTile extends React.Component<IProps> {
|
||||
// A map of <callId, LegacyCallEventGrouper>
|
||||
private callEventGroupers = new Map<string, LegacyCallEventGrouper>();
|
||||
|
||||
public constructor(props: IProps, context: React.ContextType<typeof RoomContext>) {
|
||||
super(props, context);
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.buildLegacyCallEventGroupers(this.props.timeline);
|
||||
}
|
||||
@@ -79,7 +77,7 @@ export default class SearchResultTile extends React.Component<IProps> {
|
||||
highlights = this.props.searchHighlights;
|
||||
}
|
||||
|
||||
if (haveRendererForEvent(mxEv, cli, this.context?.showHiddenEvents)) {
|
||||
if (haveRendererForEvent(mxEv, cli, this.props.context?.showHiddenEvents)) {
|
||||
// do we need a date separator since the last event?
|
||||
const prevEv = timeline[j - 1];
|
||||
// is this a continuation of the previous message?
|
||||
@@ -90,7 +88,7 @@ export default class SearchResultTile extends React.Component<IProps> {
|
||||
prevEv,
|
||||
mxEv,
|
||||
cli,
|
||||
this.context?.showHiddenEvents,
|
||||
this.props.context?.showHiddenEvents,
|
||||
TimelineRenderingType.Search,
|
||||
);
|
||||
|
||||
@@ -108,7 +106,7 @@ export default class SearchResultTile extends React.Component<IProps> {
|
||||
mxEv,
|
||||
nextEv,
|
||||
cli,
|
||||
this.context?.showHiddenEvents,
|
||||
this.props.context?.showHiddenEvents,
|
||||
TimelineRenderingType.Search,
|
||||
);
|
||||
}
|
||||
@@ -140,3 +138,9 @@ export default class SearchResultTile extends React.Component<IProps> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default forwardRef<SearchResultTile, Omit<IProps, "context">>((props, ref) => (
|
||||
<RoomContext.Consumer>
|
||||
{(context) => <SearchResultTile {...props} context={context} ref={ref} />}
|
||||
</RoomContext.Consumer>
|
||||
));
|
||||
|
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { createRef, KeyboardEvent, SyntheticEvent } from "react";
|
||||
import React, { createRef, forwardRef, KeyboardEvent, SyntheticEvent } from "react";
|
||||
import EMOJI_REGEX from "emojibase-regex";
|
||||
import {
|
||||
IContent,
|
||||
@@ -250,12 +250,10 @@ interface ISendMessageComposerProps extends MatrixClientProps {
|
||||
onChange?(model: EditorModel): void;
|
||||
includeReplyLegacyFallback?: boolean;
|
||||
toggleStickerPickerOpen: () => void;
|
||||
context: React.ContextType<typeof RoomContext>;
|
||||
}
|
||||
|
||||
export class SendMessageComposer extends React.Component<ISendMessageComposerProps> {
|
||||
public static contextType = RoomContext;
|
||||
public context!: React.ContextType<typeof RoomContext>;
|
||||
|
||||
class SendMessageComposer extends React.Component<ISendMessageComposerProps> {
|
||||
private readonly prepareToEncrypt?: DebouncedFunc<() => void>;
|
||||
private readonly editorRef = createRef<BasicMessageComposer>();
|
||||
private model: EditorModel;
|
||||
@@ -267,9 +265,8 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
|
||||
includeReplyLegacyFallback: true,
|
||||
};
|
||||
|
||||
public constructor(props: ISendMessageComposerProps, context: React.ContextType<typeof RoomContext>) {
|
||||
super(props, context);
|
||||
this.context = context; // otherwise React will only set it prior to render due to type def above
|
||||
public constructor(props: ISendMessageComposerProps) {
|
||||
super(props);
|
||||
|
||||
if (this.props.mxClient.isCryptoEnabled() && this.props.mxClient.isRoomEncrypted(this.props.room.roomId)) {
|
||||
this.prepareToEncrypt = throttle(
|
||||
@@ -336,7 +333,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
|
||||
case KeyBindingAction.EditPrevMessage:
|
||||
// selection must be collapsed and caret at start
|
||||
if (this.editorRef.current?.isSelectionCollapsed() && this.editorRef.current?.isCaretAtStart()) {
|
||||
const events = this.context.liveTimeline
|
||||
const events = this.props.context.liveTimeline
|
||||
?.getEvents()
|
||||
.concat(replyingToThread ? [] : this.props.room.getPendingEvents());
|
||||
const editEvent = events
|
||||
@@ -352,17 +349,17 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
|
||||
dis.dispatch({
|
||||
action: Action.EditEvent,
|
||||
event: editEvent,
|
||||
timelineRenderingType: this.context.timelineRenderingType,
|
||||
timelineRenderingType: this.props.context.timelineRenderingType,
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
case KeyBindingAction.CancelReplyOrEdit:
|
||||
if (!!this.context.replyToEvent) {
|
||||
if (!!this.props.context.replyToEvent) {
|
||||
dis.dispatch({
|
||||
action: "reply_to_event",
|
||||
event: null,
|
||||
context: this.context.timelineRenderingType,
|
||||
context: this.props.context.timelineRenderingType,
|
||||
});
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
@@ -395,7 +392,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
|
||||
dis.dispatch({
|
||||
action: "reply_to_event",
|
||||
event: replyEventId ? this.props.room.findEventById(replyEventId) : null,
|
||||
context: this.context.timelineRenderingType,
|
||||
context: this.props.context.timelineRenderingType,
|
||||
});
|
||||
if (parts) {
|
||||
this.model.reset(parts);
|
||||
@@ -405,7 +402,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
|
||||
}
|
||||
|
||||
private sendQuickReaction(): void {
|
||||
const timeline = this.context.liveTimeline;
|
||||
const timeline = this.props.context.liveTimeline;
|
||||
if (!timeline) return;
|
||||
const events = timeline.getEvents();
|
||||
const reaction = this.model.parts[1].text;
|
||||
@@ -518,7 +515,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
|
||||
// re-focus the composer after QuestionDialog is closed
|
||||
dis.dispatch({
|
||||
action: Action.FocusAComposer,
|
||||
context: this.context.timelineRenderingType,
|
||||
context: this.props.context.timelineRenderingType,
|
||||
});
|
||||
// if !sendAnyway bail to let the user edit the composer and try again
|
||||
if (!sendAnyway) return;
|
||||
@@ -563,7 +560,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
|
||||
dis.dispatch({
|
||||
action: "reply_to_event",
|
||||
event: null,
|
||||
context: this.context.timelineRenderingType,
|
||||
context: this.props.context.timelineRenderingType,
|
||||
});
|
||||
}
|
||||
dis.dispatch({ action: "message_sent" });
|
||||
@@ -593,7 +590,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
|
||||
if (shouldSend && SettingsStore.getValue("scrollToBottomOnMessageSent")) {
|
||||
dis.dispatch({
|
||||
action: "scroll_to_bottom",
|
||||
timelineRenderingType: this.context.timelineRenderingType,
|
||||
timelineRenderingType: this.props.context.timelineRenderingType,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -631,7 +628,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
|
||||
dis.dispatch({
|
||||
action: "reply_to_event",
|
||||
event: this.props.room.findEventById(replyEventId),
|
||||
context: this.context.timelineRenderingType,
|
||||
context: this.props.context.timelineRenderingType,
|
||||
});
|
||||
}
|
||||
return parts;
|
||||
@@ -665,12 +662,12 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
|
||||
switch (payload.action) {
|
||||
case "reply_to_event":
|
||||
case Action.FocusSendMessageComposer:
|
||||
if ((payload.context ?? TimelineRenderingType.Room) === this.context.timelineRenderingType) {
|
||||
if ((payload.context ?? TimelineRenderingType.Room) === this.props.context.timelineRenderingType) {
|
||||
this.editorRef.current?.focus();
|
||||
}
|
||||
break;
|
||||
case Action.ComposerInsert:
|
||||
if (payload.timelineRenderingType !== this.context.timelineRenderingType) break;
|
||||
if (payload.timelineRenderingType !== this.props.context.timelineRenderingType) break;
|
||||
if (payload.composerType !== ComposerType.Send) break;
|
||||
|
||||
if (payload.userId) {
|
||||
@@ -695,7 +692,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
|
||||
this.props.room.roomId,
|
||||
this.props.relation,
|
||||
this.props.mxClient,
|
||||
this.context.timelineRenderingType,
|
||||
this.props.context.timelineRenderingType,
|
||||
);
|
||||
return true; // to skip internal onPaste handler
|
||||
}
|
||||
@@ -734,7 +731,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
|
||||
this.props.room.roomId,
|
||||
this.props.relation,
|
||||
this.props.mxClient,
|
||||
this.context.replyToEvent,
|
||||
this.props.context.replyToEvent,
|
||||
);
|
||||
},
|
||||
(error) => {
|
||||
@@ -789,4 +786,10 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
|
||||
}
|
||||
|
||||
const SendMessageComposerWithMatrixClient = withMatrixClientHOC(SendMessageComposer);
|
||||
export default SendMessageComposerWithMatrixClient;
|
||||
export default forwardRef<SendMessageComposer, Omit<ISendMessageComposerProps, "context" | "mxClient">>(
|
||||
(props, ref) => (
|
||||
<RoomContext.Consumer>
|
||||
{(context) => <SendMessageComposerWithMatrixClient {...props} context={context} ref={ref} />}
|
||||
</RoomContext.Consumer>
|
||||
),
|
||||
);
|
||||
|
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { ReactNode } from "react";
|
||||
import React, { forwardRef, ReactNode } from "react";
|
||||
import { Room, IEventRelation, MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { Optional } from "matrix-events-sdk";
|
||||
@@ -50,6 +50,7 @@ interface IProps {
|
||||
permalinkCreator?: RoomPermalinkCreator;
|
||||
relation?: IEventRelation;
|
||||
replyToEvent?: MatrixEvent;
|
||||
context: React.ContextType<typeof RoomContext>;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
@@ -61,9 +62,7 @@ interface IState {
|
||||
/**
|
||||
* Container tile for rendering the voice message recorder in the composer.
|
||||
*/
|
||||
export default class VoiceRecordComposerTile extends React.PureComponent<IProps, IState> {
|
||||
public static contextType = RoomContext;
|
||||
public context!: React.ContextType<typeof RoomContext>;
|
||||
class VoiceRecordComposerTile extends React.PureComponent<IProps, IState> {
|
||||
private voiceRecordingId: string;
|
||||
|
||||
public constructor(props: IProps) {
|
||||
@@ -141,7 +140,7 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
|
||||
defaultDispatcher.dispatch({
|
||||
action: "reply_to_event",
|
||||
event: null,
|
||||
context: this.context.timelineRenderingType,
|
||||
context: this.props.context.timelineRenderingType,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -323,3 +322,9 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default forwardRef<VoiceRecordComposerTile, Omit<IProps, "context">>((props, ref) => (
|
||||
<RoomContext.Consumer>
|
||||
{(context) => <VoiceRecordComposerTile {...props} context={context} ref={ref} />}
|
||||
</RoomContext.Consumer>
|
||||
));
|
||||
|
@@ -59,7 +59,7 @@ interface WysiwygAutocompleteProps {
|
||||
const WysiwygAutocomplete = forwardRef(
|
||||
(
|
||||
{ suggestion, handleMention, handleCommand, handleAtRoomMention }: WysiwygAutocompleteProps,
|
||||
ref: ForwardedRef<Autocomplete>,
|
||||
ref: ForwardedRef<React.ComponentRef<typeof Autocomplete>>,
|
||||
): JSX.Element | null => {
|
||||
const { room } = useRoomContext();
|
||||
const client = useMatrixClientContext();
|
||||
|
@@ -58,7 +58,7 @@ export const WysiwygComposer = memo(function WysiwygComposer({
|
||||
eventRelation,
|
||||
}: WysiwygComposerProps) {
|
||||
const { room } = useRoomContext();
|
||||
const autocompleteRef = useRef<Autocomplete | null>(null);
|
||||
const autocompleteRef = useRef<React.ComponentRef<typeof Autocomplete> | null>(null);
|
||||
|
||||
const inputEventProcessor = useInputEventProcessor(onSend, autocompleteRef, initialContent, eventRelation);
|
||||
const { ref, isWysiwygReady, content, actionStates, wysiwyg, suggestion, messageContent } = useWysiwyg({
|
||||
|
@@ -37,7 +37,7 @@ import { handleClipboardEvent, handleEventWithAutocomplete, isEventToHandleAsCli
|
||||
|
||||
export function useInputEventProcessor(
|
||||
onSend: () => void,
|
||||
autocompleteRef: React.RefObject<Autocomplete>,
|
||||
autocompleteRef: React.RefObject<React.ComponentRef<typeof Autocomplete>>,
|
||||
initialContent?: string,
|
||||
eventRelation?: IEventRelation,
|
||||
): (event: WysiwygEvent, composer: Wysiwyg, editor: HTMLElement) => WysiwygEvent | null {
|
||||
@@ -105,7 +105,7 @@ function handleKeyboardEvent(
|
||||
roomContext: IRoomState,
|
||||
composerContext: ComposerContextState,
|
||||
mxClient: MatrixClient | undefined,
|
||||
autocompleteRef: React.RefObject<Autocomplete>,
|
||||
autocompleteRef: React.RefObject<React.ComponentRef<typeof Autocomplete>>,
|
||||
): KeyboardEvent | null {
|
||||
const { editorStateTransfer } = composerContext;
|
||||
const isEditing = Boolean(editorStateTransfer);
|
||||
|
@@ -55,7 +55,7 @@ export function usePlainTextListeners(
|
||||
eventRelation?: IEventRelation,
|
||||
): {
|
||||
ref: RefObject<HTMLDivElement>;
|
||||
autocompleteRef: React.RefObject<Autocomplete>;
|
||||
autocompleteRef: React.RefObject<React.ComponentRef<typeof Autocomplete>>;
|
||||
content?: string;
|
||||
onBeforeInput(event: SyntheticEvent<HTMLDivElement, InputEvent | ClipboardEvent>): void;
|
||||
onInput(event: SyntheticEvent<HTMLDivElement, InputEvent | ClipboardEvent>): void;
|
||||
@@ -72,7 +72,7 @@ export function usePlainTextListeners(
|
||||
const mxClient = useMatrixClientContext();
|
||||
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
const autocompleteRef = useRef<Autocomplete | null>(null);
|
||||
const autocompleteRef = useRef<React.ComponentRef<typeof Autocomplete> | null>(null);
|
||||
const [content, setContent] = useState<string | undefined>(initialContent);
|
||||
|
||||
const send = useCallback(() => {
|
||||
|
@@ -70,7 +70,7 @@ export function setCursorPositionAtTheEnd(element: HTMLElement): void {
|
||||
* @returns boolean - whether or not the autocomplete has handled the event
|
||||
*/
|
||||
export function handleEventWithAutocomplete(
|
||||
autocompleteRef: RefObject<Autocomplete>,
|
||||
autocompleteRef: RefObject<React.ComponentRef<typeof Autocomplete>>,
|
||||
// we get a React Keyboard event from plain text composer, a Keyboard Event from the rich text composer
|
||||
event: KeyboardEvent | React.KeyboardEvent<HTMLDivElement>,
|
||||
): boolean {
|
||||
|
@@ -28,7 +28,7 @@ export interface ICallback {
|
||||
}
|
||||
|
||||
export type UpdateCallback = (data: ICallback) => void;
|
||||
export type GetAutocompleterComponent = () => Autocomplete | null;
|
||||
export type GetAutocompleterComponent = () => React.ComponentRef<typeof Autocomplete> | null;
|
||||
export type UpdateQuery = (test: string) => Promise<void>;
|
||||
|
||||
export default class AutocompleteWrapperModel {
|
||||
|
@@ -56,6 +56,7 @@ import {
|
||||
shouldDisplayAsVoiceBroadcastStoppedText,
|
||||
VoiceBroadcastChunkEventType,
|
||||
} from "../voice-broadcast";
|
||||
import { IBodyProps } from "../components/views/messages/IBodyProps";
|
||||
|
||||
// Subset of EventTile's IProps plus some mixins
|
||||
export interface EventTileTypeProps
|
||||
@@ -78,11 +79,11 @@ export interface EventTileTypeProps
|
||||
ref?: React.RefObject<any>; // `any` because it's effectively impossible to convince TS of a reasonable type
|
||||
timestamp?: JSX.Element;
|
||||
maxImageHeight?: number; // pixels
|
||||
overrideBodyTypes?: Record<string, typeof React.Component>;
|
||||
overrideEventTypes?: Record<string, typeof React.Component>;
|
||||
overrideBodyTypes?: Record<string, React.ComponentType<IBodyProps>>;
|
||||
overrideEventTypes?: Record<string, React.ComponentType<IBodyProps>>;
|
||||
}
|
||||
|
||||
type FactoryProps = Omit<EventTileTypeProps, "ref">;
|
||||
type FactoryProps = EventTileTypeProps;
|
||||
type Factory<X = FactoryProps> = (ref: Optional<React.RefObject<any>>, props: X) => JSX.Element;
|
||||
|
||||
export const MessageEventFactory: Factory = (ref, props) => <MessageEvent ref={ref} {...props} />;
|
||||
|
@@ -40,7 +40,7 @@ import React, { createRef } from "react";
|
||||
import { Mocked, mocked } from "jest-mock";
|
||||
import { forEachRight } from "lodash";
|
||||
|
||||
import TimelinePanel from "../../../src/components/structures/TimelinePanel";
|
||||
import TimelinePanel, { TimelinePanel as TimelinePanelClass } from "../../../src/components/structures/TimelinePanel";
|
||||
import MatrixClientContext from "../../../src/contexts/MatrixClientContext";
|
||||
import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
|
||||
import { isCallEvent } from "../../../src/components/structures/LegacyCallEventGrouper";
|
||||
@@ -77,7 +77,7 @@ const mkTimeline = (room: Room, events: MatrixEvent[]): [EventTimeline, EventTim
|
||||
return [timeline, timelineSet];
|
||||
};
|
||||
|
||||
const getProps = (room: Room, events: MatrixEvent[]): TimelinePanel["props"] => {
|
||||
const getProps = (room: Room, events: MatrixEvent[]): React.ComponentProps<typeof TimelinePanel> => {
|
||||
const [, timelineSet] = mkTimeline(room, events);
|
||||
|
||||
return {
|
||||
@@ -184,7 +184,7 @@ describe("TimelinePanel", () => {
|
||||
const roomId = "#room:example.com";
|
||||
let room: Room;
|
||||
let timelineSet: EventTimelineSet;
|
||||
let timelinePanel: TimelinePanel;
|
||||
let timelinePanel: React.ComponentRef<typeof TimelinePanel>;
|
||||
|
||||
const ev1 = new MatrixEvent({
|
||||
event_id: "ev1",
|
||||
@@ -203,7 +203,7 @@ describe("TimelinePanel", () => {
|
||||
});
|
||||
|
||||
const renderTimelinePanel = async (): Promise<void> => {
|
||||
const ref = createRef<TimelinePanel>();
|
||||
const ref = createRef<React.ComponentRef<typeof TimelinePanel>>();
|
||||
render(
|
||||
<TimelinePanel
|
||||
timelineSet={timelineSet}
|
||||
@@ -239,7 +239,7 @@ describe("TimelinePanel", () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
TimelinePanel.roomReadMarkerTsMap = {};
|
||||
TimelinePanelClass.roomReadMarkerTsMap = {};
|
||||
});
|
||||
|
||||
it("when there is no event, it should not send any receipt", async () => {
|
||||
|
@@ -50,7 +50,7 @@ import {
|
||||
} from "../../../test-utils";
|
||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||
import DMRoomMap from "../../../../src/utils/DMRoomMap";
|
||||
import RoomHeader, { IProps as RoomHeaderProps } from "../../../../src/components/views/rooms/LegacyRoomHeader";
|
||||
import RoomHeader from "../../../../src/components/views/rooms/LegacyRoomHeader";
|
||||
import { SearchScope } from "../../../../src/components/views/rooms/SearchBar";
|
||||
import { E2EStatus } from "../../../../src/utils/ShieldUtils";
|
||||
import { IRoomState } from "../../../../src/components/structures/RoomView";
|
||||
@@ -201,7 +201,10 @@ describe("LegacyRoomHeader", () => {
|
||||
WidgetMessagingStore.instance.stopMessaging(widget, call.roomId);
|
||||
};
|
||||
|
||||
const renderHeader = (props: Partial<RoomHeaderProps> = {}, roomContext: Partial<IRoomState> = {}) => {
|
||||
const renderHeader = (
|
||||
props: Partial<React.ComponentProps<typeof RoomHeader>> = {},
|
||||
roomContext: Partial<IRoomState> = {},
|
||||
) => {
|
||||
render(
|
||||
<RoomContext.Provider value={{ ...roomContext, room } as IRoomState}>
|
||||
<RoomHeader
|
||||
@@ -843,7 +846,7 @@ function createRoom(info: IRoomCreationInfo) {
|
||||
}
|
||||
|
||||
function mountHeader(room: Room, propsOverride = {}, roomContext?: Partial<IRoomState>): RenderResult {
|
||||
const props: RoomHeaderProps = {
|
||||
const props: React.ComponentProps<typeof RoomHeader> = {
|
||||
room,
|
||||
inRoom: true,
|
||||
onSearchClick: () => {},
|
||||
|
@@ -43,7 +43,7 @@ jest.mock("../../../../src/stores/VoiceRecordingStore", () => ({
|
||||
}));
|
||||
|
||||
describe("<VoiceRecordComposerTile/>", () => {
|
||||
let voiceRecordComposerTile: RefObject<VoiceRecordComposerTile>;
|
||||
let voiceRecordComposerTile: RefObject<React.ComponentRef<typeof VoiceRecordComposerTile>>;
|
||||
let mockRecorder: VoiceMessageRecording;
|
||||
let mockUpload: IUpload;
|
||||
let mockClient: MatrixClient;
|
||||
|
@@ -60,7 +60,7 @@ describe("WysiwygAutocomplete", () => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
const autocompleteRef = createRef<Autocomplete>();
|
||||
const autocompleteRef = createRef<React.ComponentRef<typeof Autocomplete>>();
|
||||
const getCompletionsSpy = jest.spyOn(Autocompleter.prototype, "getCompletions").mockResolvedValue([
|
||||
{
|
||||
completions: mockCompletion,
|
||||
|
Reference in New Issue
Block a user