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