Merge branch 'develop' into sort-imports
Signed-off-by: Aaron Raimist <aaron@raim.ist>
This commit is contained in:
commit
7b94e13a84
642 changed files with 30052 additions and 8035 deletions
|
@ -17,7 +17,7 @@ limitations under the License.
|
|||
|
||||
import React, { createRef } from 'react';
|
||||
import classNames from "classnames";
|
||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||
import { EventType, MsgType } from "matrix-js-sdk/src/@types/event";
|
||||
import { EventStatus, MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { Relations } from "matrix-js-sdk/src/models/relations";
|
||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||
|
@ -29,7 +29,7 @@ import { _t } from '../../../languageHandler';
|
|||
import { hasText } from "../../../TextForEvent";
|
||||
import * as sdk from "../../../index";
|
||||
import dis from '../../../dispatcher/dispatcher';
|
||||
import { Layout } from "../../../settings/Layout";
|
||||
import { Layout } from "../../../settings/enums/Layout";
|
||||
import { formatTime } from "../../../DateUtils";
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import { ALL_RULE_TYPES } from "../../../mjolnir/BanList";
|
||||
|
@ -63,10 +63,15 @@ import { dispatchShowThreadEvent } from '../../../dispatcher/dispatch-actions/th
|
|||
import { MessagePreviewStore } from '../../../stores/room-list/MessagePreviewStore';
|
||||
import { TimelineRenderingType } from "../../../contexts/RoomContext";
|
||||
import { MediaEventHelper } from "../../../utils/MediaEventHelper";
|
||||
import Toolbar from '../../../accessibility/Toolbar';
|
||||
import { POLL_START_EVENT_TYPE } from '../../../polls/consts';
|
||||
import { RovingAccessibleTooltipButton } from '../../../accessibility/roving/RovingAccessibleTooltipButton';
|
||||
import ThreadListContextMenu from '../context_menus/ThreadListContextMenu';
|
||||
|
||||
const eventTileTypes = {
|
||||
[EventType.RoomMessage]: 'messages.MessageEvent',
|
||||
[EventType.Sticker]: 'messages.MessageEvent',
|
||||
[POLL_START_EVENT_TYPE.name]: 'messages.MessageEvent',
|
||||
[EventType.KeyVerificationCancel]: 'messages.MKeyVerificationConclusion',
|
||||
[EventType.KeyVerificationDone]: 'messages.MKeyVerificationConclusion',
|
||||
[EventType.CallInvite]: 'messages.CallEvent',
|
||||
|
@ -124,7 +129,7 @@ export function getHandlerTile(ev) {
|
|||
// not even when showing hidden events
|
||||
if (type === "m.room.message") {
|
||||
const content = ev.getContent();
|
||||
if (content && content.msgtype === "m.key.verification.request") {
|
||||
if (content && content.msgtype === MsgType.KeyVerificationRequest) {
|
||||
const client = MatrixClientPeg.get();
|
||||
const me = client && client.getUserId();
|
||||
if (ev.getSender() !== me && content.to !== me) {
|
||||
|
@ -546,7 +551,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
}
|
||||
};
|
||||
|
||||
private renderThreadInfo(): React.ReactNode {
|
||||
private get thread(): Thread | null {
|
||||
if (!SettingsStore.getValue("feature_thread")) {
|
||||
return null;
|
||||
}
|
||||
|
@ -560,19 +565,55 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
|
||||
const thread = room?.threads.get(this.props.mxEvent.getId());
|
||||
|
||||
if (thread && !thread.ready) {
|
||||
thread.addEvent(this.props.mxEvent, true);
|
||||
}
|
||||
|
||||
if (!thread || this.props.showThreadInfo === false || thread.length === 0) {
|
||||
if (!thread || thread.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [lastEvent] = thread.events
|
||||
return thread;
|
||||
}
|
||||
|
||||
private renderThreadPanelSummary(): JSX.Element | null {
|
||||
if (!this.thread) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <div className="mx_ThreadPanel_replies">
|
||||
<span className="mx_ThreadPanel_repliesSummary">
|
||||
{ this.thread.length }
|
||||
</span>
|
||||
{ this.renderThreadLastMessagePreview() }
|
||||
</div>;
|
||||
}
|
||||
|
||||
private renderThreadLastMessagePreview(): JSX.Element | null {
|
||||
if (!this.thread) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [lastEvent] = this.thread.events
|
||||
.filter(event => event.isThreadRelation)
|
||||
.slice(-1);
|
||||
const threadMessagePreview = MessagePreviewStore.instance.generatePreviewForEvent(lastEvent);
|
||||
|
||||
if (!threadMessagePreview || !lastEvent.sender) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <>
|
||||
<MemberAvatar member={lastEvent.sender} width={24} height={24} className="mx_ThreadInfo_avatar" />
|
||||
<div className="mx_ThreadInfo_content">
|
||||
<span className="mx_ThreadInfo_message-preview">
|
||||
{ threadMessagePreview }
|
||||
</span>
|
||||
</div>
|
||||
</>;
|
||||
}
|
||||
|
||||
private renderThreadInfo(): React.ReactNode {
|
||||
if (!this.thread) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="mx_ThreadInfo"
|
||||
|
@ -582,20 +623,12 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
);
|
||||
}}
|
||||
>
|
||||
<span className="mx_ThreadInfo_thread-icon" />
|
||||
<span className="mx_ThreadInfo_threads-amount">
|
||||
{ _t("%(count)s reply", {
|
||||
count: thread.length,
|
||||
count: this.thread.length,
|
||||
}) }
|
||||
</span>
|
||||
{ (threadMessagePreview && lastEvent.sender) && <>
|
||||
<MemberAvatar member={lastEvent.sender} width={24} height={24} />
|
||||
<div className="mx_ThreadInfo_content">
|
||||
<span className="mx_ThreadInfo_message-preview">
|
||||
{ threadMessagePreview }
|
||||
</span>
|
||||
</div>
|
||||
</> }
|
||||
{ this.renderThreadLastMessagePreview() }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -842,7 +875,8 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
if (remainder > 0) {
|
||||
remText = <span className="mx_EventTile_readAvatarRemainder"
|
||||
onClick={this.toggleAllReadAvatars}
|
||||
style={{ right: "calc(" + toRem(-left) + " + " + receiptOffset + "px)" }}>{ remainder }+
|
||||
style={{ right: "calc(" + toRem(-left) + " + " + receiptOffset + "px)" }}
|
||||
aria-live="off">{ remainder }+
|
||||
</span>;
|
||||
}
|
||||
}
|
||||
|
@ -884,7 +918,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
// matrix.to, but also for it to enable routing within Element when clicked.
|
||||
e.preventDefault();
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
action: Action.ViewRoom,
|
||||
event_id: this.props.mxEvent.getId(),
|
||||
highlighted: true,
|
||||
room_id: this.props.mxEvent.getRoomId(),
|
||||
|
@ -955,7 +989,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
return this.props.getRelationsForEvent(eventId, "m.annotation", "m.reaction");
|
||||
};
|
||||
|
||||
private onReactionsCreated = (relationType, eventType) => {
|
||||
private onReactionsCreated = (relationType: string, eventType: string) => {
|
||||
if (relationType !== "m.annotation" || eventType !== "m.reaction") {
|
||||
return;
|
||||
}
|
||||
|
@ -1031,6 +1065,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
mx_EventTile_bad: isEncryptionFailure,
|
||||
mx_EventTile_emote: msgtype === 'm.emote',
|
||||
mx_EventTile_noSender: this.props.hideSender,
|
||||
mx_EventTile_clamp: this.props.tileShape === TileShape.ThreadPanel,
|
||||
});
|
||||
|
||||
// If the tile is in the Sending state, don't speak the message.
|
||||
|
@ -1121,6 +1156,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
onFocusChange={this.onActionBarFocusChange}
|
||||
isQuoteExpanded={isQuoteExpanded}
|
||||
toggleThreadExpanded={() => this.setQuoteExpanded(!isQuoteExpanded)}
|
||||
getRelationsForEvent={this.props.getRelationsForEvent}
|
||||
/> : undefined;
|
||||
|
||||
const showTimestamp = this.props.mxEvent.getTs()
|
||||
|
@ -1129,8 +1165,20 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
|| this.state.hover
|
||||
|| this.state.actionBarFocused);
|
||||
|
||||
const timestamp = showTimestamp ?
|
||||
<MessageTimestamp showTwelveHour={this.props.isTwelveHour} ts={this.props.mxEvent.getTs()} /> : null;
|
||||
const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
|
||||
const thread = room?.findThreadForEvent?.(this.props.mxEvent);
|
||||
|
||||
// Thread panel shows the timestamp of the last reply in that thread
|
||||
const ts = this.props.tileShape !== TileShape.ThreadPanel
|
||||
? this.props.mxEvent.getTs()
|
||||
: thread?.lastReply.getTs();
|
||||
|
||||
const timestamp = showTimestamp && ts ?
|
||||
<MessageTimestamp
|
||||
showRelative={this.props.tileShape === TileShape.ThreadPanel}
|
||||
showTwelveHour={this.props.isTwelveHour}
|
||||
ts={ts}
|
||||
/> : null;
|
||||
|
||||
const keyRequestHelpText =
|
||||
<div className="mx_EventTile_keyRequestInfo_tooltip_contents">
|
||||
|
@ -1193,6 +1241,20 @@ 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}
|
||||
/>) : null;
|
||||
|
||||
switch (this.props.tileShape) {
|
||||
case TileShape.Notif: {
|
||||
const room = this.context.getRoom(this.props.mxEvent.getRoomId());
|
||||
|
@ -1224,31 +1286,22 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
onHeightChanged={this.props.onHeightChanged}
|
||||
tileShape={this.props.tileShape}
|
||||
editState={this.props.editState}
|
||||
getRelationsForEvent={this.props.getRelationsForEvent}
|
||||
/>
|
||||
</div>,
|
||||
]);
|
||||
}
|
||||
case TileShape.Thread: {
|
||||
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}
|
||||
/>) : null;
|
||||
const room = this.context.getRoom(this.props.mxEvent.getRoomId());
|
||||
return React.createElement(this.props.as || "li", {
|
||||
"ref": this.ref,
|
||||
"className": classes,
|
||||
"aria-live": ariaLive,
|
||||
"aria-atomic": true,
|
||||
"data-scroll-tokens": scrollToken,
|
||||
"data-has-reply": !!replyChain,
|
||||
"onMouseEnter": () => this.setState({ hover: true }),
|
||||
"onMouseLeave": () => this.setState({ hover: false }),
|
||||
}, [
|
||||
<div className="mx_EventTile_roomName" key="mx_EventTile_roomName">
|
||||
<RoomAvatar room={room} width={28} height={28} />
|
||||
|
@ -1260,7 +1313,6 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
{ avatar }
|
||||
<a href={permalink} onClick={this.onPermalinkClicked}>
|
||||
{ sender }
|
||||
{ timestamp }
|
||||
</a>
|
||||
</div>,
|
||||
<div className={lineClasses} key="mx_EventTile_line">
|
||||
|
@ -1274,12 +1326,65 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
tileShape={this.props.tileShape}
|
||||
editState={this.props.editState}
|
||||
replacingEventId={this.props.replacingEventId}
|
||||
getRelationsForEvent={this.props.getRelationsForEvent}
|
||||
/>
|
||||
{ actionBar }
|
||||
{ timestamp }
|
||||
</div>,
|
||||
reactionsRow,
|
||||
]);
|
||||
}
|
||||
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", {
|
||||
"ref": this.ref,
|
||||
"className": classes,
|
||||
"tabIndex": -1,
|
||||
"aria-live": ariaLive,
|
||||
"aria-atomic": "true",
|
||||
"data-scroll-tokens": scrollToken,
|
||||
"data-layout": this.props.layout,
|
||||
"data-shape": this.props.tileShape,
|
||||
"data-self": isOwnEvent,
|
||||
"data-has-reply": !!replyChain,
|
||||
"onMouseEnter": () => this.setState({ hover: true }),
|
||||
"onMouseLeave": () => this.setState({ hover: false }),
|
||||
|
||||
}, <>
|
||||
{ sender }
|
||||
{ avatar }
|
||||
<div
|
||||
className={lineClasses}
|
||||
onClick={() => dispatchShowThreadEvent(this.props.mxEvent)}
|
||||
key="mx_EventTile_line"
|
||||
>
|
||||
{ linkedTimestamp }
|
||||
{ this.renderE2EPadlock() }
|
||||
<div className="mx_EventTile_body">
|
||||
{ MessagePreviewStore.instance.generatePreviewForEvent(this.props.mxEvent) }
|
||||
</div>
|
||||
{ this.renderThreadPanelSummary() }
|
||||
</div>
|
||||
<Toolbar className="mx_MessageActionBar" aria-label={_t("Message Actions")} aria-live="off">
|
||||
<RovingAccessibleTooltipButton
|
||||
className="mx_MessageActionBar_maskButton mx_MessageActionBar_threadButton"
|
||||
title={_t("Reply in thread")}
|
||||
onClick={() => dispatchShowThreadEvent(this.props.mxEvent)}
|
||||
key="thread"
|
||||
/>
|
||||
<ThreadListContextMenu
|
||||
mxEvent={this.props.mxEvent}
|
||||
permalinkCreator={this.props.permalinkCreator}
|
||||
onMenuToggle={this.onActionBarFocusChange}
|
||||
/>
|
||||
</Toolbar>
|
||||
{ msgOption }
|
||||
</>)
|
||||
);
|
||||
}
|
||||
case TileShape.FileGrid: {
|
||||
return React.createElement(this.props.as || "li", {
|
||||
"className": classes,
|
||||
|
@ -1296,6 +1401,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
tileShape={this.props.tileShape}
|
||||
onHeightChanged={this.props.onHeightChanged}
|
||||
editState={this.props.editState}
|
||||
getRelationsForEvent={this.props.getRelationsForEvent}
|
||||
/>
|
||||
</div>,
|
||||
<a
|
||||
|
@ -1313,19 +1419,6 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
default: {
|
||||
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}
|
||||
/>) : 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
|
||||
|
@ -1362,13 +1455,19 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
permalinkCreator={this.props.permalinkCreator}
|
||||
onHeightChanged={this.props.onHeightChanged}
|
||||
callEventGrouper={this.props.callEventGrouper}
|
||||
getRelationsForEvent={this.props.getRelationsForEvent}
|
||||
/>
|
||||
{ keyRequestInfo }
|
||||
{ actionBar }
|
||||
{ this.props.layout === Layout.IRC && (reactionsRow) }
|
||||
{ this.props.layout === Layout.IRC && <>
|
||||
{ reactionsRow }
|
||||
{ this.renderThreadInfo() }
|
||||
</> }
|
||||
</div>
|
||||
{ this.renderThreadInfo() }
|
||||
{ this.props.layout !== Layout.IRC && (reactionsRow) }
|
||||
{ this.props.layout !== Layout.IRC && <>
|
||||
{ reactionsRow }
|
||||
{ this.renderThreadInfo() }
|
||||
</> }
|
||||
{ msgOption }
|
||||
</>)
|
||||
);
|
||||
|
@ -1403,25 +1502,25 @@ export function haveTileForEvent(e: MatrixEvent, showHiddenEvents?: boolean) {
|
|||
|
||||
function E2ePadlockUndecryptable(props) {
|
||||
return (
|
||||
<E2ePadlock title={_t("This message cannot be decrypted")} icon="undecryptable" {...props} />
|
||||
<E2ePadlock title={_t("This message cannot be decrypted")} icon={E2ePadlockIcon.Warning} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
function E2ePadlockUnverified(props) {
|
||||
return (
|
||||
<E2ePadlock title={_t("Encrypted by an unverified session")} icon="unverified" {...props} />
|
||||
<E2ePadlock title={_t("Encrypted by an unverified session")} icon={E2ePadlockIcon.Warning} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
function E2ePadlockUnencrypted(props) {
|
||||
return (
|
||||
<E2ePadlock title={_t("Unencrypted")} icon="unencrypted" {...props} />
|
||||
<E2ePadlock title={_t("Unencrypted")} icon={E2ePadlockIcon.Warning} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
function E2ePadlockUnknown(props) {
|
||||
return (
|
||||
<E2ePadlock title={_t("Encrypted by a deleted session")} icon="unknown" {...props} />
|
||||
<E2ePadlock title={_t("Encrypted by a deleted session")} icon={E2ePadlockIcon.Normal} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1429,14 +1528,19 @@ function E2ePadlockUnauthenticated(props) {
|
|||
return (
|
||||
<E2ePadlock
|
||||
title={_t("The authenticity of this encrypted message can't be guaranteed on this device.")}
|
||||
icon="unauthenticated"
|
||||
icon={E2ePadlockIcon.Normal}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
enum E2ePadlockIcon {
|
||||
Normal = "normal",
|
||||
Warning = "warning",
|
||||
}
|
||||
|
||||
interface IE2ePadlockProps {
|
||||
icon: string;
|
||||
icon: E2ePadlockIcon;
|
||||
title: string;
|
||||
}
|
||||
|
||||
|
@ -1445,7 +1549,7 @@ interface IE2ePadlockState {
|
|||
}
|
||||
|
||||
class E2ePadlock extends React.Component<IE2ePadlockProps, IE2ePadlockState> {
|
||||
constructor(props) {
|
||||
constructor(props: IE2ePadlockProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
|
@ -1453,15 +1557,15 @@ class E2ePadlock extends React.Component<IE2ePadlockProps, IE2ePadlockState> {
|
|||
};
|
||||
}
|
||||
|
||||
onHoverStart = () => {
|
||||
private onHoverStart = (): void => {
|
||||
this.setState({ hover: true });
|
||||
};
|
||||
|
||||
onHoverEnd = () => {
|
||||
private onHoverEnd = (): void => {
|
||||
this.setState({ hover: false });
|
||||
};
|
||||
|
||||
render() {
|
||||
public render(): JSX.Element {
|
||||
let tooltip = null;
|
||||
if (this.state.hover) {
|
||||
tooltip = <Tooltip className="mx_EventTile_e2eIcon_tooltip" label={this.props.title} />;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue