mirror of
https://github.com/element-hq/element-web
synced 2024-11-26 19:26:04 +03:00
Allow bubble layout in Thread View (#7478)
This commit is contained in:
parent
038a6bc204
commit
f5465b37a9
4 changed files with 144 additions and 99 deletions
|
@ -180,7 +180,7 @@ limitations under the License.
|
|||
border-top-left-radius: var(--cornerRadius);
|
||||
border-top-right-radius: var(--cornerRadius);
|
||||
|
||||
> a {
|
||||
> a, .mx_MessageTimestamp {
|
||||
position: absolute;
|
||||
padding: 4px 8px;
|
||||
bottom: 0;
|
||||
|
@ -375,7 +375,8 @@ limitations under the License.
|
|||
margin-left: 9px;
|
||||
}
|
||||
|
||||
.mx_EventTile_line > a {
|
||||
.mx_EventTile_line > a,
|
||||
.mx_EventTile_line .mx_MessageTimestamp {
|
||||
// Align timestamps with those of normal bubble tiles
|
||||
right: auto;
|
||||
top: -11px;
|
||||
|
|
|
@ -841,24 +841,6 @@ $left-gutter: 64px;
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.mx_EventTile_senderDetails {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: calc(6px + $selected-message-border-width);
|
||||
|
||||
a {
|
||||
flex: 1;
|
||||
min-width: none;
|
||||
max-width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.mx_SenderProfile {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx_ThreadView_List {
|
||||
flex: 1;
|
||||
overflow: scroll;
|
||||
|
@ -869,7 +851,6 @@ $left-gutter: 64px;
|
|||
}
|
||||
|
||||
.mx_EventTile {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-top: 0;
|
||||
|
@ -880,11 +861,7 @@ $left-gutter: 64px;
|
|||
}
|
||||
|
||||
.mx_MessageTimestamp {
|
||||
left: auto;
|
||||
right: 2px !important;
|
||||
top: 1px !important;
|
||||
font-size: 1rem;
|
||||
text-align: right;
|
||||
font-size: $font-10px;
|
||||
}
|
||||
|
||||
.mx_ReactionsRow {
|
||||
|
@ -896,16 +873,63 @@ $left-gutter: 64px;
|
|||
}
|
||||
}
|
||||
|
||||
.mx_EventTile_content,
|
||||
.mx_RedactedBody,
|
||||
.mx_ReplyChain_wrapper {
|
||||
.mx_EventTile[data-layout=bubble] {
|
||||
margin-left: 36px;
|
||||
margin-right: 50px;
|
||||
margin-right: 36px;
|
||||
|
||||
&[data-self=true] {
|
||||
align-items: flex-end;
|
||||
|
||||
.mx_EventTile_line.mx_EventTile_mediaLine {
|
||||
margin: 0 -13px 0 0; // align with normal messages
|
||||
padding: 0 !important;
|
||||
|
||||
.mx_MFileBody ~ .mx_MessageTimestamp {
|
||||
bottom: calc($font-14px + 4px); // above the Decrypt/Download text line
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx_EventTile[data-layout=group] {
|
||||
width: 100%;
|
||||
|
||||
.mx_EventTile_content,
|
||||
.mx_RedactedBody,
|
||||
.mx_MImageBody {
|
||||
margin: 0;
|
||||
.mx_ReplyChain_wrapper {
|
||||
margin-left: 36px;
|
||||
margin-right: 50px;
|
||||
|
||||
.mx_EventTile_content,
|
||||
.mx_RedactedBody,
|
||||
.mx_MImageBody {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_MessageTimestamp {
|
||||
left: auto;
|
||||
right: 2px !important;
|
||||
top: 1px !important;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.mx_EventTile_senderDetails {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: calc(6px + $selected-message-border-width);
|
||||
|
||||
a {
|
||||
flex: 1;
|
||||
min-width: none;
|
||||
max-width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.mx_SenderProfile {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ import React from 'react';
|
|||
import { IEventRelation, MatrixEvent, Room } from 'matrix-js-sdk/src';
|
||||
import { Thread, ThreadEvent } from 'matrix-js-sdk/src/models/thread';
|
||||
import { RelationType } from 'matrix-js-sdk/src/@types/event';
|
||||
import classNames from "classnames";
|
||||
|
||||
import BaseCard from "../views/right_panel/BaseCard";
|
||||
import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases";
|
||||
|
@ -40,6 +41,7 @@ import UploadBar from './UploadBar';
|
|||
import { _t } from '../../languageHandler';
|
||||
import ThreadListContextMenu from '../views/context_menus/ThreadListContextMenu';
|
||||
import RightPanelStore from '../../stores/right-panel/RightPanelStore';
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
|
||||
interface IProps {
|
||||
room: Room;
|
||||
|
@ -53,6 +55,7 @@ interface IProps {
|
|||
}
|
||||
interface IState {
|
||||
thread?: Thread;
|
||||
layout: Layout;
|
||||
editState?: EditorStateTransfer;
|
||||
replyToEvent?: MatrixEvent;
|
||||
}
|
||||
|
@ -63,10 +66,17 @@ export default class ThreadView extends React.Component<IProps, IState> {
|
|||
|
||||
private dispatcherRef: string;
|
||||
private timelinePanelRef: React.RefObject<TimelinePanel> = React.createRef();
|
||||
private readonly layoutWatcherRef: string;
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
this.state = {
|
||||
layout: SettingsStore.getValue("layout"),
|
||||
};
|
||||
|
||||
this.layoutWatcherRef = SettingsStore.watchSetting("layout", null, (...[,,, value]) =>
|
||||
this.setState({ layout: value as Layout }),
|
||||
);
|
||||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
|
@ -82,6 +92,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
|
|||
dis.unregister(this.dispatcherRef);
|
||||
const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
|
||||
room.removeListener(ThreadEvent.New, this.onNewThread);
|
||||
SettingsStore.unwatchSetting(this.layoutWatcherRef);
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps) {
|
||||
|
@ -192,6 +203,12 @@ export default class ThreadView extends React.Component<IProps, IState> {
|
|||
event_id: this.state.thread?.id,
|
||||
};
|
||||
|
||||
const messagePanelClassNames = classNames(
|
||||
"mx_RoomView_messagePanel",
|
||||
{
|
||||
"mx_GroupLayout": this.state.layout === Layout.Group,
|
||||
});
|
||||
|
||||
return (
|
||||
<RoomContext.Provider value={{
|
||||
...this.context,
|
||||
|
@ -216,11 +233,12 @@ export default class ThreadView extends React.Component<IProps, IState> {
|
|||
timelineSet={this.state?.thread?.timelineSet}
|
||||
showUrlPreview={true}
|
||||
tileShape={TileShape.Thread}
|
||||
layout={Layout.Group}
|
||||
// ThreadView doesn't support IRC layout at this time
|
||||
layout={this.state.layout === Layout.Bubble ? Layout.Bubble : Layout.Group}
|
||||
hideThreadedMessages={false}
|
||||
hidden={false}
|
||||
showReactions={true}
|
||||
className="mx_RoomView_messagePanel mx_GroupLayout"
|
||||
className={messagePanelClassNames}
|
||||
permalinkCreator={this.props.permalinkCreator}
|
||||
membersLoaded={true}
|
||||
editState={this.state.editState}
|
||||
|
|
|
@ -23,7 +23,7 @@ import { Relations } from "matrix-js-sdk/src/models/relations";
|
|||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||
import { Thread, ThreadEvent } from 'matrix-js-sdk/src/models/thread';
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { NotificationCountType } from 'matrix-js-sdk/src/models/room';
|
||||
import { NotificationCountType, Room } from 'matrix-js-sdk/src/models/room';
|
||||
import { POLL_START_EVENT_TYPE } from "matrix-js-sdk/src/@types/polls";
|
||||
|
||||
import ReplyChain from "../elements/ReplyChain";
|
||||
|
@ -129,16 +129,15 @@ for (const evType of ALL_RULE_TYPES) {
|
|||
stateEventTileTypes[evType] = 'messages.TextualEvent';
|
||||
}
|
||||
|
||||
export function getHandlerTile(ev) {
|
||||
export function getHandlerTile(ev: MatrixEvent): string {
|
||||
const type = ev.getType();
|
||||
|
||||
// don't show verification requests we're not involved in,
|
||||
// not even when showing hidden events
|
||||
if (type === "m.room.message") {
|
||||
if (type === EventType.RoomMessage) {
|
||||
const content = ev.getContent();
|
||||
if (content && content.msgtype === MsgType.KeyVerificationRequest) {
|
||||
const client = MatrixClientPeg.get();
|
||||
const me = client && client.getUserId();
|
||||
const me = MatrixClientPeg.get()?.getUserId();
|
||||
if (ev.getSender() !== me && content.to !== me) {
|
||||
return undefined;
|
||||
} else {
|
||||
|
@ -148,20 +147,19 @@ export function getHandlerTile(ev) {
|
|||
}
|
||||
// these events are sent by both parties during verification, but we only want to render one
|
||||
// tile once the verification concludes, so filter out the one from the other party.
|
||||
if (type === "m.key.verification.done") {
|
||||
const client = MatrixClientPeg.get();
|
||||
const me = client && client.getUserId();
|
||||
if (type === EventType.KeyVerificationDone) {
|
||||
const me = MatrixClientPeg.get()?.getUserId();
|
||||
if (ev.getSender() !== me) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// sometimes MKeyVerificationConclusion declines to render. Jankily decline to render and
|
||||
// sometimes MKeyVerificationConclusion declines to render. Jankily decline to render and
|
||||
// fall back to showing hidden events, if we're viewing hidden events
|
||||
// XXX: This is extremely a hack. Possibly these components should have an interface for
|
||||
// declining to render?
|
||||
if (type === "m.key.verification.cancel" || type === "m.key.verification.done") {
|
||||
if (!MKeyVerificationConclusion.shouldRender(ev, ev.request)) {
|
||||
if (type === EventType.KeyVerificationCancel || type === EventType.KeyVerificationDone) {
|
||||
if (!MKeyVerificationConclusion.shouldRender(ev, ev.verificationRequest)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -375,8 +373,9 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
};
|
||||
|
||||
static contextType = MatrixClientContext;
|
||||
public context!: React.ContextType<typeof MatrixClientContext>;
|
||||
|
||||
constructor(props, context) {
|
||||
constructor(props: IProps, context: React.ContextType<typeof MatrixClientContext>) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
|
@ -424,7 +423,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
|
||||
// Quickly check to see if the event was sent by us. If it wasn't, it won't qualify for
|
||||
// special read receipts.
|
||||
const myUserId = MatrixClientPeg.get().getUserId();
|
||||
const myUserId = this.context.getUserId();
|
||||
if (this.props.mxEvent.getSender() !== myUserId) return false;
|
||||
|
||||
// Finally, determine if the type is relevant to the user. This notably excludes state
|
||||
|
@ -455,7 +454,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
|
||||
// If anyone has read the event besides us, we don't want to show a sent receipt.
|
||||
const receipts = this.props.readReceipts || [];
|
||||
const myUserId = MatrixClientPeg.get().getUserId();
|
||||
const myUserId = this.context.getUserId();
|
||||
if (receipts.some(r => r.userId !== myUserId)) return false;
|
||||
|
||||
// Finally, we should show a receipt.
|
||||
|
@ -511,7 +510,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
room?.on(ThreadEvent.New, this.onNewThread);
|
||||
}
|
||||
|
||||
private setupNotificationListener = (thread): void => {
|
||||
private setupNotificationListener = (thread: Thread): void => {
|
||||
const room = this.context.getRoom(this.props.mxEvent.getRoomId());
|
||||
const notifications = RoomNotificationStateStore.instance.getThreadsRoomState(room);
|
||||
|
||||
|
@ -537,7 +536,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
});
|
||||
};
|
||||
|
||||
private updateThread = (thread) => {
|
||||
private updateThread = (thread: Thread) => {
|
||||
if (thread !== this.state.thread) {
|
||||
if (this.threadState) {
|
||||
this.threadState.off(NotificationStateEvents.Update, this.onThreadStateUpdate);
|
||||
|
@ -554,7 +553,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
|
||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||
// eslint-disable-next-line
|
||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||
UNSAFE_componentWillReceiveProps(nextProps: IProps) {
|
||||
// re-check the sender verification as outgoing events progress through
|
||||
// the send process.
|
||||
if (nextProps.eventSendStatus !== this.props.eventSendStatus) {
|
||||
|
@ -562,7 +561,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState, nextContext) {
|
||||
shouldComponentUpdate(nextProps: IProps, nextState: IState): boolean {
|
||||
if (objectHasDiff(this.state, nextState)) {
|
||||
return true;
|
||||
}
|
||||
|
@ -592,7 +591,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||
componentDidUpdate(prevProps: IProps, prevState: IState, snapshot) {
|
||||
// If we're not listening for receipts and expect to be, register a listener.
|
||||
if (!this.isListeningForReceipts && (this.shouldShowSentReceipt || this.shouldShowSendingReceipt)) {
|
||||
this.context.on("Room.receipt", this.onRoomReceipt);
|
||||
|
@ -619,7 +618,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
* We currently have no reliable way to discover than an event is a thread
|
||||
* when we are at the sync stage
|
||||
*/
|
||||
const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
|
||||
const room = this.context.getRoom(this.props.mxEvent.getRoomId());
|
||||
const thread = room?.threads.get(this.props.mxEvent.getId());
|
||||
|
||||
if (!thread || thread.length === 0) {
|
||||
|
@ -692,9 +691,9 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
);
|
||||
}
|
||||
|
||||
private onRoomReceipt = (ev, room) => {
|
||||
private onRoomReceipt = (ev: MatrixEvent, room: Room): void => {
|
||||
// ignore events for other rooms
|
||||
const tileRoom = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
|
||||
const tileRoom = this.context.getRoom(this.props.mxEvent.getRoomId());
|
||||
if (room !== tileRoom) return;
|
||||
|
||||
if (!this.shouldShowSentReceipt && !this.shouldShowSendingReceipt && !this.isListeningForReceipts) {
|
||||
|
@ -722,19 +721,19 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
this.forceUpdate();
|
||||
};
|
||||
|
||||
private onDeviceVerificationChanged = (userId, device) => {
|
||||
private onDeviceVerificationChanged = (userId: string, device: string): void => {
|
||||
if (userId === this.props.mxEvent.getSender()) {
|
||||
this.verifyEvent(this.props.mxEvent);
|
||||
}
|
||||
};
|
||||
|
||||
private onUserVerificationChanged = (userId, _trustStatus) => {
|
||||
private onUserVerificationChanged = (userId: string, _trustStatus: string): void => {
|
||||
if (userId === this.props.mxEvent.getSender()) {
|
||||
this.verifyEvent(this.props.mxEvent);
|
||||
}
|
||||
};
|
||||
|
||||
private async verifyEvent(mxEvent) {
|
||||
private async verifyEvent(mxEvent: MatrixEvent): Promise<void> {
|
||||
if (!mxEvent.isEncrypted()) {
|
||||
return;
|
||||
}
|
||||
|
@ -788,7 +787,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
}, this.props.onHeightChanged); // Decryption may have caused a change in size
|
||||
}
|
||||
|
||||
private propsEqual(objA, objB) {
|
||||
private propsEqual(objA: IProps, objB: IProps): boolean {
|
||||
const keysA = Object.keys(objA);
|
||||
const keysB = Object.keys(objB);
|
||||
|
||||
|
@ -836,7 +835,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
return true;
|
||||
}
|
||||
|
||||
shouldHighlight() {
|
||||
private shouldHighlight(): boolean {
|
||||
if (this.props.forExport) return false;
|
||||
const actions = this.context.getPushActionsForEvent(this.props.mxEvent.replacingEvent() || this.props.mxEvent);
|
||||
if (!actions || !actions.tweaks) { return false; }
|
||||
|
@ -849,13 +848,13 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
return actions.tweaks.highlight;
|
||||
}
|
||||
|
||||
toggleAllReadAvatars = () => {
|
||||
private toggleAllReadAvatars = () => {
|
||||
this.setState({
|
||||
allReadAvatars: !this.state.allReadAvatars,
|
||||
});
|
||||
};
|
||||
|
||||
getReadAvatars() {
|
||||
private getReadAvatars() {
|
||||
if (this.shouldShowSentReceipt || this.shouldShowSendingReceipt) {
|
||||
return <SentReceipt messageState={this.props.mxEvent.getAssociatedStatus()} />;
|
||||
}
|
||||
|
@ -928,7 +927,8 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
/>,
|
||||
);
|
||||
}
|
||||
let remText;
|
||||
|
||||
let remText: JSX.Element;
|
||||
if (!this.state.allReadAvatars) {
|
||||
const remainder = receipts.length - MAX_READ_AVATARS;
|
||||
if (remainder > 0) {
|
||||
|
@ -950,7 +950,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
);
|
||||
}
|
||||
|
||||
onSenderProfileClick = () => {
|
||||
private onSenderProfileClick = () => {
|
||||
if (!this.props.timelineRenderingType) return;
|
||||
dis.dispatch<ComposerInsertPayload>({
|
||||
action: Action.ComposerInsert,
|
||||
|
@ -959,7 +959,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
});
|
||||
};
|
||||
|
||||
onRequestKeysClick = () => {
|
||||
private onRequestKeysClick = () => {
|
||||
this.setState({
|
||||
// Indicate in the UI that the keys have been requested (this is expected to
|
||||
// be reset if the component is mounted in the future).
|
||||
|
@ -972,7 +972,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
this.context.cancelAndResendEventRoomKeyRequest(this.props.mxEvent);
|
||||
};
|
||||
|
||||
onPermalinkClicked = e => {
|
||||
private onPermalinkClicked = e => {
|
||||
// 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 Element when clicked.
|
||||
e.preventDefault();
|
||||
|
@ -1027,17 +1027,16 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
return null;
|
||||
}
|
||||
|
||||
onActionBarFocusChange = focused => {
|
||||
this.setState({
|
||||
actionBarFocused: focused,
|
||||
});
|
||||
private onActionBarFocusChange = (actionBarFocused: boolean) => {
|
||||
this.setState({ actionBarFocused });
|
||||
};
|
||||
|
||||
// TODO: Types
|
||||
getTile: () => any | null = () => this.tile.current;
|
||||
private getTile: () => any | null = () => this.tile.current;
|
||||
|
||||
getReplyChain = () => this.replyChain.current;
|
||||
private getReplyChain = () => this.replyChain.current;
|
||||
|
||||
getReactions = () => {
|
||||
private getReactions = () => {
|
||||
if (
|
||||
!this.props.showReactions ||
|
||||
!this.props.getRelationsForEvent
|
||||
|
@ -1063,6 +1062,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
isQuoteExpanded: expanded,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const msgtype = this.props.mxEvent.getContent().msgtype;
|
||||
const eventType = this.props.mxEvent.getType() as EventType;
|
||||
|
@ -1099,6 +1099,11 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
const isRedacted = isMessageEvent(this.props.mxEvent) && this.props.isRedacted;
|
||||
const isEncryptionFailure = this.props.mxEvent.isDecryptionFailure();
|
||||
|
||||
let isContinuation = this.props.continuation;
|
||||
if (this.props.tileShape && this.props.layout !== Layout.Bubble) {
|
||||
isContinuation = false;
|
||||
}
|
||||
|
||||
const isEditing = !!this.props.editState;
|
||||
const classes = classNames({
|
||||
mx_EventTile_bubbleContainer: isBubbleMessage,
|
||||
|
@ -1111,10 +1116,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
mx_EventTile_sending: !isEditing && isSending,
|
||||
mx_EventTile_highlight: this.props.tileShape === TileShape.Notif ? false : this.shouldHighlight(),
|
||||
mx_EventTile_selected: this.props.isSelectedEvent,
|
||||
mx_EventTile_continuation: (
|
||||
(this.props.tileShape ? '' : this.props.continuation) ||
|
||||
eventType === EventType.CallInvite
|
||||
),
|
||||
mx_EventTile_continuation: isContinuation || eventType === EventType.CallInvite,
|
||||
mx_EventTile_last: this.props.last,
|
||||
mx_EventTile_lastInSection: this.props.lastInSection,
|
||||
mx_EventTile_contextual: this.props.contextual,
|
||||
|
@ -1226,7 +1228,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
|| this.state.hover
|
||||
|| this.state.actionBarFocused);
|
||||
|
||||
const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
|
||||
const room = this.context.getRoom(this.props.mxEvent.getRoomId());
|
||||
const thread = room?.findThreadForEvent?.(this.props.mxEvent);
|
||||
|
||||
// Thread panel shows the timestamp of the last reply in that thread
|
||||
|
@ -1312,20 +1314,22 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
msgOption = readAvatars;
|
||||
}
|
||||
|
||||
const replyChain = 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}
|
||||
getRelationsForEvent={this.props.getRelationsForEvent}
|
||||
/>) : null;
|
||||
const replyChain = 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}
|
||||
getRelationsForEvent={this.props.getRelationsForEvent}
|
||||
/>
|
||||
: null;
|
||||
|
||||
const isOwnEvent = this.props.mxEvent?.sender?.userId === this.context.getUserId();
|
||||
|
||||
switch (this.props.tileShape) {
|
||||
case TileShape.Notif: {
|
||||
|
@ -1372,6 +1376,8 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
"aria-atomic": true,
|
||||
"data-scroll-tokens": scrollToken,
|
||||
"data-has-reply": !!replyChain,
|
||||
"data-layout": this.props.layout,
|
||||
"data-self": isOwnEvent,
|
||||
"onMouseEnter": () => this.setState({ hover: true }),
|
||||
"onMouseLeave": () => this.setState({ hover: false }),
|
||||
}, [
|
||||
|
@ -1407,8 +1413,6 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
]);
|
||||
}
|
||||
case TileShape.ThreadPanel: {
|
||||
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
|
||||
return (
|
||||
React.createElement(this.props.as || "li", {
|
||||
|
@ -1491,8 +1495,6 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
default: {
|
||||
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
|
||||
return (
|
||||
React.createElement(this.props.as || "li", {
|
||||
|
@ -1554,7 +1556,7 @@ function isMessageEvent(ev: MatrixEvent): boolean {
|
|||
return (messageTypes.includes(ev.getType()));
|
||||
}
|
||||
|
||||
export function haveTileForEvent(e: MatrixEvent, showHiddenEvents?: boolean) {
|
||||
export function haveTileForEvent(e: MatrixEvent, showHiddenEvents?: boolean): boolean {
|
||||
// Only messages have a tile (black-rectangle) if redacted
|
||||
if (e.isRedacted() && !isMessageEvent(e)) return false;
|
||||
|
||||
|
|
Loading…
Reference in a new issue