Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Add ability to properly edit messages in Threads. #6877

Merged
merged 23 commits into from
Oct 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 18 additions & 10 deletions src/components/structures/MessagePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ import Spinner from "../views/elements/Spinner";
import TileErrorBoundary from '../views/messages/TileErrorBoundary';
import { RoomPermalinkCreator } from "../../utils/permalinks/Permalinks";
import EditorStateTransfer from "../../utils/EditorStateTransfer";
import { logger } from 'matrix-js-sdk/src/logger';
Palid marked this conversation as resolved.
Show resolved Hide resolved
import { Action } from '../../dispatcher/actions';

const CONTINUATION_MAX_INTERVAL = 5 * 60 * 1000; // 5 minutes
const continuedTypes = [EventType.Sticker, EventType.RoomMessage];
Expand Down Expand Up @@ -287,6 +289,15 @@ export default class MessagePanel extends React.Component<IProps, IState> {
ghostReadMarkers,
});
}

const pendingEditItem = this.pendingEditItem;
if (!this.props.editState && this.props.room && pendingEditItem) {
defaultDispatcher.dispatch({
action: Action.EditEvent,
event: this.props.room.findEventById(pendingEditItem),
timelineRenderingType: this.context.timelineRenderingType,
});
}
}

private calculateRoomMembersCount = (): void => {
Expand Down Expand Up @@ -550,10 +561,14 @@ export default class MessagePanel extends React.Component<IProps, IState> {
return { nextEvent, nextTile };
}

private get roomHasPendingEdit(): string {
return this.props.room && localStorage.getItem(`mx_edit_room_${this.props.room.roomId}`);
private get pendingEditItem(): string | undefined {
try {
return localStorage.getItem(`mx_edit_room_${this.props.room.roomId}_${this.context.timelineRenderingType}`);
} catch (err) {
logger.error(err);
return undefined;
}
}

private getEventTiles(): ReactNode[] {
this.eventNodes = {};

Expand Down Expand Up @@ -663,13 +678,6 @@ export default class MessagePanel extends React.Component<IProps, IState> {
}
}

if (!this.props.editState && this.roomHasPendingEdit) {
defaultDispatcher.dispatch({
action: "edit_event",
event: this.props.room.findEventById(this.roomHasPendingEdit),
});
}

if (grouper) {
ret.push(...grouper.getTiles());
}
Expand Down
28 changes: 21 additions & 7 deletions src/components/structures/RoomView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ import { Layout } from "../../settings/Layout";
import AccessibleButton from "../views/elements/AccessibleButton";
import RightPanelStore from "../../stores/RightPanelStore";
import { haveTileForEvent } from "../views/rooms/EventTile";
import RoomContext from "../../contexts/RoomContext";
import MatrixClientContext from "../../contexts/MatrixClientContext";
import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
import MatrixClientContext, { withMatrixClientHOC, MatrixClientProps } from "../../contexts/MatrixClientContext";
import { E2EStatus, shieldStatusForRoom } from '../../utils/ShieldUtils';
import { Action } from "../../dispatcher/actions";
import { IMatrixClientCreds } from "../../MatrixClientPeg";
Expand Down Expand Up @@ -91,6 +91,7 @@ import TopUnreadMessagesBar from "../views/rooms/TopUnreadMessagesBar";
import SpaceStore from "../../stores/SpaceStore";

import { logger } from "matrix-js-sdk/src/logger";
import { EventTimeline } from 'matrix-js-sdk/src/models/event-timeline';

const DEBUG = false;
let debuglog = function(msg: string) {};
Expand All @@ -102,7 +103,7 @@ if (DEBUG) {
debuglog = logger.log.bind(console);
}

interface IProps {
interface IRoomProps extends MatrixClientProps {
threepidInvite: IThreepidInvite;
oobData?: IOOBData;

Expand All @@ -113,7 +114,7 @@ interface IProps {
onRegistered?(credentials: IMatrixClientCreds): void;
}

export interface IState {
export interface IRoomState {
room?: Room;
roomId?: string;
roomAlias?: string;
Expand Down Expand Up @@ -187,10 +188,12 @@ export interface IState {
// if it did we don't want the room to be marked as read as soon as it is loaded.
wasContextSwitch?: boolean;
editState?: EditorStateTransfer;
timelineRenderingType: TimelineRenderingType;
liveTimeline?: EventTimeline;
}

@replaceableComponent("structures.RoomView")
export default class RoomView extends React.Component<IProps, IState> {
export class RoomView extends React.Component<IRoomProps, IRoomState> {
private readonly dispatcherRef: string;
private readonly roomStoreToken: EventSubscription;
private readonly rightPanelStoreToken: EventSubscription;
Expand Down Expand Up @@ -247,6 +250,8 @@ export default class RoomView extends React.Component<IProps, IState> {
showDisplaynameChanges: true,
matrixClientIsReady: this.context && this.context.isInitialSyncComplete(),
dragCounter: 0,
timelineRenderingType: TimelineRenderingType.Room,
liveTimeline: undefined,
};

this.dispatcherRef = dis.register(this.onAction);
Expand Down Expand Up @@ -336,7 +341,7 @@ export default class RoomView extends React.Component<IProps, IState> {

const roomId = RoomViewStore.getRoomId();

const newState: Pick<IState, any> = {
const newState: Pick<IRoomState, any> = {
roomId,
roomAlias: RoomViewStore.getRoomAlias(),
roomLoading: RoomViewStore.isRoomLoading(),
Expand Down Expand Up @@ -808,7 +813,9 @@ export default class RoomView extends React.Component<IProps, IState> {
this.onSearchClick();
break;

case "edit_event": {
case Action.EditEvent: {
// Quit early if we're trying to edit events in wrong rendering context
if (payload.timelineRenderingType !== this.state.timelineRenderingType) return;
const editState = payload.event ? new EditorStateTransfer(payload.event) : null;
this.setState({ editState }, () => {
if (payload.event) {
Expand Down Expand Up @@ -932,6 +939,10 @@ export default class RoomView extends React.Component<IProps, IState> {
this.updateE2EStatus(room);
this.updatePermissions(room);
this.checkWidgets(room);

this.setState({
liveTimeline: room.getLiveTimeline(),
});
};

private async calculateRecommendedVersion(room: Room) {
Expand Down Expand Up @@ -2086,3 +2097,6 @@ export default class RoomView extends React.Component<IProps, IState> {
);
}
}

const RoomViewWithMatrixClient = withMatrixClientHOC(RoomView);
export default RoomViewWithMatrixClient;
106 changes: 69 additions & 37 deletions src/components/structures/ThreadView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import { SetRightPanelPhasePayload } from '../../dispatcher/payloads/SetRightPan
import { Action } from '../../dispatcher/actions';
import { MatrixClientPeg } from '../../MatrixClientPeg';
import { E2EStatus } from '../../utils/ShieldUtils';
import EditorStateTransfer from '../../utils/EditorStateTransfer';
import RoomContext, { TimelineRenderingType } from '../../contexts/RoomContext';

interface IProps {
room: Room;
Expand All @@ -47,10 +49,14 @@ interface IProps {
interface IState {
replyToEvent?: MatrixEvent;
thread?: Thread;
editState?: EditorStateTransfer;

}

@replaceableComponent("structures.ThreadView")
export default class ThreadView extends React.Component<IProps, IState> {
static contextType = RoomContext;

private dispatcherRef: string;
private timelinePanelRef: React.RefObject<TimelinePanel> = React.createRef();

Expand Down Expand Up @@ -90,6 +96,23 @@ export default class ThreadView extends React.Component<IProps, IState> {
this.setupThread(payload.event);
}
}
switch (payload.action) {
case Action.EditEvent: {
// Quit early if it's not a thread context
if (payload.timelineRenderingType !== TimelineRenderingType.Thread) return;
// Quit early if that's not a thread event
if (payload.event && !payload.event.getThread()) return;
const editState = payload.event ? new EditorStateTransfer(payload.event) : null;
this.setState({ editState }, () => {
if (payload.event) {
this.timelinePanelRef.current?.scrollToEventIfNeeded(payload.event.getId());
}
});
break;
}
default:
break;
}
};

private setupThread = (mxEv: MatrixEvent) => {
Expand Down Expand Up @@ -124,44 +147,53 @@ export default class ThreadView extends React.Component<IProps, IState> {

public render(): JSX.Element {
return (
<BaseCard
className="mx_ThreadView"
onClose={this.props.onClose}
previousPhase={RightPanelPhases.RoomSummary}
withoutScrollContainer={true}
>
{ this.state.thread && (
<TimelinePanel
ref={this.timelinePanelRef}
showReadReceipts={false} // No RR support in thread's MVP
manageReadReceipts={false} // No RR support in thread's MVP
manageReadMarkers={false} // No RM support in thread's MVP
sendReadReceiptOnLoad={false} // No RR support in thread's MVP
timelineSet={this.state?.thread?.timelineSet}
showUrlPreview={true}
tileShape={TileShape.Thread}
empty={<div>empty</div>}
alwaysShowTimestamps={true}
layout={Layout.Group}
hideThreadedMessages={false}
hidden={false}
showReactions={true}
className="mx_RoomView_messagePanel mx_GroupLayout"
<RoomContext.Provider value={{
...this.context,
timelineRenderingType: TimelineRenderingType.Thread,
liveTimeline: this.state?.thread?.timelineSet?.getLiveTimeline(),
}}>

<BaseCard
className="mx_ThreadView"
onClose={this.props.onClose}
previousPhase={RightPanelPhases.RoomSummary}
withoutScrollContainer={true}
>
{ this.state.thread && (
<TimelinePanel
ref={this.timelinePanelRef}
showReadReceipts={false} // No RR support in thread's MVP
manageReadReceipts={false} // No RR support in thread's MVP
manageReadMarkers={false} // No RM support in thread's MVP
sendReadReceiptOnLoad={false} // No RR support in thread's MVP
timelineSet={this.state?.thread?.timelineSet}
showUrlPreview={true}
tileShape={TileShape.Thread}
empty={<div>empty</div>}
alwaysShowTimestamps={true}
layout={Layout.Group}
hideThreadedMessages={false}
hidden={false}
showReactions={true}
className="mx_RoomView_messagePanel mx_GroupLayout"
permalinkCreator={this.props.permalinkCreator}
membersLoaded={true}
editState={this.state.editState}
/>
) }

{ this.state?.thread?.timelineSet && (<MessageComposer
room={this.props.room}
resizeNotifier={this.props.resizeNotifier}
replyInThread={true}
replyToEvent={this.state?.thread?.replyToEvent}
showReplyPreview={false}
permalinkCreator={this.props.permalinkCreator}
membersLoaded={true}
/>
) }
<MessageComposer
room={this.props.room}
resizeNotifier={this.props.resizeNotifier}
replyInThread={true}
replyToEvent={this.state?.thread?.replyToEvent}
showReplyPreview={false}
permalinkCreator={this.props.permalinkCreator}
e2eStatus={this.props.e2eStatus}
compact={true}
/>
</BaseCard>
e2eStatus={this.props.e2eStatus}
compact={true}
/>) }
</BaseCard>
</RoomContext.Provider>
);
}
}
17 changes: 4 additions & 13 deletions src/components/views/messages/MessageActionBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { Action } from '../../../dispatcher/actions';
import { RightPanelPhases } from '../../../stores/RightPanelStorePhases';
import { aboveLeftOf, ContextMenu, ContextMenuTooltipButton, useContextMenu } from '../../structures/ContextMenu';
import { isContentActionable, canEditContent } from '../../../utils/EventUtils';
import RoomContext from "../../../contexts/RoomContext";
import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
import Toolbar from "../../../accessibility/Toolbar";
import { RovingAccessibleTooltipButton, useRovingTabIndex } from "../../../accessibility/RovingTabIndex";
import { replaceableComponent } from "../../../utils/replaceableComponent";
Expand Down Expand Up @@ -128,11 +128,6 @@ const ReactButton: React.FC<IReactButtonProps> = ({ mxEvent, reactions, onFocusC
</React.Fragment>;
};

export enum ActionBarRenderingContext {
Room,
Thread
}

interface IMessageActionBarProps {
mxEvent: MatrixEvent;
reactions?: Relations;
Expand All @@ -142,18 +137,13 @@ interface IMessageActionBarProps {
permalinkCreator?: RoomPermalinkCreator;
onFocusChange?: (menuDisplayed: boolean) => void;
toggleThreadExpanded: () => void;
renderingContext?: ActionBarRenderingContext;
isQuoteExpanded?: boolean;
}

@replaceableComponent("views.messages.MessageActionBar")
export default class MessageActionBar extends React.PureComponent<IMessageActionBarProps> {
public static contextType = RoomContext;

public static defaultProps = {
renderingContext: ActionBarRenderingContext.Room,
};

public componentDidMount(): void {
if (this.props.mxEvent.status && this.props.mxEvent.status !== EventStatus.SENT) {
this.props.mxEvent.on("Event.status", this.onSent);
Expand Down Expand Up @@ -217,8 +207,9 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction

private onEditClick = (ev: React.MouseEvent): void => {
dis.dispatch({
action: 'edit_event',
action: Action.EditEvent,
event: this.props.mxEvent,
timelineRenderingType: this.context.timelineRenderingType,
});
};

Expand Down Expand Up @@ -298,7 +289,7 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
// Like the resend button, the react and reply buttons need to appear before the edit.
// The only catch is we do the reply button first so that we can make sure the react
// button is the very first button without having to do length checks for `splice()`.
if (this.context.canReply && this.props.renderingContext === ActionBarRenderingContext.Room) {
if (this.context.canReply && this.context.timelineRenderingType === TimelineRenderingType.Room) {
toolbarOpts.splice(0, 0, <>
<RovingAccessibleTooltipButton
className="mx_MessageActionBar_maskButton mx_MessageActionBar_replyButton"
Expand Down
Loading