Start DM on first message (#8612)
This commit is contained in:
parent
0e0be08781
commit
ed8ccb5d80
16 changed files with 482 additions and 65 deletions
37
src/components/structures/LargeLoader.tsx
Normal file
37
src/components/structures/LargeLoader.tsx
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
|
||||
import Spinner from "../views/elements/Spinner";
|
||||
|
||||
interface LargeLoaderProps {
|
||||
text: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loader component that displays a (almost centered) spinner and loading message.
|
||||
*/
|
||||
export const LargeLoader: React.FC<LargeLoaderProps> = ({ text }) => {
|
||||
return (
|
||||
<div className="mx_LargeLoader">
|
||||
<Spinner w={45} h={45} />
|
||||
<div className="mx_LargeLoader_text">
|
||||
{ text }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -20,7 +20,7 @@ limitations under the License.
|
|||
// TODO: This component is enormous! There's several things which could stand-alone:
|
||||
// - Search results component
|
||||
|
||||
import React, { createRef } from 'react';
|
||||
import React, { createRef, ReactElement, ReactNode, RefObject, useContext } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { IRecommendedVersion, NotificationCountType, Room, RoomEvent } from "matrix-js-sdk/src/models/room";
|
||||
import { IThreadBundledRelationship, MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/models/event";
|
||||
|
@ -46,7 +46,7 @@ import ResizeNotifier from '../../utils/ResizeNotifier';
|
|||
import ContentMessages from '../../ContentMessages';
|
||||
import Modal from '../../Modal';
|
||||
import CallHandler, { CallHandlerEvent } from '../../CallHandler';
|
||||
import dis from '../../dispatcher/dispatcher';
|
||||
import dis, { defaultDispatcher } from '../../dispatcher/dispatcher';
|
||||
import * as Rooms from '../../Rooms';
|
||||
import eventSearch, { searchPagination } from '../../Searching';
|
||||
import MainSplit from './MainSplit';
|
||||
|
@ -110,7 +110,15 @@ import FileDropTarget from './FileDropTarget';
|
|||
import Measured from '../views/elements/Measured';
|
||||
import { FocusComposerPayload } from '../../dispatcher/payloads/FocusComposerPayload';
|
||||
import { haveRendererForEvent } from "../../events/EventTileFactory";
|
||||
import { LocalRoom, LocalRoomState } from '../../models/LocalRoom';
|
||||
import { createRoomFromLocalRoom } from '../../utils/direct-messages';
|
||||
import NewRoomIntro from '../views/rooms/NewRoomIntro';
|
||||
import EncryptionEvent from '../views/messages/EncryptionEvent';
|
||||
import { StaticNotificationState } from '../../stores/notifications/StaticNotificationState';
|
||||
import { isLocalRoom } from '../../utils/localRoom/isLocalRoom';
|
||||
import { ShowThreadPayload } from "../../dispatcher/payloads/ShowThreadPayload";
|
||||
import { RoomStatusBarUnsentMessages } from './RoomStatusBarUnsentMessages';
|
||||
import { LargeLoader } from './LargeLoader';
|
||||
|
||||
const DEBUG = false;
|
||||
let debuglog = function(msg: string) {};
|
||||
|
@ -222,6 +230,137 @@ export interface IRoomState {
|
|||
narrow: boolean;
|
||||
}
|
||||
|
||||
interface LocalRoomViewProps {
|
||||
resizeNotifier: ResizeNotifier;
|
||||
permalinkCreator: RoomPermalinkCreator;
|
||||
roomView: RefObject<HTMLElement>;
|
||||
onFileDrop: (dataTransfer: DataTransfer) => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Local room view. Uses only the bits necessary to display a local room view like room header or composer.
|
||||
*
|
||||
* @param {LocalRoomViewProps} props Room view props
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
function LocalRoomView(props: LocalRoomViewProps): ReactElement {
|
||||
const context = useContext(RoomContext);
|
||||
const room = context.room as LocalRoom;
|
||||
const encryptionEvent = context.room.currentState.getStateEvents(EventType.RoomEncryption)[0];
|
||||
let encryptionTile: ReactNode;
|
||||
|
||||
if (encryptionEvent) {
|
||||
encryptionTile = <EncryptionEvent mxEvent={encryptionEvent} />;
|
||||
}
|
||||
|
||||
const onRetryClicked = () => {
|
||||
room.state = LocalRoomState.NEW;
|
||||
defaultDispatcher.dispatch({
|
||||
action: "local_room_event",
|
||||
roomId: room.roomId,
|
||||
});
|
||||
};
|
||||
|
||||
let statusBar: ReactElement;
|
||||
let composer: ReactElement;
|
||||
|
||||
if (room.isError) {
|
||||
const buttons = (
|
||||
<AccessibleButton onClick={onRetryClicked} className="mx_RoomStatusBar_unsentRetry">
|
||||
{ _t("Retry") }
|
||||
</AccessibleButton>
|
||||
);
|
||||
|
||||
statusBar = <RoomStatusBarUnsentMessages
|
||||
title={_t("Some of your messages have not been sent")}
|
||||
notificationState={StaticNotificationState.RED_EXCLAMATION}
|
||||
buttons={buttons}
|
||||
/>;
|
||||
} else {
|
||||
composer = <MessageComposer
|
||||
room={context.room}
|
||||
resizeNotifier={props.resizeNotifier}
|
||||
permalinkCreator={props.permalinkCreator}
|
||||
/>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_RoomView mx_RoomView--local">
|
||||
<ErrorBoundary>
|
||||
<RoomHeader
|
||||
room={context.room}
|
||||
searchInfo={null}
|
||||
inRoom={true}
|
||||
onSearchClick={null}
|
||||
onInviteClick={null}
|
||||
onForgetClick={null}
|
||||
e2eStatus={E2EStatus.Normal}
|
||||
onAppsClick={null}
|
||||
appsShown={false}
|
||||
onCallPlaced={null}
|
||||
excludedRightPanelPhaseButtons={[]}
|
||||
showButtons={false}
|
||||
enableRoomOptionsMenu={false}
|
||||
/>
|
||||
<main className="mx_RoomView_body" ref={props.roomView}>
|
||||
<FileDropTarget parent={props.roomView.current} onFileDrop={props.onFileDrop} />
|
||||
<div className="mx_RoomView_timeline">
|
||||
<ScrollPanel
|
||||
className="mx_RoomView_messagePanel"
|
||||
resizeNotifier={props.resizeNotifier}
|
||||
>
|
||||
{ encryptionTile }
|
||||
<NewRoomIntro />
|
||||
</ScrollPanel>
|
||||
</div>
|
||||
{ statusBar }
|
||||
{ composer }
|
||||
</main>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface ILocalRoomCreateLoaderProps {
|
||||
names: string;
|
||||
resizeNotifier: ResizeNotifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Room create loader view displaying a message and a spinner.
|
||||
*
|
||||
* @param {ILocalRoomCreateLoaderProps} props Room view props
|
||||
* @return {ReactElement}
|
||||
*/
|
||||
function LocalRoomCreateLoader(props: ILocalRoomCreateLoaderProps): ReactElement {
|
||||
const context = useContext(RoomContext);
|
||||
const text = _t("We're creating a room with %(names)s", { names: props.names });
|
||||
return (
|
||||
<div className="mx_RoomView mx_RoomView--local">
|
||||
<ErrorBoundary>
|
||||
<RoomHeader
|
||||
room={context.room}
|
||||
searchInfo={null}
|
||||
inRoom={true}
|
||||
onSearchClick={null}
|
||||
onInviteClick={null}
|
||||
onForgetClick={null}
|
||||
e2eStatus={E2EStatus.Normal}
|
||||
onAppsClick={null}
|
||||
appsShown={false}
|
||||
onCallPlaced={null}
|
||||
excludedRightPanelPhaseButtons={[]}
|
||||
showButtons={false}
|
||||
enableRoomOptionsMenu={false}
|
||||
/>
|
||||
<div className="mx_RoomView_body">
|
||||
<LargeLoader text={text} />
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||
private readonly dispatcherRef: string;
|
||||
private readonly roomStoreToken: EventSubscription;
|
||||
|
@ -765,6 +904,11 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
for (const watcher of this.settingWatchers) {
|
||||
SettingsStore.unwatchSetting(watcher);
|
||||
}
|
||||
|
||||
if (this.viewsLocalRoom) {
|
||||
// clean up if this was a local room
|
||||
this.props.mxClient.store.removeRoom(this.state.room.roomId);
|
||||
}
|
||||
}
|
||||
|
||||
private onRightPanelStoreUpdate = () => {
|
||||
|
@ -873,6 +1017,10 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
this.onSearchClick();
|
||||
break;
|
||||
|
||||
case 'local_room_event':
|
||||
this.onLocalRoomEvent(payload.roomId);
|
||||
break;
|
||||
|
||||
case Action.EditEvent: {
|
||||
// Quit early if we're trying to edit events in wrong rendering context
|
||||
if (payload.timelineRenderingType !== this.state.timelineRenderingType) return;
|
||||
|
@ -925,6 +1073,11 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
}
|
||||
};
|
||||
|
||||
private onLocalRoomEvent(roomId: string) {
|
||||
if (roomId !== this.state.room.roomId) return;
|
||||
createRoomFromLocalRoom(this.props.mxClient, this.state.room as LocalRoom);
|
||||
}
|
||||
|
||||
private onRoomTimeline = (ev: MatrixEvent, room: Room | null, toStartOfTimeline: boolean, removed, data) => {
|
||||
if (this.unmounted) return;
|
||||
|
||||
|
@ -1494,7 +1647,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
searchResult={result}
|
||||
searchHighlights={this.state.searchHighlights}
|
||||
resultLink={resultLink}
|
||||
permalinkCreator={this.getPermalinkCreatorForRoom(room)}
|
||||
permalinkCreator={this.permalinkCreator}
|
||||
onHeightChanged={onHeightChanged}
|
||||
/>);
|
||||
}
|
||||
|
@ -1769,7 +1922,44 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
this.setState({ narrow });
|
||||
};
|
||||
|
||||
private get viewsLocalRoom(): boolean {
|
||||
return isLocalRoom(this.state.room);
|
||||
}
|
||||
|
||||
private get permalinkCreator(): RoomPermalinkCreator {
|
||||
return this.getPermalinkCreatorForRoom(this.state.room);
|
||||
}
|
||||
|
||||
private renderLocalRoomCreateLoader(): ReactElement {
|
||||
const names = this.state.room.getDefaultRoomName(this.props.mxClient.getUserId());
|
||||
return <RoomContext.Provider value={this.state}>
|
||||
<LocalRoomCreateLoader
|
||||
names={names}
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
/>
|
||||
</RoomContext.Provider>;
|
||||
}
|
||||
|
||||
private renderLocalRoomView(): ReactElement {
|
||||
return <RoomContext.Provider value={this.state}>
|
||||
<LocalRoomView
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
permalinkCreator={this.permalinkCreator}
|
||||
roomView={this.roomView}
|
||||
onFileDrop={this.onFileDrop}
|
||||
/>
|
||||
</RoomContext.Provider>;
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.room instanceof LocalRoom) {
|
||||
if (this.state.room.state === LocalRoomState.CREATING) {
|
||||
return this.renderLocalRoomCreateLoader();
|
||||
}
|
||||
|
||||
return this.renderLocalRoomView();
|
||||
}
|
||||
|
||||
if (!this.state.room) {
|
||||
const loading = !this.state.matrixClientIsReady || this.state.roomLoading || this.state.peekLoading;
|
||||
if (loading) {
|
||||
|
@ -2027,7 +2217,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
e2eStatus={this.state.e2eStatus}
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
replyToEvent={this.state.replyToEvent}
|
||||
permalinkCreator={this.getPermalinkCreatorForRoom(this.state.room)}
|
||||
permalinkCreator={this.permalinkCreator}
|
||||
/>;
|
||||
}
|
||||
|
||||
|
@ -2093,7 +2283,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
showUrlPreview={this.state.showUrlPreview}
|
||||
className={this.messagePanelClassNames}
|
||||
membersLoaded={this.state.membersLoaded}
|
||||
permalinkCreator={this.getPermalinkCreatorForRoom(this.state.room)}
|
||||
permalinkCreator={this.permalinkCreator}
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
showReactions={true}
|
||||
layout={this.state.layout}
|
||||
|
@ -2123,7 +2313,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
? <RightPanel
|
||||
room={this.state.room}
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
permalinkCreator={this.getPermalinkCreatorForRoom(this.state.room)}
|
||||
permalinkCreator={this.permalinkCreator}
|
||||
e2eStatus={this.state.e2eStatus} />
|
||||
: null;
|
||||
|
||||
|
@ -2237,6 +2427,8 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
appsShown={this.state.showApps}
|
||||
onCallPlaced={onCallPlaced}
|
||||
excludedRightPanelPhaseButtons={excludedRightPanelPhaseButtons}
|
||||
showButtons={!this.viewsLocalRoom}
|
||||
enableRoomOptionsMenu={!this.viewsLocalRoom}
|
||||
/>
|
||||
<MainSplit panel={rightPanel} resizeNotifier={this.props.resizeNotifier}>
|
||||
<div className={mainSplitContentClasses} ref={this.roomViewBody} data-layout={this.state.layout}>
|
||||
|
|
|
@ -62,11 +62,16 @@ import CopyableText from "../elements/CopyableText";
|
|||
import { ScreenName } from '../../../PosthogTrackers';
|
||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||
import { DirectoryMember, IDMUserTileProps, Member, ThreepidMember } from "../../../utils/direct-messages";
|
||||
import {
|
||||
DirectoryMember,
|
||||
IDMUserTileProps,
|
||||
Member,
|
||||
startDmOnFirstMessage,
|
||||
ThreepidMember,
|
||||
} from "../../../utils/direct-messages";
|
||||
import { AnyInviteKind, KIND_CALL_TRANSFER, KIND_DM, KIND_INVITE } from './InviteDialogTypes';
|
||||
import Modal from '../../../Modal';
|
||||
import dis from "../../../dispatcher/dispatcher";
|
||||
import { startDm } from '../../../utils/dm/startDm';
|
||||
|
||||
// we have a number of types defined from the Matrix spec which can't reasonably be altered here.
|
||||
/* eslint-disable camelcase */
|
||||
|
@ -446,11 +451,10 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
|||
}
|
||||
|
||||
private startDm = async () => {
|
||||
this.setState({ busy: true });
|
||||
try {
|
||||
const cli = MatrixClientPeg.get();
|
||||
const targets = this.convertFilter();
|
||||
await startDm(cli, targets);
|
||||
startDmOnFirstMessage(cli, targets);
|
||||
this.props.onFinished(true);
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
|
@ -458,8 +462,6 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
|||
busy: false,
|
||||
errorText: _t("We couldn't create your DM."),
|
||||
});
|
||||
} finally {
|
||||
this.setState({ busy: false });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ import { RecentAlgorithm } from "../../../../stores/room-list/algorithms/tag-sor
|
|||
import { RoomViewStore } from "../../../../stores/RoomViewStore";
|
||||
import { getMetaSpaceName } from "../../../../stores/spaces";
|
||||
import SpaceStore from "../../../../stores/spaces/SpaceStore";
|
||||
import { DirectoryMember, Member } from "../../../../utils/direct-messages";
|
||||
import { DirectoryMember, Member, startDmOnFirstMessage } from "../../../../utils/direct-messages";
|
||||
import DMRoomMap from "../../../../utils/DMRoomMap";
|
||||
import { makeUserPermalink } from "../../../../utils/permalinks/Permalinks";
|
||||
import { buildActivityScores, buildMemberScores, compareMembers } from "../../../../utils/SortMembers";
|
||||
|
@ -92,7 +92,6 @@ import { RoomResultContextMenus } from "./RoomResultContextMenus";
|
|||
import { RoomContextDetails } from "../../rooms/RoomContextDetails";
|
||||
import { TooltipOption } from "./TooltipOption";
|
||||
import { isLocalRoom } from "../../../../utils/localRoom/isLocalRoom";
|
||||
import { startDm } from "../../../../utils/dm/startDm";
|
||||
|
||||
const MAX_RECENT_SEARCHES = 10;
|
||||
const SECTION_LIMIT = 50; // only show 50 results per section for performance reasons
|
||||
|
@ -593,7 +592,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
|||
id={`mx_SpotlightDialog_button_result_${result.member.userId}`}
|
||||
key={`${Section[result.section]}-${result.member.userId}`}
|
||||
onClick={() => {
|
||||
startDm(cli, [result.member]);
|
||||
startDmOnFirstMessage(cli, [result.member]);
|
||||
onFinished();
|
||||
}}
|
||||
aria-label={result.member instanceof RoomMember
|
||||
|
|
|
@ -33,7 +33,6 @@ import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
|
|||
import dis from '../../../dispatcher/dispatcher';
|
||||
import Modal from '../../../Modal';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import createRoom from '../../../createRoom';
|
||||
import DMRoomMap from '../../../utils/DMRoomMap';
|
||||
import AccessibleButton, { ButtonEvent } from '../elements/AccessibleButton';
|
||||
import SdkConfig from '../../../SdkConfig';
|
||||
|
@ -78,8 +77,7 @@ import { IRightPanelCardState } from '../../../stores/right-panel/RightPanelStor
|
|||
import UserIdentifierCustomisations from '../../../customisations/UserIdentifier';
|
||||
import PosthogTrackers from "../../../PosthogTrackers";
|
||||
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { privateShouldBeEncrypted } from "../../../utils/rooms";
|
||||
import { findDMForUser } from '../../../utils/dm/findDMForUser';
|
||||
import { DirectoryMember, startDmOnFirstMessage } from '../../../utils/direct-messages';
|
||||
|
||||
export interface IDevice {
|
||||
deviceId: string;
|
||||
|
@ -124,38 +122,13 @@ export const getE2EStatus = (cli: MatrixClient, userId: string, devices: IDevice
|
|||
return anyDeviceUnverified ? E2EStatus.Warning : E2EStatus.Verified;
|
||||
};
|
||||
|
||||
async function openDMForUser(matrixClient: MatrixClient, userId: string, viaKeyboard = false): Promise<void> {
|
||||
const lastActiveRoom = findDMForUser(matrixClient, userId);
|
||||
|
||||
if (lastActiveRoom) {
|
||||
dis.dispatch<ViewRoomPayload>({
|
||||
action: Action.ViewRoom,
|
||||
room_id: lastActiveRoom.roomId,
|
||||
metricsTrigger: "MessageUser",
|
||||
metricsViaKeyboard: viaKeyboard,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const createRoomOptions = {
|
||||
dmUserId: userId,
|
||||
encryption: undefined,
|
||||
};
|
||||
|
||||
if (privateShouldBeEncrypted()) {
|
||||
// Check whether all users have uploaded device keys before.
|
||||
// If so, enable encryption in the new room.
|
||||
const usersToDevicesMap = await matrixClient.downloadKeys([userId]);
|
||||
const allHaveDeviceKeys = Object.values(usersToDevicesMap).every(devices => {
|
||||
// `devices` is an object of the form { deviceId: deviceInfo, ... }.
|
||||
return Object.keys(devices).length > 0;
|
||||
});
|
||||
if (allHaveDeviceKeys) {
|
||||
createRoomOptions.encryption = true;
|
||||
}
|
||||
}
|
||||
|
||||
await createRoom(createRoomOptions);
|
||||
async function openDMForUser(matrixClient: MatrixClient, user: RoomMember): Promise<void> {
|
||||
const startDMUser = new DirectoryMember({
|
||||
user_id: user.userId,
|
||||
display_name: user.rawDisplayName,
|
||||
avatar_url: user.getMxcAvatarUrl(),
|
||||
});
|
||||
startDmOnFirstMessage(matrixClient, [startDMUser]);
|
||||
}
|
||||
|
||||
type SetUpdating = (updating: boolean) => void;
|
||||
|
@ -328,17 +301,17 @@ function DevicesSection({ devices, userId, loading }: { devices: IDevice[], user
|
|||
);
|
||||
}
|
||||
|
||||
const MessageButton = ({ userId }: { userId: string }) => {
|
||||
const MessageButton = ({ member }: { member: RoomMember }) => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
const [busy, setBusy] = useState(false);
|
||||
|
||||
return (
|
||||
<AccessibleButton
|
||||
kind="link"
|
||||
onClick={async (ev) => {
|
||||
onClick={async () => {
|
||||
if (busy) return;
|
||||
setBusy(true);
|
||||
await openDMForUser(cli, userId, ev.type !== "click");
|
||||
await openDMForUser(cli, member);
|
||||
setBusy(false);
|
||||
}}
|
||||
className="mx_UserInfo_field"
|
||||
|
@ -484,7 +457,7 @@ const UserOptionsSection: React.FC<{
|
|||
|
||||
let directMessageButton: JSX.Element;
|
||||
if (!isMe) {
|
||||
directMessageButton = <MessageButton userId={member.userId} />;
|
||||
directMessageButton = <MessageButton member={member} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -948,7 +948,6 @@ export class UnwrappedEventTile extends React.Component<IProps, IState> {
|
|||
isSeeingThroughMessageHiddenForModeration,
|
||||
} = getEventDisplayInfo(this.props.mxEvent, this.context.showHiddenEvents, this.shouldHideEvent());
|
||||
const { isQuoteExpanded } = this.state;
|
||||
|
||||
// This shouldn't happen: the caller should check we support this type
|
||||
// before trying to instantiate us
|
||||
if (!hasRenderer) {
|
||||
|
|
|
@ -3156,6 +3156,7 @@
|
|||
"You can select all or individual messages to retry or delete": "You can select all or individual messages to retry or delete",
|
||||
"Connectivity to the server has been lost.": "Connectivity to the server has been lost.",
|
||||
"Sent messages will be stored until your connection has returned.": "Sent messages will be stored until your connection has returned.",
|
||||
"We're creating a room with %(names)s": "We're creating a room with %(names)s",
|
||||
"You seem to be uploading files, are you sure you want to quit?": "You seem to be uploading files, are you sure you want to quit?",
|
||||
"You seem to be in a call, are you sure you want to quit?": "You seem to be in a call, are you sure you want to quit?",
|
||||
"Search failed": "Search failed",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue