Allow quote-reply in thread view element-web (#6959)

This commit is contained in:
Germain 2021-10-19 16:05:34 +01:00 committed by GitHub
parent d39002338d
commit 694ec946e2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 84 additions and 88 deletions

View file

@ -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>();
@ -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 (
@ -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} />
@ -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}
@ -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}

View file

@ -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;
@ -227,7 +228,6 @@ interface IProps {
permalinkCreator: RoomPermalinkCreator;
replyToEvent?: MatrixEvent;
relation?: IEventRelation;
showReplyPreview?: boolean;
e2eStatus?: E2EStatus;
compact?: boolean;
}
@ -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,
};
@ -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
@ -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) }

View file

@ -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;
export default class ReplyPreview extends React.Component<IProps> {
public static contextType = RoomContext;
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 });
}
};
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">
@ -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>

View file

@ -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:
@ -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);
@ -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" });
@ -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;
@ -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) {