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

Commit

Permalink
Allow quote-reply in thread view element-web (#6959)
Browse files Browse the repository at this point in the history
  • Loading branch information
germain-gg authored Oct 19, 2021
1 parent d390023 commit 694ec94
Show file tree
Hide file tree
Showing 11 changed files with 84 additions and 88 deletions.
5 changes: 4 additions & 1 deletion src/components/structures/RoomView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -779,7 +779,10 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
});
break;
case 'reply_to_event':
if (this.state.searchResults && payload.event.getRoomId() === this.state.roomId && !this.unmounted) {
if (this.state.searchResults
&& payload.event.getRoomId() === this.state.roomId
&& !this.unmounted
&& payload.context === TimelineRenderingType.Room) {
this.onCancelSearchClick();
}
break;
Expand Down
17 changes: 2 additions & 15 deletions src/components/structures/ThreadPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ limitations under the License.
*/

import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
import { Thread, ThreadEvent } from 'matrix-js-sdk/src/models/thread';
import { EventTimelineSet } from 'matrix-js-sdk/src/models/event-timeline-set';
import { Room } from 'matrix-js-sdk/src/models/room';
Expand All @@ -24,7 +23,6 @@ import BaseCard from "../views/right_panel/BaseCard";
import { RightPanelPhases } from "../../stores/RightPanelStorePhases";

import ResizeNotifier from '../../utils/ResizeNotifier';
import EventTile, { TileShape } from '../views/rooms/EventTile';
import MatrixClientContext from '../../contexts/MatrixClientContext';
import { _t } from '../../languageHandler';
import { ContextMenuButton } from '../../accessibility/context_menu/ContextMenuButton';
Expand All @@ -34,25 +32,14 @@ import TimelinePanel from './TimelinePanel';
import { Layout } from '../../settings/Layout';
import { useEventEmitter } from '../../hooks/useEventEmitter';
import AccessibleButton from '../views/elements/AccessibleButton';
import { TileShape } from '../views/rooms/EventTile';

interface IProps {
roomId: string;
onClose: () => void;
resizeNotifier: ResizeNotifier;
}

export const ThreadPanelItem: React.FC<{ event: MatrixEvent }> = ({ event }) => {
return <EventTile
key={event.getId()}
mxEvent={event}
enableFlair={false}
showReadReceipts={false}
as="div"
tileShape={TileShape.Thread}
alwaysShowTimestamps={true}
/>;
};

export enum ThreadFilterType {
"My",
"All"
Expand Down Expand Up @@ -230,7 +217,7 @@ const ThreadPanel: React.FC<IProps> = ({ roomId, onClose }) => {
showReactions={true}
className="mx_RoomView_messagePanel mx_GroupLayout"
membersLoaded={true}
tileShape={TileShape.ThreadPanel}
tileShape={TileShape.Thread}
/>
</BaseCard>
</RoomContext.Provider>
Expand Down
10 changes: 9 additions & 1 deletion src/components/structures/ThreadView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ interface IProps {
interface IState {
thread?: Thread;
editState?: EditorStateTransfer;
replyToEvent?: MatrixEvent;
}

@replaceableComponent("structures.ThreadView")
Expand Down Expand Up @@ -114,6 +115,13 @@ export default class ThreadView extends React.Component<IProps, IState> {
});
break;
}
case 'reply_to_event':
if (payload.context === TimelineRenderingType.Thread) {
this.setState({
replyToEvent: payload.event,
});
}
break;
default:
break;
}
Expand Down Expand Up @@ -199,7 +207,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
rel_type: RelationType.Thread,
event_id: this.state.thread.id,
}}
showReplyPreview={false}
replyToEvent={this.state.replyToEvent}
permalinkCreator={this.props.permalinkCreator}
e2eStatus={this.props.e2eStatus}
compact={true}
Expand Down
10 changes: 6 additions & 4 deletions src/components/views/messages/MessageActionBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,15 @@ const OptionsButton: React.FC<IOptionsButtonProps> =
let contextMenu;
if (menuDisplayed) {
const tile = getTile && getTile();
const replyThread = getReplyChain && getReplyChain();
const replyChain = getReplyChain && getReplyChain();

const buttonRect = button.current.getBoundingClientRect();
contextMenu = <MessageContextMenu
{...aboveLeftOf(buttonRect)}
mxEvent={mxEvent}
permalinkCreator={permalinkCreator}
eventTileOps={tile && tile.getEventTileOps ? tile.getEventTileOps() : undefined}
collapseReplyChain={replyThread && replyThread.canCollapse() ? replyThread.collapse : undefined}
collapseReplyChain={replyChain && replyChain.canCollapse() ? replyChain.collapse : undefined}
onFinished={closeMenu}
/>;
}
Expand Down Expand Up @@ -191,6 +191,7 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
dis.dispatch({
action: 'reply_to_event',
event: this.props.mxEvent,
context: this.context.timelineRenderingType,
});
};

Expand Down Expand Up @@ -289,15 +290,16 @@ 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.context.timelineRenderingType !== TimelineRenderingType.Thread) {
if (this.context.canReply) {
toolbarOpts.splice(0, 0, <>
<RovingAccessibleTooltipButton
className="mx_MessageActionBar_maskButton mx_MessageActionBar_replyButton"
title={_t("Reply")}
onClick={this.onReplyClick}
key="reply"
/>
{ SettingsStore.getValue("feature_thread") && (
{ (SettingsStore.getValue("feature_thread")
&& this.context.timelineRenderingType !== TimelineRenderingType.Thread) && (
<RovingAccessibleTooltipButton
className="mx_MessageActionBar_maskButton mx_MessageActionBar_threadButton"
title={_t("Thread")}
Expand Down
21 changes: 18 additions & 3 deletions src/components/views/rooms/EventTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ export default class EventTile extends React.Component<IProps, IState> {
private isListeningForReceipts: boolean;
// TODO: Types
private tile = React.createRef<unknown>();
private replyThread = React.createRef<ReplyChain>();
private replyChain = React.createRef<ReplyChain>();

public readonly ref = createRef<HTMLElement>();

Expand Down Expand Up @@ -933,7 +933,7 @@ export default class EventTile extends React.Component<IProps, IState> {
// TODO: Types
getTile: () => any | null = () => this.tile.current;

getReplyChain = () => this.replyThread.current;
getReplyChain = () => this.replyChain.current;

getReactions = () => {
if (
Expand Down Expand Up @@ -1214,12 +1214,26 @@ export default class EventTile extends React.Component<IProps, IState> {
]);
}
case TileShape.Thread: {
const thread = haveTileForEvent(this.props.mxEvent) &&
ReplyChain.hasReply(this.props.mxEvent) ? (
<ReplyChain
parentEv={this.props.mxEvent}
onHeightChanged={this.props.onHeightChanged}
ref={this.replyChain}
forExport={this.props.forExport}
permalinkCreator={this.props.permalinkCreator}
layout={this.props.layout}
alwaysShowTimestamps={this.props.alwaysShowTimestamps || this.state.hover}
isQuoteExpanded={isQuoteExpanded}
setQuoteExpanded={this.setQuoteExpanded}
/>) : null;
const room = this.context.getRoom(this.props.mxEvent.getRoomId());
return React.createElement(this.props.as || "li", {
"className": classes,
"aria-live": ariaLive,
"aria-atomic": true,
"data-scroll-tokens": scrollToken,
"data-has-reply": !!thread,
}, [
<div className="mx_EventTile_roomName" key="mx_EventTile_roomName">
<RoomAvatar room={room} width={28} height={28} />
Expand All @@ -1235,6 +1249,7 @@ export default class EventTile extends React.Component<IProps, IState> {
</a>
</div>,
<div className="mx_EventTile_line" key="mx_EventTile_line">
{ thread }
<EventTileType ref={this.tile}
mxEvent={this.props.mxEvent}
highlights={this.props.highlights}
Expand Down Expand Up @@ -1287,7 +1302,7 @@ export default class EventTile extends React.Component<IProps, IState> {
<ReplyChain
parentEv={this.props.mxEvent}
onHeightChanged={this.props.onHeightChanged}
ref={this.replyThread}
ref={this.replyChain}
forExport={this.props.forExport}
permalinkCreator={this.props.permalinkCreator}
layout={this.props.layout}
Expand Down
13 changes: 7 additions & 6 deletions src/components/views/rooms/MessageComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import UIStore, { UI_EVENTS } from '../../../stores/UIStore';
import Modal from "../../../Modal";
import InfoDialog from "../dialogs/InfoDialog";
import { RelationType } from 'matrix-js-sdk/src/@types/event';
import RoomContext from '../../../contexts/RoomContext';

let instanceCount = 0;
const NARROW_MODE_BREAKPOINT = 500;
Expand Down Expand Up @@ -227,7 +228,6 @@ interface IProps {
permalinkCreator: RoomPermalinkCreator;
replyToEvent?: MatrixEvent;
relation?: IEventRelation;
showReplyPreview?: boolean;
e2eStatus?: E2EStatus;
compact?: boolean;
}
Expand All @@ -252,8 +252,9 @@ export default class MessageComposer extends React.Component<IProps, IState> {
private ref: React.RefObject<HTMLDivElement> = createRef();
private instanceId: number;

public static contextType = RoomContext;

static defaultProps = {
showReplyPreview: true,
compact: false,
};

Expand Down Expand Up @@ -294,7 +295,7 @@ export default class MessageComposer extends React.Component<IProps, IState> {
};

private onAction = (payload: ActionPayload) => {
if (payload.action === 'reply_to_event') {
if (payload.action === 'reply_to_event' && payload.context === this.context.timelineRenderingType) {
// add a timeout for the reply preview to be rendered, so
// that the ScrollPanel listening to the resizeNotifier can
// correctly measure it's new height and scroll down to keep
Expand Down Expand Up @@ -633,9 +634,9 @@ export default class MessageComposer extends React.Component<IProps, IState> {
<div className={classes} ref={this.ref}>
{ recordingTooltip }
<div className="mx_MessageComposer_wrapper">
{ this.props.showReplyPreview && (
<ReplyPreview permalinkCreator={this.props.permalinkCreator} />
) }
<ReplyPreview
replyToEvent={this.props.replyToEvent}
permalinkCreator={this.props.permalinkCreator} />
<div className="mx_MessageComposer_row">
{ controls }
{ this.renderButtons(menuPosition) }
Expand Down
52 changes: 10 additions & 42 deletions src/components/views/rooms/ReplyPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,63 +17,31 @@ limitations under the License.
import React from 'react';
import dis from '../../../dispatcher/dispatcher';
import { _t } from '../../../languageHandler';
import RoomViewStore from '../../../stores/RoomViewStore';
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import ReplyTile from './ReplyTile';
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
import { EventSubscription } from 'fbemitter';
import RoomContext, { TimelineRenderingType } from '../../../contexts/RoomContext';

function cancelQuoting() {
function cancelQuoting(context: TimelineRenderingType) {
dis.dispatch({
action: 'reply_to_event',
event: null,
context,
});
}

interface IProps {
permalinkCreator: RoomPermalinkCreator;
}

interface IState {
event: MatrixEvent;
replyToEvent: MatrixEvent;
}

@replaceableComponent("views.rooms.ReplyPreview")
export default class ReplyPreview extends React.Component<IProps, IState> {
private unmounted = false;
private readonly roomStoreToken: EventSubscription;

constructor(props) {
super(props);

this.state = {
event: RoomViewStore.getQuotingEvent(),
};

this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate);
}

componentWillUnmount() {
this.unmounted = true;

// Remove RoomStore listener
if (this.roomStoreToken) {
this.roomStoreToken.remove();
}
}

private onRoomViewStoreUpdate = (): void => {
if (this.unmounted) return;

const event = RoomViewStore.getQuotingEvent();
if (this.state.event !== event) {
this.setState({ event });
}
};
export default class ReplyPreview extends React.Component<IProps> {
public static contextType = RoomContext;

render() {
if (!this.state.event) return null;
public render(): JSX.Element {
if (!this.props.replyToEvent) return null;

return <div className="mx_ReplyPreview">
<div className="mx_ReplyPreview_section">
Expand All @@ -86,13 +54,13 @@ export default class ReplyPreview extends React.Component<IProps, IState> {
src={require("../../../../res/img/cancel.svg")}
width="18"
height="18"
onClick={cancelQuoting}
onClick={() => cancelQuoting(this.context.timelineRenderingType)}
/>
</div>
<div className="mx_ReplyPreview_clear" />
<div className="mx_ReplyPreview_tile">
<ReplyTile
mxEvent={this.state.event}
mxEvent={this.props.replyToEvent}
permalinkCreator={this.props.permalinkCreator}
/>
</div>
Expand Down
8 changes: 7 additions & 1 deletion src/components/views/rooms/SendMessageComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
dis.dispatch({
action: 'reply_to_event',
event: null,
context: this.context.timelineRenderingType,
});
break;
default:
Expand Down Expand Up @@ -269,6 +270,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
dis.dispatch({
action: 'reply_to_event',
event: replyEventId ? this.props.room.findEventById(replyEventId) : null,
context: this.context.timelineRenderingType,
});
if (parts) {
this.model.reset(parts);
Expand Down Expand Up @@ -479,6 +481,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
dis.dispatch({
action: 'reply_to_event',
event: null,
context: this.context.timelineRenderingType,
});
}
dis.dispatch({ action: "message_sent" });
Expand Down Expand Up @@ -552,6 +555,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
dis.dispatch({
action: 'reply_to_event',
event: this.props.room.findEventById(replyEventId),
context: this.context.timelineRenderingType,
});
}
return parts;
Expand Down Expand Up @@ -583,7 +587,9 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
switch (payload.action) {
case 'reply_to_event':
case Action.FocusSendMessageComposer:
this.editorRef.current?.focus();
if (payload.context === this.context.timelineRenderingType) {
this.editorRef.current?.focus();
}
break;
case "send_composer_insert":
if (payload.userId) {
Expand Down
10 changes: 5 additions & 5 deletions src/contexts/RoomContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ import { IRoomState } from "../components/structures/RoomView";
import { Layout } from "../settings/Layout";

export enum TimelineRenderingType {
Room,
Thread,
ThreadsList,
File,
Notification,
Room = "Room",
Thread = "Thread",
ThreadsList = "ThreadsList",
File = "File",
Notification = "Notification",
}

const RoomContext = createContext<IRoomState>({
Expand Down
Loading

0 comments on commit 694ec94

Please sign in to comment.