{
header = ;
}
+ const { isQuoteExpanded } = this.props;
const evTiles = this.state.events.map((ev) => {
- return
-
-
;
+ const classname = classNames({
+ 'mx_ReplyThread': true,
+ [this.getReplyThreadColorClass(ev)]: true,
+ // We don't want to add the class if it's undefined, it should only be expanded/collapsed when it's true/false
+ 'mx_ReplyThread--expanded': isQuoteExpanded === true,
+ // We don't want to add the class if it's undefined, it should only be expanded/collapsed when it's true/false
+ 'mx_ReplyThread--collapsed': isQuoteExpanded === false,
+ });
+ return (
+
+ this.props.setQuoteExpanded(!this.props.isQuoteExpanded)}
+ />
+
+ );
});
return
diff --git a/src/components/views/messages/MessageActionBar.tsx b/src/components/views/messages/MessageActionBar.tsx
index f76fa32ddc..e835584387 100644
--- a/src/components/views/messages/MessageActionBar.tsx
+++ b/src/components/views/messages/MessageActionBar.tsx
@@ -17,7 +17,8 @@ limitations under the License.
*/
import React, { useEffect } from 'react';
-import { MatrixEvent, EventStatus } from 'matrix-js-sdk/src/models/event';
+import { EventStatus, MatrixEvent } from 'matrix-js-sdk/src/models/event';
+import type { Relations } from 'matrix-js-sdk/src/models/relations';
import { _t } from '../../../languageHandler';
import * as sdk from '../../../index';
@@ -35,13 +36,17 @@ import Resend from "../../../Resend";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { MediaEventHelper } from "../../../utils/MediaEventHelper";
import DownloadActionButton from "./DownloadActionButton";
+import MessageContextMenu from "../context_menus/MessageContextMenu";
+import classNames from 'classnames';
+
import SettingsStore from '../../../settings/SettingsStore';
import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
import ReplyThread from '../elements/ReplyThread';
interface IOptionsButtonProps {
mxEvent: MatrixEvent;
- getTile: () => any; // TODO: FIXME, haven't figured out what the return type is here
+ // TODO: Types
+ getTile: () => any | null;
getReplyThread: () => ReplyThread;
permalinkCreator: RoomPermalinkCreator;
onFocusChange: (menuDisplayed: boolean) => void;
@@ -57,8 +62,6 @@ const OptionsButton: React.FC =
let contextMenu;
if (menuDisplayed) {
- const MessageContextMenu = sdk.getComponent('context_menus.MessageContextMenu');
-
const tile = getTile && getTile();
const replyThread = getReplyThread && getReplyThread();
@@ -90,7 +93,7 @@ const OptionsButton: React.FC =
interface IReactButtonProps {
mxEvent: MatrixEvent;
- reactions: any; // TODO: types
+ reactions: Relations;
onFocusChange: (menuDisplayed: boolean) => void;
}
@@ -127,12 +130,14 @@ const ReactButton: React.FC = ({ mxEvent, reactions, onFocusC
interface IMessageActionBarProps {
mxEvent: MatrixEvent;
- // The Relations model from the JS SDK for reactions to `mxEvent`
- reactions?: any; // TODO: types
+ reactions?: Relations;
+ // TODO: Types
+ getTile: () => any | null;
+ getReplyThread: () => ReplyThread | undefined;
permalinkCreator?: RoomPermalinkCreator;
- getTile: () => any; // TODO: FIXME, haven't figured out what the return type is here
- getReplyThread?: () => ReplyThread;
- onFocusChange?: (menuDisplayed: boolean) => void;
+ onFocusChange: (menuDisplayed: boolean) => void;
+ isQuoteExpanded?: boolean;
+ toggleThreadExpanded: () => void;
}
@replaceableComponent("views.messages.MessageActionBar")
@@ -324,6 +329,20 @@ export default class MessageActionBar extends React.PureComponent);
+ }
+
// The menu button should be last, so dump it there.
toolbarOpts.push( {
// If it's less than 30% we don't add the expansion button.
// We also round the number as it sometimes can be 29.99...
const percentageOfViewport = Math.round(pre.offsetHeight / UIStore.instance.windowHeight * 100);
+ // TODO: additionally show the button if it's an expanded quoted message
if (percentageOfViewport < 30) return;
const button = document.createElement("span");
diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx
index d1ac06b199..592827eaf5 100644
--- a/src/components/views/rooms/EventTile.tsx
+++ b/src/components/views/rooms/EventTile.tsx
@@ -322,7 +322,7 @@ interface IState {
reactions: Relations;
hover: boolean;
-
+ isQuoteExpanded?: boolean;
thread?: Thread;
}
@@ -330,7 +330,8 @@ interface IState {
export default class EventTile extends React.Component {
private suppressReadReceiptAnimation: boolean;
private isListeningForReceipts: boolean;
- private tile = React.createRef();
+ // TODO: Types
+ private tile = React.createRef();
private replyThread = React.createRef();
public readonly ref = createRef();
@@ -888,8 +889,8 @@ export default class EventTile extends React.Component {
actionBarFocused: focused,
});
};
-
- getTile = () => this.tile.current;
+ // TODO: Types
+ getTile: () => any | null = () => this.tile.current;
getReplyThread = () => this.replyThread.current;
@@ -914,6 +915,11 @@ export default class EventTile extends React.Component {
});
};
+ private setQuoteExpanded = (expanded: boolean) => {
+ this.setState({
+ isQuoteExpanded: expanded,
+ });
+ };
render() {
const msgtype = this.props.mxEvent.getContent().msgtype;
const eventType = this.props.mxEvent.getType() as EventType;
@@ -923,6 +929,7 @@ export default class EventTile extends React.Component {
isInfoMessage,
isLeftAlignedBubbleMessage,
} = getEventDisplayInfo(this.props.mxEvent);
+ const { isQuoteExpanded } = this.state;
// This shouldn't happen: the caller should check we support this type
// before trying to instantiate us
@@ -935,6 +942,7 @@ export default class EventTile extends React.Component {
;
}
+
const EventTileType = sdk.getComponent(tileHandler);
const isSending = (['sending', 'queued', 'encrypting'].indexOf(this.props.eventSendStatus) !== -1);
@@ -1054,6 +1062,8 @@ export default class EventTile extends React.Component {
getTile={this.getTile}
getReplyThread={this.getReplyThread}
onFocusChange={this.onActionBarFocusChange}
+ isQuoteExpanded={isQuoteExpanded}
+ toggleThreadExpanded={() => this.setQuoteExpanded(!isQuoteExpanded)}
/> : undefined;
const showTimestamp = this.props.mxEvent.getTs()
@@ -1192,20 +1202,18 @@ export default class EventTile extends React.Component {
}
default: {
- let thread;
- // When the "showHiddenEventsInTimeline" lab is enabled,
- // avoid showing replies for hidden events (events without tiles)
- if (haveTileForEvent(this.props.mxEvent)) {
- thread = ReplyThread.makeThread(
- this.props.mxEvent,
- this.props.onHeightChanged,
- this.props.permalinkCreator,
- this.replyThread,
- this.props.layout,
- this.props.alwaysShowTimestamps || this.state.hover,
- );
- }
-
+ const thread = haveTileForEvent(this.props.mxEvent) &&
+ ReplyThread.hasThreadReply(this.props.mxEvent) ? (
+ ) : null;
const isOwnEvent = this.props.mxEvent?.sender?.userId === MatrixClientPeg.get().getUserId();
// tab-index=-1 to allow it to be focusable but do not add tab stop for it, primarily for screen readers
diff --git a/src/components/views/rooms/ReplyTile.tsx b/src/components/views/rooms/ReplyTile.tsx
index cf7d1ce945..01a9e2f18b 100644
--- a/src/components/views/rooms/ReplyTile.tsx
+++ b/src/components/views/rooms/ReplyTile.tsx
@@ -35,6 +35,7 @@ interface IProps {
highlights?: string[];
highlightLink?: string;
onHeightChanged?(): void;
+ toggleExpandedQuote?: () => void;
}
@replaceableComponent("views.rooms.ReplyTile")
@@ -82,12 +83,17 @@ export default class ReplyTile extends React.PureComponent {
// This allows the permalink to be opened in a new tab/window or copied as
// matrix.to, but also for it to enable routing within Riot when clicked.
e.preventDefault();
- dis.dispatch({
- action: 'view_room',
- event_id: this.props.mxEvent.getId(),
- highlighted: true,
- room_id: this.props.mxEvent.getRoomId(),
- });
+ // Expand thread on shift key
+ if (this.props.toggleExpandedQuote && e.shiftKey) {
+ this.props.toggleExpandedQuote();
+ } else {
+ dis.dispatch({
+ action: 'view_room',
+ event_id: this.props.mxEvent.getId(),
+ highlighted: true,
+ room_id: this.props.mxEvent.getRoomId(),
+ });
+ }
}
};
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index b5d90f6671..bc45caedb5 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -1944,6 +1944,8 @@
"Edit": "Edit",
"Reply": "Reply",
"Thread": "Thread",
+ "Collapse quotes │ ⇧+click": "Collapse quotes │ ⇧+click",
+ "Expand quotes │ ⇧+click": "Expand quotes │ ⇧+click",
"Message Actions": "Message Actions",
"Download %(text)s": "Download %(text)s",
"Error decrypting attachment": "Error decrypting attachment",