diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js
index e2e3592536..375545f819 100644
--- a/src/components/structures/MessagePanel.js
+++ b/src/components/structures/MessagePanel.js
@@ -30,6 +30,8 @@ import {_t} from "../../languageHandler";
import {haveTileForEvent} from "../views/rooms/EventTile";
import {textForEvent} from "../../TextForEvent";
import IRCTimelineProfileResizer from "../views/elements/IRCTimelineProfileResizer";
+import DMRoomMap from "../../utils/DMRoomMap";
+import NewRoomIntro from "../views/rooms/NewRoomIntro";
const CONTINUATION_MAX_INTERVAL = 5 * 60 * 1000; // 5 minutes
const continuedTypes = ['m.sticker', 'm.room.message'];
@@ -952,15 +954,25 @@ class CreationGrouper {
}).reduce((a, b) => a.concat(b), []);
// Get sender profile from the latest event in the summary as the m.room.create doesn't contain one
const ev = this.events[this.events.length - 1];
+
+ let summaryText;
+ const roomId = ev.getRoomId();
+ const creator = ev.sender ? ev.sender.name : ev.getSender();
+ if (DMRoomMap.shared().getUserIdForRoomId(roomId)) {
+ summaryText = _t("%(creator)s created this DM.", { creator });
+ } else {
+ summaryText = _t("%(creator)s created and configured the room.", { creator });
+ }
+
+ ret.push();
+
ret.push(
{ eventTiles }
,
diff --git a/src/components/structures/RoomStatusBar.js b/src/components/structures/RoomStatusBar.js
index e390be6979..e6d2985073 100644
--- a/src/components/structures/RoomStatusBar.js
+++ b/src/components/structures/RoomStatusBar.js
@@ -41,9 +41,6 @@ export default class RoomStatusBar extends React.Component {
static propTypes = {
// the room this statusbar is representing.
room: PropTypes.object.isRequired,
- // This is true when the user is alone in the room, but has also sent a message.
- // Used to suggest to the user to invite someone
- sentMessageAndIsAlone: PropTypes.bool,
// The active call in the room, if any (means we show the call bar
// along with the status of the call)
@@ -68,10 +65,6 @@ export default class RoomStatusBar extends React.Component {
// 'you are alone' bar
onInviteClick: PropTypes.func,
- // callback for when the user clicks on the 'stop warning me' button in the
- // 'you are alone' bar
- onStopWarningClick: PropTypes.func,
-
// callback for when we do something that changes the size of the
// status bar. This is used to trigger a re-layout in the parent
// component.
@@ -159,10 +152,7 @@ export default class RoomStatusBar extends React.Component {
// changed - so we use '0' to indicate normal size, and other values to
// indicate other sizes.
_getSize() {
- if (this._shouldShowConnectionError() ||
- this._showCallBar() ||
- this.props.sentMessageAndIsAlone
- ) {
+ if (this._shouldShowConnectionError() || this._showCallBar()) {
return STATUS_BAR_EXPANDED;
} else if (this.state.unsentMessages.length > 0) {
return STATUS_BAR_EXPANDED_LARGE;
@@ -325,24 +315,6 @@ export default class RoomStatusBar extends React.Component {
);
}
- // If you're alone in the room, and have sent a message, suggest to invite someone
- if (this.props.sentMessageAndIsAlone && !this.props.isPeeking) {
- return (
-
- { _t("There's no one else here! Would you like to invite others " +
- "or stop warning about the empty room?",
- {},
- {
- 'inviteText': (sub) =>
- { sub },
- 'nowarnText': (sub) =>
- { sub },
- },
- ) }
-
- );
- }
-
return null;
}
diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx
index 1c2bf3a000..229416d40a 100644
--- a/src/components/structures/RoomView.tsx
+++ b/src/components/structures/RoomView.tsx
@@ -150,7 +150,6 @@ export interface IState {
guestsCanJoin: boolean;
canPeek: boolean;
showApps: boolean;
- isAlone: boolean;
isPeeking: boolean;
showingPinned: boolean;
showReadReceipts: boolean;
@@ -223,7 +222,6 @@ export default class RoomView extends React.Component {
guestsCanJoin: false,
canPeek: false,
showApps: false,
- isAlone: false,
isPeeking: false,
showingPinned: false,
showReadReceipts: true,
@@ -705,9 +703,8 @@ export default class RoomView extends React.Component {
private onAction = payload => {
switch (payload.action) {
- case 'message_send_failed':
case 'message_sent':
- this.checkIfAlone(this.state.room);
+ this.checkDesktopNotifications();
break;
case 'post_sticker_message':
this.injectSticker(
@@ -1025,36 +1022,15 @@ export default class RoomView extends React.Component {
}
// rate limited because a power level change will emit an event for every member in the room.
- private updateRoomMembers = rateLimitedFunc((dueToMember) => {
+ private updateRoomMembers = rateLimitedFunc(() => {
this.updateDMState();
-
- let memberCountInfluence = 0;
- if (dueToMember && dueToMember.membership === "invite" && this.state.room.getInvitedMemberCount() === 0) {
- // A member got invited, but the room hasn't detected that change yet. Influence the member
- // count by 1 to counteract this.
- memberCountInfluence = 1;
- }
- this.checkIfAlone(this.state.room, memberCountInfluence);
-
this.updateE2EStatus(this.state.room);
}, 500);
- private checkIfAlone(room: Room, countInfluence?: number) {
- let warnedAboutLonelyRoom = false;
- if (localStorage) {
- warnedAboutLonelyRoom = Boolean(localStorage.getItem('mx_user_alone_warned_' + this.state.room.roomId));
- }
- if (warnedAboutLonelyRoom) {
- if (this.state.isAlone) this.setState({isAlone: false});
- return;
- }
-
- let joinedOrInvitedMemberCount = room.getJoinedMemberCount() + room.getInvitedMemberCount();
- if (countInfluence) joinedOrInvitedMemberCount += countInfluence;
- this.setState({isAlone: joinedOrInvitedMemberCount === 1});
-
- // if they are not alone additionally prompt the user about notifications so they don't miss replies
- if (joinedOrInvitedMemberCount > 1 && Notifier.shouldShowPrompt()) {
+ private checkDesktopNotifications() {
+ const memberCount = this.state.room.getJoinedMemberCount() + this.state.room.getInvitedMemberCount();
+ // if they are not alone prompt the user about notifications so they don't miss replies
+ if (memberCount > 1 && Notifier.shouldShowPrompt()) {
showNotificationsToast(true);
}
}
@@ -1091,14 +1067,6 @@ export default class RoomView extends React.Component {
action: 'view_invite',
roomId: this.state.room.roomId,
});
- this.setState({isAlone: false}); // there's a good chance they'll invite someone
- };
-
- private onStopAloneWarningClick = () => {
- if (localStorage) {
- localStorage.setItem('mx_user_alone_warned_' + this.state.room.roomId, String(true));
- }
- this.setState({isAlone: false});
};
private onJoinButtonClicked = () => {
@@ -1797,12 +1765,10 @@ export default class RoomView extends React.Component {
isStatusAreaExpanded = this.state.statusBarVisible;
statusBar = ;
diff --git a/src/components/views/avatars/RoomAvatar.tsx b/src/components/views/avatars/RoomAvatar.tsx
index cbdae765f7..98d69a63e7 100644
--- a/src/components/views/avatars/RoomAvatar.tsx
+++ b/src/components/views/avatars/RoomAvatar.tsx
@@ -35,6 +35,7 @@ interface IProps {
height?: number;
resizeMethod?: ResizeMethod;
viewAvatarOnClick?: boolean;
+ onClick?(): void;
}
interface IState {
@@ -130,7 +131,7 @@ export default class RoomAvatar extends React.Component {
};
public render() {
- const {room, oobData, viewAvatarOnClick, ...otherProps} = this.props;
+ const {room, oobData, viewAvatarOnClick, onClick, ...otherProps} = this.props;
const roomName = room ? room.name : oobData.name;
@@ -139,7 +140,7 @@ export default class RoomAvatar extends React.Component {
name={roomName}
idName={room ? room.roomId : null}
urls={this.state.urls}
- onClick={viewAvatarOnClick && this.state.urls[0] ? this.onRoomAvatarClick : null}
+ onClick={viewAvatarOnClick && this.state.urls[0] ? this.onRoomAvatarClick : onClick}
/>
);
}
diff --git a/src/components/views/elements/MiniAvatarUploader.tsx b/src/components/views/elements/MiniAvatarUploader.tsx
new file mode 100644
index 0000000000..b5e117b42a
--- /dev/null
+++ b/src/components/views/elements/MiniAvatarUploader.tsx
@@ -0,0 +1,90 @@
+/*
+Copyright 2020 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, {useContext, useRef, useState} from 'react';
+import classNames from 'classnames';
+
+import AccessibleButton from "./AccessibleButton";
+import Tooltip from './Tooltip';
+import MatrixClientContext from "../../../contexts/MatrixClientContext";
+import {useTimeout} from "../../../hooks/useTimeout";
+
+export const AVATAR_SIZE = 52;
+
+interface IProps {
+ hasAvatar: boolean;
+ noAvatarLabel?: string;
+ hasAvatarLabel?: string;
+ setAvatarUrl(url: string): Promise;
+}
+
+const MiniAvatarUploader: React.FC = ({ hasAvatar, hasAvatarLabel, noAvatarLabel, setAvatarUrl, children }) => {
+ const cli = useContext(MatrixClientContext);
+ const [busy, setBusy] = useState(false);
+ const [hover, setHover] = useState(false);
+ const [show, setShow] = useState(false);
+
+ useTimeout(() => {
+ setShow(true);
+ }, 3000); // show after 3 seconds
+ useTimeout(() => {
+ setShow(false);
+ }, 13000); // hide after being shown for 10 seconds
+
+ const uploadRef = useRef();
+
+ const label = (hasAvatar || busy) ? hasAvatarLabel : noAvatarLabel;
+
+ return
+ {
+ if (!ev.target.files?.length) return;
+ setBusy(true);
+ const file = ev.target.files[0];
+ const uri = await cli.uploadContent(file);
+ await setAvatarUrl(uri);
+ setBusy(false);
+ }}
+ accept="image/*"
+ />
+
+ {
+ uploadRef.current.click();
+ }}
+ onMouseOver={() => setHover(true)}
+ onMouseLeave={() => setHover(false)}
+ >
+ { children }
+
+
+
+ ;
+};
+
+export default MiniAvatarUploader;
diff --git a/src/components/views/messages/EncryptionEvent.js b/src/components/views/messages/EncryptionEvent.js
deleted file mode 100644
index a9ce10d202..0000000000
--- a/src/components/views/messages/EncryptionEvent.js
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
-Copyright 2020 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 PropTypes from 'prop-types';
-import { _t } from '../../../languageHandler';
-import { MatrixClientPeg } from '../../../MatrixClientPeg';
-
-export default class EncryptionEvent extends React.Component {
- render() {
- const {mxEvent} = this.props;
-
- let body;
- let classes = "mx_EventTile_bubble mx_cryptoEvent mx_cryptoEvent_icon";
- const isRoomEncrypted = MatrixClientPeg.get().isRoomEncrypted(mxEvent.getRoomId());
- if (mxEvent.getContent().algorithm === 'm.megolm.v1.aes-sha2' && isRoomEncrypted) {
- body =
-
{_t("Encryption enabled")}
-
- {_t(
- "Messages in this room are end-to-end encrypted. " +
- "Learn more & verify this user in their user profile.",
- )}
-
-
;
- } else if (isRoomEncrypted) {
- body =
-
{_t("Encryption enabled")}
-
- {_t("Ignored attempt to disable encryption")}
-
-
;
- } else {
- body =
-
{_t("Encryption not enabled")}
-
{_t("The encryption used by this room isn't supported.")}
);
- }
-}
-
-EncryptionEvent.propTypes = {
- /* the MatrixEvent to show */
- mxEvent: PropTypes.object.isRequired,
-};
diff --git a/src/components/views/messages/EncryptionEvent.tsx b/src/components/views/messages/EncryptionEvent.tsx
new file mode 100644
index 0000000000..3af9c463c9
--- /dev/null
+++ b/src/components/views/messages/EncryptionEvent.tsx
@@ -0,0 +1,68 @@
+/*
+Copyright 2020 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, {forwardRef, useContext} from 'react';
+import {MatrixEvent} from "matrix-js-sdk/src/models/event";
+
+import { _t } from '../../../languageHandler';
+import { MatrixClientPeg } from '../../../MatrixClientPeg';
+import EventTileBubble from "./EventTileBubble";
+import MatrixClientContext from "../../../contexts/MatrixClientContext";
+import DMRoomMap from "../../../utils/DMRoomMap";
+
+interface IProps {
+ mxEvent: MatrixEvent;
+}
+
+const EncryptionEvent = forwardRef(({mxEvent}, ref) => {
+ const cli = useContext(MatrixClientContext);
+ const roomId = mxEvent.getRoomId();
+ const isRoomEncrypted = MatrixClientPeg.get().isRoomEncrypted(roomId);
+
+ if (mxEvent.getContent().algorithm === 'm.megolm.v1.aes-sha2' && isRoomEncrypted) {
+ let subtitle: string;
+ const dmPartner = DMRoomMap.shared().getUserIdForRoomId(roomId);
+ if (dmPartner) {
+ const displayName = cli?.getRoom(roomId)?.getMember(dmPartner)?.rawDisplayName || dmPartner;
+ subtitle = _t("Messages here are end-to-end encrypted. " +
+ "Verify %(displayName)s in their profile - tap on their avatar.", { displayName });
+ } else {
+ subtitle = _t("Messages in this room are end-to-end encrypted. " +
+ "When people join, you can verify them in their profile, just tap on their avatar.");
+ }
+
+ return ;
+ } else if (isRoomEncrypted) {
+ return ;
+ }
+
+ return ;
+});
+
+export default EncryptionEvent;
diff --git a/src/components/views/messages/EventTileBubble.tsx b/src/components/views/messages/EventTileBubble.tsx
new file mode 100644
index 0000000000..f797a97a3d
--- /dev/null
+++ b/src/components/views/messages/EventTileBubble.tsx
@@ -0,0 +1,34 @@
+/*
+Copyright 2020 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, {forwardRef, ReactNode} from "react";
+import classNames from "classnames";
+
+interface IProps {
+ className: string;
+ title: string;
+ subtitle?: ReactNode;
+}
+
+const EventTileBubble = forwardRef(({ className, title, subtitle, children }, ref) => {
+ return
+
{ title }
+ { subtitle &&
{ subtitle }
}
+ { children }
+
;
+});
+
+export default EventTileBubble;
diff --git a/src/components/views/messages/MJitsiWidgetEvent.tsx b/src/components/views/messages/MJitsiWidgetEvent.tsx
index 3d191209f9..82aa32d3b7 100644
--- a/src/components/views/messages/MJitsiWidgetEvent.tsx
+++ b/src/components/views/messages/MJitsiWidgetEvent.tsx
@@ -18,6 +18,7 @@ import React from 'react';
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { _t } from "../../../languageHandler";
import WidgetStore from "../../../stores/WidgetStore";
+import EventTileBubble from "./EventTileBubble";
interface IProps {
mxEvent: MatrixEvent;
@@ -40,37 +41,24 @@ export default class MJitsiWidgetEvent extends React.PureComponent {
if (!url) {
// removed
- return (
-
-
- {_t('Video conference ended by %(senderName)s', {senderName})}
-
;
+ );
+
+ return ;
}
}
diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js
index 48ab6831d9..c358ef610d 100644
--- a/src/components/views/rooms/EventTile.js
+++ b/src/components/views/rooms/EventTile.js
@@ -21,6 +21,7 @@ import ReplyThread from "../elements/ReplyThread";
import React, {createRef} from 'react';
import PropTypes from 'prop-types';
import classNames from "classnames";
+import {EventType} from "matrix-js-sdk/src/@types/event";
import { _t, _td } from '../../../languageHandler';
import * as TextForEvent from "../../../TextForEvent";
import * as sdk from "../../../index";
@@ -646,12 +647,13 @@ export default class EventTile extends React.Component {
// Info messages are basically information about commands processed on a room
const isBubbleMessage = eventType.startsWith("m.key.verification") ||
- (eventType === "m.room.message" && msgtype && msgtype.startsWith("m.key.verification")) ||
- (eventType === "m.room.encryption") ||
+ (eventType === EventType.RoomMessage && msgtype && msgtype.startsWith("m.key.verification")) ||
+ (eventType === EventType.RoomCreate) ||
+ (eventType === EventType.RoomEncryption) ||
(tileHandler === "messages.MJitsiWidgetEvent");
let isInfoMessage = (
- !isBubbleMessage && eventType !== 'm.room.message' &&
- eventType !== 'm.sticker' && eventType !== 'm.room.create'
+ !isBubbleMessage && eventType !== EventType.RoomMessage &&
+ eventType !== EventType.Sticker && eventType !== EventType.RoomCreate
);
// If we're showing hidden events in the timeline, we should use the
diff --git a/src/components/views/rooms/NewRoomIntro.tsx b/src/components/views/rooms/NewRoomIntro.tsx
new file mode 100644
index 0000000000..27404eef12
--- /dev/null
+++ b/src/components/views/rooms/NewRoomIntro.tsx
@@ -0,0 +1,131 @@
+/*
+Copyright 2020 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, {useContext} from "react";
+import {EventType} from "matrix-js-sdk/src/@types/event";
+
+import MatrixClientContext from "../../../contexts/MatrixClientContext";
+import RoomContext from "../../../contexts/RoomContext";
+import DMRoomMap from "../../../utils/DMRoomMap";
+import {_t} from "../../../languageHandler";
+import AccessibleButton from "../elements/AccessibleButton";
+import MiniAvatarUploader, {AVATAR_SIZE} from "../elements/MiniAvatarUploader";
+import RoomAvatar from "../avatars/RoomAvatar";
+import defaultDispatcher from "../../../dispatcher/dispatcher";
+import {ViewUserPayload} from "../../../dispatcher/payloads/ViewUserPayload";
+import {Action} from "../../../dispatcher/actions";
+import dis from "../../../dispatcher/dispatcher";
+
+const NewRoomIntro = () => {
+ const cli = useContext(MatrixClientContext);
+ const {room, roomId} = useContext(RoomContext);
+
+ const dmPartner = DMRoomMap.shared().getUserIdForRoomId(roomId);
+ let body;
+ if (dmPartner) {
+ let caption;
+ if ((room.getJoinedMemberCount() + room.getInvitedMemberCount()) === 2) {
+ caption = _t("Only the two of you are in this conversation, unless either of you invites anyone to join.");
+ }
+
+ const member = room?.getMember(dmPartner);
+ const displayName = member?.rawDisplayName || dmPartner;
+ body =
+ {
+ defaultDispatcher.dispatch({
+ action: Action.ViewUser,
+ // XXX: We should be using a real member object and not assuming what the receiver wants.
+ member: member || {userId: dmPartner},
+ });
+ }} />
+
+
{ room.name }
+
+
{_t("This is the beginning of your direct message history with .", {}, {
+ displayName: () => { displayName },
+ })}
+ { caption &&
{ caption }
}
+ ;
+ } else {
+ const topic = room.currentState.getStateEvents(EventType.RoomTopic, "")?.getContent()?.topic;
+ const canAddTopic = room.currentState.maySendStateEvent(EventType.RoomTopic, cli.getUserId());
+
+ const onTopicClick = () => {
+ dis.dispatch({
+ action: "open_room_settings",
+ room_id: roomId,
+ }, true);
+ // focus the topic field to help the user find it as it'll gain an outline
+ setImmediate(() => {
+ window.document.getElementById("profileTopic").focus();
+ });
+ };
+
+ let topicText;
+ if (canAddTopic && topic) {
+ topicText = _t("Topic: %(topic)s (edit)", { topic }, {
+ a: sub => { sub },
+ });
+ } else if (topic) {
+ topicText = _t("Topic: %(topic)s ", { topic });
+ } else if (canAddTopic) {
+ topicText = _t("Add a topic to help people know what it is about.", {}, {
+ a: sub => { sub },
+ });
+ }
+
+ const creator = room.currentState.getStateEvents(EventType.RoomCreate, "")?.getSender();
+ const creatorName = room?.getMember(creator)?.rawDisplayName || creator;
+
+ let createdText;
+ if (creator === cli.getUserId()) {
+ createdText = _t("You created this room.");
+ } else {
+ createdText = _t("%(displayName)s created this room.", {
+ displayName: creatorName,
+ });
+ }
+
+ const avatarUrl = room.currentState.getStateEvents(EventType.RoomAvatar, "")?.getContent()?.url;
+ body =
+ cli.sendStateEvent(roomId, EventType.RoomAvatar, { url }, '')}
+ >
+
+
+
+
{ room.name }
+
+
{createdText} {_t("This is the start of .", {}, {
+ roomName: () => { room.name },
+ })}
+
{topicText}
+
+
+ {_t("Invite to this room")}
+
+
+ ;
+ }
+
+ return
+ { body }
+
;
+};
+
+export default NewRoomIntro;
diff --git a/src/contexts/RoomContext.ts b/src/contexts/RoomContext.ts
index e8eb0c23b4..082dcc4e6b 100644
--- a/src/contexts/RoomContext.ts
+++ b/src/contexts/RoomContext.ts
@@ -29,7 +29,6 @@ const RoomContext = createContext({
guestsCanJoin: false,
canPeek: false,
showApps: false,
- isAlone: false,
isPeeking: false,
showingPinned: false,
showReadReceipts: true,
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 5fec27c7f6..830d3cdee4 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -1335,6 +1335,15 @@
"Strikethrough": "Strikethrough",
"Code block": "Code block",
"Quote": "Quote",
+ "Only the two of you are in this conversation, unless either of you invites anyone to join.": "Only the two of you are in this conversation, unless either of you invites anyone to join.",
+ "This is the beginning of your direct message history with .": "This is the beginning of your direct message history with .",
+ "Topic: %(topic)s (edit)": "Topic: %(topic)s (edit)",
+ "Topic: %(topic)s ": "Topic: %(topic)s ",
+ "Add a topic to help people know what it is about.": "Add a topic to help people know what it is about.",
+ "You created this room.": "You created this room.",
+ "%(displayName)s created this room.": "%(displayName)s created this room.",
+ "Add a photo, so people can easily spot your room.": "Add a photo, so people can easily spot your room.",
+ "This is the start of .": "This is the start of .",
"No pinned messages.": "No pinned messages.",
"Loading...": "Loading...",
"Pinned Messages": "Pinned Messages",
@@ -1633,8 +1642,9 @@
"Today": "Today",
"Yesterday": "Yesterday",
"View Source": "View Source",
+ "Messages here are end-to-end encrypted. Verify %(displayName)s in their profile - tap on their avatar.": "Messages here are end-to-end encrypted. Verify %(displayName)s in their profile - tap on their avatar.",
+ "Messages in this room are end-to-end encrypted. When people join, you can verify them in their profile, just tap on their avatar.": "Messages in this room are end-to-end encrypted. When people join, you can verify them in their profile, just tap on their avatar.",
"Encryption enabled": "Encryption enabled",
- "Messages in this room are end-to-end encrypted. Learn more & verify this user in their user profile.": "Messages in this room are end-to-end encrypted. Learn more & verify this user in their user profile.",
"Ignored attempt to disable encryption": "Ignored attempt to disable encryption",
"Encryption not enabled": "Encryption not enabled",
"The encryption used by this room isn't supported.": "The encryption used by this room isn't supported.",
@@ -1680,8 +1690,8 @@
"%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s changed the avatar for %(roomName)s",
"%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s removed the room avatar.",
"%(senderDisplayName)s changed the room avatar to ": "%(senderDisplayName)s changed the room avatar to ",
- "This room is a continuation of another conversation.": "This room is a continuation of another conversation.",
"Click here to see older messages.": "Click here to see older messages.",
+ "This room is a continuation of another conversation.": "This room is a continuation of another conversation.",
"Copied!": "Copied!",
"Failed to copy": "Failed to copy",
"Add an Integration": "Add an Integration",
@@ -2331,6 +2341,7 @@
"Data from an older version of %(brand)s has been detected. This will have caused end-to-end cryptography to malfunction in the older version. End-to-end encrypted messages exchanged recently whilst using the older version may not be decryptable in this version. This may also cause messages exchanged with this version to fail. If you experience problems, log out and back in again. To retain message history, export and re-import your keys.": "Data from an older version of %(brand)s has been detected. This will have caused end-to-end cryptography to malfunction in the older version. End-to-end encrypted messages exchanged recently whilst using the older version may not be decryptable in this version. This may also cause messages exchanged with this version to fail. If you experience problems, log out and back in again. To retain message history, export and re-import your keys.",
"Self-verification request": "Self-verification request",
"Logout": "Logout",
+ "%(creator)s created this DM.": "%(creator)s created this DM.",
"%(creator)s created and configured the room.": "%(creator)s created and configured the room.",
"Your Communities": "Your Communities",
"Did you know: you can use communities to filter your %(brand)s experience!": "Did you know: you can use communities to filter your %(brand)s experience!",
@@ -2376,7 +2387,6 @@
"Starting microphone...": "Starting microphone...",
"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.",
- "There's no one else here! Would you like to invite others or stop warning about the empty room?": "There's no one else here! Would you like to invite others or stop warning about the empty room?",
"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",
diff --git a/test/components/structures/MessagePanel-test.js b/test/components/structures/MessagePanel-test.js
index 235ae94010..f40f8c5187 100644
--- a/test/components/structures/MessagePanel-test.js
+++ b/test/components/structures/MessagePanel-test.js
@@ -38,6 +38,7 @@ import { configure, mount } from "enzyme";
import Velocity from 'velocity-animate';
import MatrixClientContext from "../../../src/contexts/MatrixClientContext";
import RoomContext from "../../../src/contexts/RoomContext";
+import DMRoomMap from "../../../src/utils/DMRoomMap";
configure({ adapter: new Adapter() });
@@ -52,7 +53,7 @@ class WrappedMessagePanel extends React.Component {
render() {
return
-
+ ;
@@ -79,6 +80,8 @@ describe('MessagePanel', function() {
// complete without this even if we mock the clock and tick it
// what should be the correct amount of time).
Velocity.mock = true;
+
+ DMRoomMap.makeShared();
});
afterEach(function() {
@@ -433,8 +436,8 @@ describe('MessagePanel', function() {
const rm = res.find('.mx_RoomView_myReadMarker_container').getDOMNode();
const rows = res.find('.mx_RoomView_MessageList').children();
- expect(rows.length).toEqual(6);
- expect(rm.previousSibling).toEqual(rows.at(4).getDOMNode());
+ expect(rows.length).toEqual(7); // 6 events + the NewRoomIntro
+ expect(rm.previousSibling).toEqual(rows.at(5).getDOMNode());
// read marker should be hidden given props and at the last event
expect(isReadMarkerVisible(rm)).toBeFalsy();
diff --git a/test/test-utils.js b/test/test-utils.js
index d006eee823..c8e623b1d9 100644
--- a/test/test-utils.js
+++ b/test/test-utils.js
@@ -242,6 +242,7 @@ export function mkStubRoom(roomId = null) {
setBlacklistUnverifiedDevices: jest.fn(),
on: jest.fn(),
removeListener: jest.fn(),
+ getDMInviter: jest.fn(),
};
}