Merge branch 'develop' into sort-imports

Signed-off-by: Aaron Raimist <aaron@raim.ist>
This commit is contained in:
Aaron Raimist 2021-12-09 08:34:20 +00:00
commit 7b94e13a84
642 changed files with 30052 additions and 8035 deletions

View file

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