+
{/* set by CSS masking */}
@@ -432,6 +448,8 @@ export default class RoomSublist2 extends React.Component {
resizeHandles={handles}
onResize={this.onResize}
className="mx_RoomSublist2_resizeBox"
+ onResizeStart={this.onResizeStart}
+ onResizeStop={this.onResizeStop}
>
{visibleTiles}
{showNButton}
diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx
index 18b4ee8185..3d0a555877 100644
--- a/src/components/views/rooms/RoomTile2.tsx
+++ b/src/components/views/rooms/RoomTile2.tsx
@@ -34,7 +34,7 @@ import NotificationBadge, {
import { _t } from "../../../languageHandler";
import { ContextMenu, ContextMenuButton } from "../../structures/ContextMenu";
import { DefaultTagID, TagID } from "../../../stores/room-list/models";
-import { MessagePreviewStore } from "../../../stores/MessagePreviewStore";
+import { MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore";
import RoomTileIcon from "./RoomTileIcon";
/*******************************************************************
@@ -271,7 +271,7 @@ export default class RoomTile2 extends React.Component {
let messagePreview = null;
if (this.props.showMessagePreview && !this.props.isMinimized) {
// The preview store heavily caches this info, so should be safe to hammer.
- const text = MessagePreviewStore.instance.getPreviewForRoom(this.props.room);
+ const text = MessagePreviewStore.instance.getPreviewForRoom(this.props.room, this.props.tag);
// Only show the preview if there is one to show.
if (text) {
diff --git a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx
index 42f8cb01de..f02147608d 100644
--- a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx
+++ b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx
@@ -390,7 +390,13 @@ export default class AppearanceUserSettingsTab extends React.Component
+ advanced = <>
+
-
;
+ >;
}
return
{toggle}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 74e747726a..d721979329 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -247,7 +247,6 @@
"%(senderDisplayName)s enabled flair for %(groups)s in this room.": "%(senderDisplayName)s enabled flair for %(groups)s in this room.",
"%(senderDisplayName)s disabled flair for %(groups)s in this room.": "%(senderDisplayName)s disabled flair for %(groups)s in this room.",
"%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for %(oldGroups)s in this room.": "%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for %(oldGroups)s in this room.",
- "sent an image.": "sent an image.",
"%(senderDisplayName)s sent an image.": "%(senderDisplayName)s sent an image.",
"%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s set the main address for this room to %(address)s.",
"%(senderName)s removed the main address for this room.": "%(senderName)s removed the main address for this room.",
@@ -421,12 +420,66 @@
"Restart": "Restart",
"Upgrade your Riot": "Upgrade your Riot",
"A new version of Riot is available!": "A new version of Riot is available!",
- "You: %(message)s": "You: %(message)s",
"Guest": "Guest",
"There was an error joining the room": "There was an error joining the room",
"Sorry, your homeserver is too old to participate in this room.": "Sorry, your homeserver is too old to participate in this room.",
"Please contact your homeserver administrator.": "Please contact your homeserver administrator.",
"Failed to join room": "Failed to join room",
+ "You joined the call": "You joined the call",
+ "%(senderName)s joined the call": "%(senderName)s joined the call",
+ "Call in progress": "Call in progress",
+ "You left the call": "You left the call",
+ "%(senderName)s left the call": "%(senderName)s left the call",
+ "Call ended": "Call ended",
+ "You started a call": "You started a call",
+ "%(senderName)s started a call": "%(senderName)s started a call",
+ "Waiting for answer": "Waiting for answer",
+ "%(senderName)s is calling": "%(senderName)s is calling",
+ "You created the room": "You created the room",
+ "%(senderName)s created the room": "%(senderName)s created the room",
+ "You made the chat encrypted": "You made the chat encrypted",
+ "%(senderName)s made the chat encrypted": "%(senderName)s made the chat encrypted",
+ "You made history visible to new members": "You made history visible to new members",
+ "%(senderName)s made history visible to new members": "%(senderName)s made history visible to new members",
+ "You made history visible to anyone": "You made history visible to anyone",
+ "%(senderName)s made history visible to anyone": "%(senderName)s made history visible to anyone",
+ "You made history visible to future members": "You made history visible to future members",
+ "%(senderName)s made history visible to future members": "%(senderName)s made history visible to future members",
+ "You were invited": "You were invited",
+ "%(targetName)s was invited": "%(targetName)s was invited",
+ "You left": "You left",
+ "%(targetName)s left": "%(targetName)s left",
+ "You were kicked (%(reason)s)": "You were kicked (%(reason)s)",
+ "%(targetName)s was kicked (%(reason)s)": "%(targetName)s was kicked (%(reason)s)",
+ "You were kicked": "You were kicked",
+ "%(targetName)s was kicked": "%(targetName)s was kicked",
+ "You rejected the invite": "You rejected the invite",
+ "%(targetName)s rejected the invite": "%(targetName)s rejected the invite",
+ "You were uninvited": "You were uninvited",
+ "%(targetName)s was uninvited": "%(targetName)s was uninvited",
+ "You were banned (%(reason)s)": "You were banned (%(reason)s)",
+ "%(targetName)s was banned (%(reason)s)": "%(targetName)s was banned (%(reason)s)",
+ "You were banned": "You were banned",
+ "%(targetName)s was banned": "%(targetName)s was banned",
+ "You joined": "You joined",
+ "%(targetName)s joined": "%(targetName)s joined",
+ "You changed your name": "You changed your name",
+ "%(targetName)s changed their name": "%(targetName)s changed their name",
+ "You changed your avatar": "You changed your avatar",
+ "%(targetName)s changed their avatar": "%(targetName)s changed their avatar",
+ "%(senderName)s %(emote)s": "%(senderName)s %(emote)s",
+ "%(senderName)s: %(message)s": "%(senderName)s: %(message)s",
+ "You changed the room name": "You changed the room name",
+ "%(senderName)s changed the room name": "%(senderName)s changed the room name",
+ "%(senderName)s: %(reaction)s": "%(senderName)s: %(reaction)s",
+ "%(senderName)s: %(stickerName)s": "%(senderName)s: %(stickerName)s",
+ "You uninvited %(targetName)s": "You uninvited %(targetName)s",
+ "%(senderName)s uninvited %(targetName)s": "%(senderName)s uninvited %(targetName)s",
+ "You invited %(targetName)s": "You invited %(targetName)s",
+ "%(senderName)s invited %(targetName)s": "%(senderName)s invited %(targetName)s",
+ "You changed the room topic": "You changed the room topic",
+ "%(senderName)s changed the room topic": "%(senderName)s changed the room topic",
+ "New spinner design": "New spinner design",
"Font scaling": "Font scaling",
"Message Pinning": "Message Pinning",
"Custom user status messages": "Custom user status messages",
@@ -441,7 +494,7 @@
"Font size": "Font size",
"Use custom size": "Use custom size",
"Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing",
- "Use compact timeline layout": "Use compact timeline layout",
+ "Use a more compact ‘Modern’ layout": "Use a more compact ‘Modern’ layout",
"Show a placeholder for removed messages": "Show a placeholder for removed messages",
"Show join/leave messages (invites/kicks/bans unaffected)": "Show join/leave messages (invites/kicks/bans unaffected)",
"Show avatar changes": "Show avatar changes",
diff --git a/src/settings/Settings.js b/src/settings/Settings.js
index 5948a4ec9e..820329f6c6 100644
--- a/src/settings/Settings.js
+++ b/src/settings/Settings.js
@@ -97,6 +97,12 @@ export const SETTINGS = {
// // not use this for new settings.
// invertedSettingName: "my-negative-setting",
// },
+ "feature_new_spinner": {
+ isFeature: true,
+ displayName: _td("New spinner design"),
+ supportedLevels: LEVELS_FEATURE,
+ default: false,
+ },
"feature_font_scaling": {
isFeature: true,
displayName: _td("Font scaling"),
@@ -197,7 +203,7 @@ export const SETTINGS = {
},
"useCompactLayout": {
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
- displayName: _td('Use compact timeline layout'),
+ displayName: _td('Use a more compact ‘Modern’ layout'),
default: false,
},
"showRedactions": {
diff --git a/src/stores/MessagePreviewStore.ts b/src/stores/MessagePreviewStore.ts
deleted file mode 100644
index 9e225d1709..0000000000
--- a/src/stores/MessagePreviewStore.ts
+++ /dev/null
@@ -1,134 +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 { Room } from "matrix-js-sdk/src/models/room";
-import { ActionPayload } from "../dispatcher/payloads";
-import { AsyncStoreWithClient } from "./AsyncStoreWithClient";
-import defaultDispatcher from "../dispatcher/dispatcher";
-import { RoomListStoreTempProxy } from "./room-list/RoomListStoreTempProxy";
-import { textForEvent } from "../TextForEvent";
-import { MatrixEvent } from "matrix-js-sdk/src/models/event";
-import { _t } from "../languageHandler";
-
-const PREVIEWABLE_EVENTS = [
- // This is the same list from RiotX
- {type: "m.room.message", isState: false},
- {type: "m.room.name", isState: true},
- {type: "m.room.topic", isState: true},
- {type: "m.room.member", isState: true},
- {type: "m.room.history_visibility", isState: true},
- {type: "m.call.invite", isState: false},
- {type: "m.call.hangup", isState: false},
- {type: "m.call.answer", isState: false},
- {type: "m.room.encrypted", isState: false},
- {type: "m.room.encryption", isState: true},
- {type: "m.room.third_party_invite", isState: true},
- {type: "m.sticker", isState: false},
- {type: "m.room.create", isState: true},
-];
-
-// The maximum number of events we're willing to look back on to get a preview.
-const MAX_EVENTS_BACKWARDS = 50;
-
-interface IState {
- [roomId: string]: string | null; // null indicates the preview is empty
-}
-
-export class MessagePreviewStore extends AsyncStoreWithClient {
- private static internalInstance = new MessagePreviewStore();
-
- private constructor() {
- super(defaultDispatcher, {});
- }
-
- public static get instance(): MessagePreviewStore {
- return MessagePreviewStore.internalInstance;
- }
-
- /**
- * Gets the pre-translated preview for a given room
- * @param room The room to get the preview for.
- * @returns The preview, or null if none present.
- */
- public getPreviewForRoom(room: Room): string {
- if (!room) return null; // invalid room, just return nothing
-
- // It's faster to do a lookup this way than it is to use Object.keys().includes()
- // We only want to generate a preview if there's one actually missing and not explicitly
- // set as 'none'.
- const val = this.state[room.roomId];
- if (val !== null && typeof(val) !== "string") {
- this.generatePreview(room);
- }
-
- return this.state[room.roomId];
- }
-
- private generatePreview(room: Room) {
- const events = room.timeline;
- if (!events) return; // should only happen in tests
-
- for (let i = events.length - 1; i >= 0; i--) {
- if (i === events.length - MAX_EVENTS_BACKWARDS) return; // limit reached
-
- const event = events[i];
- const preview = this.generatePreviewForEvent(event);
- if (preview.isPreviewable) {
- // noinspection JSIgnoredPromiseFromCall - the AsyncStore handles concurrent calls
- this.updateState({[room.roomId]: preview.preview});
- return; // break - we found some text
- }
- }
-
- // if we didn't find anything, subscribe ourselves to an update
- // noinspection JSIgnoredPromiseFromCall - the AsyncStore handles concurrent calls
- this.updateState({[room.roomId]: null});
- }
-
- protected async onAction(payload: ActionPayload) {
- if (!this.matrixClient) return;
-
- // TODO: Remove when new room list is made the default
- if (!RoomListStoreTempProxy.isUsingNewStore()) return;
-
- if (payload.action === 'MatrixActions.Room.timeline' || payload.action === 'MatrixActions.Event.decrypted') {
- const event = payload.event; // TODO: Type out the dispatcher
- if (!Object.keys(this.state).includes(event.getRoomId())) return; // not important
-
- const preview = this.generatePreviewForEvent(event);
- if (preview.isPreviewable) {
- await this.updateState({[event.getRoomId()]: preview.preview});
- return; // break - we found some text
- }
- }
- }
-
- private generatePreviewForEvent(event: MatrixEvent): { isPreviewable: boolean, preview: string } {
- if (PREVIEWABLE_EVENTS.some(p => p.type === event.getType() && p.isState === event.isState())) {
- const isSelf = event.getSender() === this.matrixClient.getUserId();
- let text = textForEvent(event, /*skipUserPrefix=*/isSelf);
- if (!text || text.trim().length === 0) text = null; // force null if useless to us
- if (text && isSelf) {
- // XXX: i18n doesn't really work here if the language doesn't support prefixing.
- // We'd ideally somehow route the `You:` bit to the textForEvent call, however
- // threading that through is non-trivial.
- text = _t("You: %(message)s", {message: text});
- }
- return {isPreviewable: true, preview: text};
- }
- return {isPreviewable: false, preview: null};
- }
-}
diff --git a/src/stores/room-list/ListLayout.ts b/src/stores/room-list/ListLayout.ts
index ebc7b95854..8ca8ad637b 100644
--- a/src/stores/room-list/ListLayout.ts
+++ b/src/stores/room-list/ListLayout.ts
@@ -18,6 +18,10 @@ import { TagID } from "./models";
const TILE_HEIGHT_PX = 44;
+// the .65 comes from the CSS where the show more button is
+// mathematically 65% of a tile when floating.
+const RESIZER_BOX_FACTOR = 0.65;
+
interface ISerializedListLayout {
numTiles: number;
showPreviews: boolean;
@@ -67,6 +71,7 @@ export class ListLayout {
}
public get visibleTiles(): number {
+ if (this._n === 0) return this.defaultVisibleTiles;
return Math.max(this._n, this.minVisibleTiles);
}
@@ -76,9 +81,13 @@ export class ListLayout {
}
public get minVisibleTiles(): number {
- // the .65 comes from the CSS where the show more button is
- // mathematically 65% of a tile when floating.
- return 4.65;
+ return 1 + RESIZER_BOX_FACTOR;
+ }
+
+ public get defaultVisibleTiles(): number {
+ // TODO: Remove dogfood flag
+ const val = Number(localStorage.getItem("mx_dogfood_rl_defTiles") || 4);
+ return val + RESIZER_BOX_FACTOR;
}
public calculateTilesToPixelsMin(maxTiles: number, n: number, possiblePadding: number): number {
@@ -92,6 +101,10 @@ export class ListLayout {
return this.tilesToPixels(Math.min(maxTiles, n)) + padding;
}
+ public tilesWithResizerBoxFactor(n: number): number {
+ return n + RESIZER_BOX_FACTOR;
+ }
+
public tilesWithPadding(n: number, paddingPx: number): number {
return this.pixelsToTiles(this.tilesToPixelsWithPadding(n, paddingPx));
}
diff --git a/src/stores/room-list/MessagePreviewStore.ts b/src/stores/room-list/MessagePreviewStore.ts
new file mode 100644
index 0000000000..b727069f9f
--- /dev/null
+++ b/src/stores/room-list/MessagePreviewStore.ts
@@ -0,0 +1,204 @@
+/*
+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 { Room } from "matrix-js-sdk/src/models/room";
+import { ActionPayload } from "../../dispatcher/payloads";
+import { AsyncStoreWithClient } from "../AsyncStoreWithClient";
+import defaultDispatcher from "../../dispatcher/dispatcher";
+import { RoomListStoreTempProxy } from "./RoomListStoreTempProxy";
+import { MessageEventPreview } from "./previews/MessageEventPreview";
+import { NameEventPreview } from "./previews/NameEventPreview";
+import { TagID } from "./models";
+import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
+import { TopicEventPreview } from "./previews/TopicEventPreview";
+import { MembershipEventPreview } from "./previews/MembershipEventPreview";
+import { HistoryVisibilityEventPreview } from "./previews/HistoryVisibilityEventPreview";
+import { CallInviteEventPreview } from "./previews/CallInviteEventPreview";
+import { CallAnswerEventPreview } from "./previews/CallAnswerEventPreview";
+import { CallHangupEvent } from "./previews/CallHangupEvent";
+import { EncryptionEventPreview } from "./previews/EncryptionEventPreview";
+import { ThirdPartyInviteEventPreview } from "./previews/ThirdPartyInviteEventPreview";
+import { StickerEventPreview } from "./previews/StickerEventPreview";
+import { ReactionEventPreview } from "./previews/ReactionEventPreview";
+import { CreationEventPreview } from "./previews/CreationEventPreview";
+
+const PREVIEWS = {
+ 'm.room.message': {
+ isState: false,
+ previewer: new MessageEventPreview(),
+ },
+ 'm.room.name': {
+ isState: true,
+ previewer: new NameEventPreview(),
+ },
+ 'm.room.topic': {
+ isState: true,
+ previewer: new TopicEventPreview(),
+ },
+ 'm.room.member': {
+ isState: true,
+ previewer: new MembershipEventPreview(),
+ },
+ 'm.room.history_visibility': {
+ isState: true,
+ previewer: new HistoryVisibilityEventPreview(),
+ },
+ 'm.call.invite': {
+ isState: false,
+ previewer: new CallInviteEventPreview(),
+ },
+ 'm.call.answer': {
+ isState: false,
+ previewer: new CallAnswerEventPreview(),
+ },
+ 'm.call.hangup': {
+ isState: false,
+ previewer: new CallHangupEvent(),
+ },
+ 'm.room.encryption': {
+ isState: true,
+ previewer: new EncryptionEventPreview(),
+ },
+ 'm.room.third_party_invite': {
+ isState: true,
+ previewer: new ThirdPartyInviteEventPreview(),
+ },
+ 'm.sticker': {
+ isState: false,
+ previewer: new StickerEventPreview(),
+ },
+ 'm.reaction': {
+ isState: false,
+ previewer: new ReactionEventPreview(),
+ },
+ 'm.room.create': {
+ isState: true,
+ previewer: new CreationEventPreview(),
+ },
+};
+
+// The maximum number of events we're willing to look back on to get a preview.
+const MAX_EVENTS_BACKWARDS = 50;
+
+// type merging ftw
+type TAG_ANY = "im.vector.any";
+const TAG_ANY: TAG_ANY = "im.vector.any";
+
+interface IState {
+ [roomId: string]: Map; // null indicates the preview is empty / irrelevant
+}
+
+export class MessagePreviewStore extends AsyncStoreWithClient {
+ private static internalInstance = new MessagePreviewStore();
+
+ private constructor() {
+ super(defaultDispatcher, {});
+ }
+
+ public static get instance(): MessagePreviewStore {
+ return MessagePreviewStore.internalInstance;
+ }
+
+ /**
+ * Gets the pre-translated preview for a given room
+ * @param room The room to get the preview for.
+ * @param inTagId The tag ID in which the room resides
+ * @returns The preview, or null if none present.
+ */
+ public getPreviewForRoom(room: Room, inTagId: TagID): string {
+ if (!room) return null; // invalid room, just return nothing
+
+ const val = this.state[room.roomId];
+ if (!val) this.generatePreview(room, inTagId);
+
+ const previews = this.state[room.roomId];
+ if (!previews) return null;
+
+ if (!previews.has(inTagId)) {
+ return previews.get(TAG_ANY);
+ }
+ return previews.get(inTagId);
+ }
+
+ private generatePreview(room: Room, tagId?: TagID) {
+ const events = room.timeline;
+ if (!events) return; // should only happen in tests
+
+ let map = this.state[room.roomId];
+ if (!map) {
+ map = new Map();
+
+ // We set the state later with the map, so no need to send an update now
+ }
+
+ // Set the tags so we know what to generate
+ if (!map.has(TAG_ANY)) map.set(TAG_ANY, null);
+ if (tagId && !map.has(tagId)) map.set(tagId, null);
+
+ let changed = false;
+ for (let i = events.length - 1; i >= 0; i--) {
+ if (i === events.length - MAX_EVENTS_BACKWARDS) return; // limit reached
+
+ const event = events[i];
+ const previewDef = PREVIEWS[event.getType()];
+ if (!previewDef) continue;
+ if (previewDef.isState && isNullOrUndefined(event.getStateKey())) continue;
+
+ const anyPreview = previewDef.previewer.getTextFor(event, null);
+ if (!anyPreview) continue; // not previewable for some reason
+
+ changed = changed || anyPreview !== map.get(TAG_ANY);
+ map.set(TAG_ANY, anyPreview);
+
+ const tagsToGenerate = Array.from(map.keys()).filter(t => t !== TAG_ANY); // we did the any tag above
+ for (const genTagId of tagsToGenerate) {
+ const realTagId: TagID = genTagId === TAG_ANY ? null : genTagId;
+ const preview = previewDef.previewer.getTextFor(event, realTagId);
+ if (preview === anyPreview) {
+ changed = changed || anyPreview !== map.get(genTagId);
+ map.delete(genTagId);
+ } else {
+ changed = changed || preview !== map.get(genTagId);
+ map.set(genTagId, preview);
+ }
+ }
+
+ if (changed) {
+ // Update state for good measure - causes emit for update
+ // noinspection JSIgnoredPromiseFromCall - the AsyncStore handles concurrent calls
+ this.updateState({[room.roomId]: map});
+ }
+ return; // we're done
+ }
+
+ // At this point, we didn't generate a preview so clear it
+ // noinspection JSIgnoredPromiseFromCall - the AsyncStore handles concurrent calls
+ this.updateState({[room.roomId]: null});
+ }
+
+ protected async onAction(payload: ActionPayload) {
+ if (!this.matrixClient) return;
+
+ // TODO: Remove when new room list is made the default
+ if (!RoomListStoreTempProxy.isUsingNewStore()) return;
+
+ if (payload.action === 'MatrixActions.Room.timeline' || payload.action === 'MatrixActions.Event.decrypted') {
+ const event = payload.event; // TODO: Type out the dispatcher
+ if (!Object.keys(this.state).includes(event.getRoomId())) return; // not important
+ this.generatePreview(this.matrixClient.getRoom(event.getRoomId()), TAG_ANY);
+ }
+ }
+}
diff --git a/src/stores/room-list/previews/CallAnswerEventPreview.ts b/src/stores/room-list/previews/CallAnswerEventPreview.ts
new file mode 100644
index 0000000000..b7207307e2
--- /dev/null
+++ b/src/stores/room-list/previews/CallAnswerEventPreview.ts
@@ -0,0 +1,35 @@
+/*
+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 { IPreview } from "./IPreview";
+import { TagID } from "../models";
+import { MatrixEvent } from "matrix-js-sdk/src/models/event";
+import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils";
+import { _t } from "../../../languageHandler";
+
+export class CallAnswerEventPreview implements IPreview {
+ public getTextFor(event: MatrixEvent, tagId?: TagID): string {
+ if (shouldPrefixMessagesIn(event.getRoomId(), tagId)) {
+ if (isSelf(event)) {
+ return _t("You joined the call");
+ } else {
+ return _t("%(senderName)s joined the call", {senderName: getSenderName(event)});
+ }
+ } else {
+ return _t("Call in progress");
+ }
+ }
+}
diff --git a/src/stores/room-list/previews/CallHangupEvent.ts b/src/stores/room-list/previews/CallHangupEvent.ts
new file mode 100644
index 0000000000..adc7d1aac8
--- /dev/null
+++ b/src/stores/room-list/previews/CallHangupEvent.ts
@@ -0,0 +1,35 @@
+/*
+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 { IPreview } from "./IPreview";
+import { TagID } from "../models";
+import { MatrixEvent } from "matrix-js-sdk/src/models/event";
+import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils";
+import { _t } from "../../../languageHandler";
+
+export class CallHangupEvent implements IPreview {
+ public getTextFor(event: MatrixEvent, tagId?: TagID): string {
+ if (shouldPrefixMessagesIn(event.getRoomId(), tagId)) {
+ if (isSelf(event)) {
+ return _t("You left the call");
+ } else {
+ return _t("%(senderName)s left the call", {senderName: getSenderName(event)});
+ }
+ } else {
+ return _t("Call ended");
+ }
+ }
+}
diff --git a/src/stores/room-list/previews/CallInviteEventPreview.ts b/src/stores/room-list/previews/CallInviteEventPreview.ts
new file mode 100644
index 0000000000..47486e3701
--- /dev/null
+++ b/src/stores/room-list/previews/CallInviteEventPreview.ts
@@ -0,0 +1,39 @@
+/*
+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 { IPreview } from "./IPreview";
+import { TagID } from "../models";
+import { MatrixEvent } from "matrix-js-sdk/src/models/event";
+import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils";
+import { _t } from "../../../languageHandler";
+
+export class CallInviteEventPreview implements IPreview {
+ public getTextFor(event: MatrixEvent, tagId?: TagID): string {
+ if (shouldPrefixMessagesIn(event.getRoomId(), tagId)) {
+ if (isSelf(event)) {
+ return _t("You started a call");
+ } else {
+ return _t("%(senderName)s started a call", {senderName: getSenderName(event)});
+ }
+ } else {
+ if (isSelf(event)) {
+ return _t("Waiting for answer");
+ } else {
+ return _t("%(senderName)s is calling", {senderName: getSenderName(event)});
+ }
+ }
+ }
+}
diff --git a/src/stores/room-list/previews/CreationEventPreview.ts b/src/stores/room-list/previews/CreationEventPreview.ts
new file mode 100644
index 0000000000..62bb5fe53a
--- /dev/null
+++ b/src/stores/room-list/previews/CreationEventPreview.ts
@@ -0,0 +1,31 @@
+/*
+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 { IPreview } from "./IPreview";
+import { TagID } from "../models";
+import { MatrixEvent } from "matrix-js-sdk/src/models/event";
+import { getSenderName, isSelf } from "./utils";
+import { _t } from "../../../languageHandler";
+
+export class CreationEventPreview implements IPreview {
+ public getTextFor(event: MatrixEvent, tagId?: TagID): string {
+ if (isSelf(event)) {
+ return _t("You created the room");
+ } else {
+ return _t("%(senderName)s created the room", {senderName: getSenderName(event)});
+ }
+ }
+}
diff --git a/src/stores/room-list/previews/EncryptionEventPreview.ts b/src/stores/room-list/previews/EncryptionEventPreview.ts
new file mode 100644
index 0000000000..d00fd7e7f9
--- /dev/null
+++ b/src/stores/room-list/previews/EncryptionEventPreview.ts
@@ -0,0 +1,31 @@
+/*
+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 { IPreview } from "./IPreview";
+import { TagID } from "../models";
+import { MatrixEvent } from "matrix-js-sdk/src/models/event";
+import { getSenderName, isSelf } from "./utils";
+import { _t } from "../../../languageHandler";
+
+export class EncryptionEventPreview implements IPreview {
+ public getTextFor(event: MatrixEvent, tagId?: TagID): string {
+ if (isSelf(event)) {
+ return _t("You made the chat encrypted");
+ } else {
+ return _t("%(senderName)s made the chat encrypted", {senderName: getSenderName(event)});
+ }
+ }
+}
diff --git a/src/stores/room-list/previews/HistoryVisibilityEventPreview.ts b/src/stores/room-list/previews/HistoryVisibilityEventPreview.ts
new file mode 100644
index 0000000000..ac77a181f8
--- /dev/null
+++ b/src/stores/room-list/previews/HistoryVisibilityEventPreview.ts
@@ -0,0 +1,42 @@
+/*
+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 { IPreview } from "./IPreview";
+import { TagID } from "../models";
+import { MatrixEvent } from "matrix-js-sdk/src/models/event";
+import { getSenderName, isSelf } from "./utils";
+import { _t } from "../../../languageHandler";
+
+export class HistoryVisibilityEventPreview implements IPreview {
+ public getTextFor(event: MatrixEvent, tagId?: TagID): string {
+ const visibility = event.getContent()['history_visibility'];
+ const isUs = isSelf(event);
+
+ if (visibility === 'invited' || visibility === 'joined') {
+ return isUs
+ ? _t("You made history visible to new members")
+ : _t("%(senderName)s made history visible to new members", {senderName: getSenderName(event)});
+ } else if (visibility === 'world_readable') {
+ return isUs
+ ? _t("You made history visible to anyone")
+ : _t("%(senderName)s made history visible to anyone", {senderName: getSenderName(event)});
+ } else { // shared, default
+ return isUs
+ ? _t("You made history visible to future members")
+ : _t("%(senderName)s made history visible to future members", {senderName: getSenderName(event)});
+ }
+ }
+}
diff --git a/src/stores/room-list/previews/IPreview.ts b/src/stores/room-list/previews/IPreview.ts
new file mode 100644
index 0000000000..9beb92bfbf
--- /dev/null
+++ b/src/stores/room-list/previews/IPreview.ts
@@ -0,0 +1,31 @@
+/*
+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 { MatrixEvent } from "matrix-js-sdk/src/models/event";
+import { TagID } from "../models";
+
+/**
+ * Represents an event preview.
+ */
+export interface IPreview {
+ /**
+ * Gets the text which represents the event as a preview.
+ * @param event The event to preview.
+ * @param tagId Optional. The tag where the room the event was sent in resides.
+ * @returns The preview.
+ */
+ getTextFor(event: MatrixEvent, tagId?: TagID): string;
+}
diff --git a/src/stores/room-list/previews/MembershipEventPreview.ts b/src/stores/room-list/previews/MembershipEventPreview.ts
new file mode 100644
index 0000000000..44339aab5f
--- /dev/null
+++ b/src/stores/room-list/previews/MembershipEventPreview.ts
@@ -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 { IPreview } from "./IPreview";
+import { TagID } from "../models";
+import { MatrixEvent } from "matrix-js-sdk/src/models/event";
+import { getTargetName, isSelfTarget } from "./utils";
+import { _t } from "../../../languageHandler";
+
+export class MembershipEventPreview implements IPreview {
+ public getTextFor(event: MatrixEvent, tagId?: TagID): string {
+ const newMembership = event.getContent()['membership'];
+ const oldMembership = event.getPrevContent()['membership'];
+ const reason = event.getContent()['reason'];
+ const isUs = isSelfTarget(event);
+
+ if (newMembership === 'invite') {
+ return isUs
+ ? _t("You were invited")
+ : _t("%(targetName)s was invited", {targetName: getTargetName(event)});
+ } else if (newMembership === 'leave' && oldMembership !== 'invite') {
+ if (event.getSender() === event.getStateKey()) {
+ return isUs
+ ? _t("You left")
+ : _t("%(targetName)s left", {targetName: getTargetName(event)});
+ } else {
+ if (reason) {
+ return isUs
+ ? _t("You were kicked (%(reason)s)", {reason})
+ : _t("%(targetName)s was kicked (%(reason)s)", {targetName: getTargetName(event), reason});
+ } else {
+ return isUs
+ ? _t("You were kicked")
+ : _t("%(targetName)s was kicked", {targetName: getTargetName(event)});
+ }
+ }
+ } else if (newMembership === 'leave' && oldMembership === 'invite') {
+ if (event.getSender() === event.getStateKey()) {
+ return isUs
+ ? _t("You rejected the invite")
+ : _t("%(targetName)s rejected the invite", {targetName: getTargetName(event)});
+ } else {
+ return isUs
+ ? _t("You were uninvited")
+ : _t("%(targetName)s was uninvited", {targetName: getTargetName(event)});
+ }
+ } else if (newMembership === 'ban') {
+ if (reason) {
+ return isUs
+ ? _t("You were banned (%(reason)s)", {reason})
+ : _t("%(targetName)s was banned (%(reason)s)", {targetName: getTargetName(event), reason});
+ } else {
+ return isUs
+ ? _t("You were banned")
+ : _t("%(targetName)s was banned", {targetName: getTargetName(event)});
+ }
+ } else if (newMembership === 'join' && oldMembership !== 'join') {
+ return isUs
+ ? _t("You joined")
+ : _t("%(targetName)s joined", {targetName: getTargetName(event)});
+ } else {
+ const isDisplayNameChange = event.getContent()['displayname'] !== event.getPrevContent()['displayname'];
+ const isAvatarChange = event.getContent()['avatar_url'] !== event.getPrevContent()['avatar_url'];
+ if (isDisplayNameChange) {
+ return isUs
+ ? _t("You changed your name")
+ : _t("%(targetName)s changed their name", {targetName: getTargetName(event)});
+ } else if (isAvatarChange) {
+ return isUs
+ ? _t("You changed your avatar")
+ : _t("%(targetName)s changed their avatar", {targetName: getTargetName(event)});
+ } else {
+ return null; // no change
+ }
+ }
+ }
+}
diff --git a/src/stores/room-list/previews/MessageEventPreview.ts b/src/stores/room-list/previews/MessageEventPreview.ts
new file mode 100644
index 0000000000..6f0dc14a58
--- /dev/null
+++ b/src/stores/room-list/previews/MessageEventPreview.ts
@@ -0,0 +1,55 @@
+/*
+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 { IPreview } from "./IPreview";
+import { TagID } from "../models";
+import { MatrixEvent } from "matrix-js-sdk/src/models/event";
+import { _t } from "../../../languageHandler";
+import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils";
+import ReplyThread from "../../../components/views/elements/ReplyThread";
+
+export class MessageEventPreview implements IPreview {
+ public getTextFor(event: MatrixEvent, tagId?: TagID): string {
+ let eventContent = event.getContent();
+
+ if (event.isRelation("m.replace")) {
+ // It's an edit, generate the preview on the new text
+ eventContent = event.getContent()['m.new_content'];
+ }
+
+ let body = (eventContent['body'] || '').trim();
+ const msgtype = eventContent['msgtype'];
+ if (!body || !msgtype) return null; // invalid event, no preview
+
+ // XXX: Newer relations have a getRelation() function which is not compatible with replies.
+ const mRelatesTo = event.getWireContent()['m.relates_to'];
+ if (mRelatesTo && mRelatesTo['m.in_reply_to']) {
+ // If this is a reply, get the real reply and use that
+ body = (ReplyThread.stripPlainReply(body) || '').trim();
+ if (!body) return null; // invalid event, no preview
+ }
+
+ if (msgtype === 'm.emote') {
+ return _t("%(senderName)s %(emote)s", {senderName: getSenderName(event), emote: body});
+ }
+
+ if (isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId(), tagId)) {
+ return body;
+ } else {
+ return _t("%(senderName)s: %(message)s", {senderName: getSenderName(event), message: body});
+ }
+ }
+}
diff --git a/src/stores/room-list/previews/NameEventPreview.ts b/src/stores/room-list/previews/NameEventPreview.ts
new file mode 100644
index 0000000000..4197abacfb
--- /dev/null
+++ b/src/stores/room-list/previews/NameEventPreview.ts
@@ -0,0 +1,31 @@
+/*
+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 { IPreview } from "./IPreview";
+import { TagID } from "../models";
+import { MatrixEvent } from "matrix-js-sdk/src/models/event";
+import { getSenderName, isSelf } from "./utils";
+import { _t } from "../../../languageHandler";
+
+export class NameEventPreview implements IPreview {
+ public getTextFor(event: MatrixEvent, tagId?: TagID): string {
+ if (isSelf(event)) {
+ return _t("You changed the room name");
+ } else {
+ return _t("%(senderName)s changed the room name", {senderName: getSenderName(event)});
+ }
+ }
+}
diff --git a/src/stores/room-list/previews/ReactionEventPreview.ts b/src/stores/room-list/previews/ReactionEventPreview.ts
new file mode 100644
index 0000000000..d58f592feb
--- /dev/null
+++ b/src/stores/room-list/previews/ReactionEventPreview.ts
@@ -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 { IPreview } from "./IPreview";
+import { TagID } from "../models";
+import { MatrixEvent } from "matrix-js-sdk/src/models/event";
+import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils";
+import { _t } from "../../../languageHandler";
+
+export class ReactionEventPreview implements IPreview {
+ public getTextFor(event: MatrixEvent, tagId?: TagID): string {
+ const reaction = event.getRelation().key;
+ if (!reaction) return;
+
+ if (isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId(), tagId)) {
+ return reaction;
+ } else {
+ return _t("%(senderName)s: %(reaction)s", {senderName: getSenderName(event), reaction});
+ }
+ }
+}
diff --git a/src/stores/room-list/previews/StickerEventPreview.ts b/src/stores/room-list/previews/StickerEventPreview.ts
new file mode 100644
index 0000000000..f8263a4a45
--- /dev/null
+++ b/src/stores/room-list/previews/StickerEventPreview.ts
@@ -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 { IPreview } from "./IPreview";
+import { TagID } from "../models";
+import { MatrixEvent } from "matrix-js-sdk/src/models/event";
+import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils";
+import { _t } from "../../../languageHandler";
+
+export class StickerEventPreview implements IPreview {
+ public getTextFor(event: MatrixEvent, tagId?: TagID): string {
+ const stickerName = event.getContent()['body'];
+ if (!stickerName) return null;
+
+ if (isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId(), tagId)) {
+ return stickerName;
+ } else {
+ return _t("%(senderName)s: %(stickerName)s", {senderName: getSenderName(event), stickerName});
+ }
+ }
+}
diff --git a/src/stores/room-list/previews/ThirdPartyInviteEventPreview.ts b/src/stores/room-list/previews/ThirdPartyInviteEventPreview.ts
new file mode 100644
index 0000000000..b22cd9fac9
--- /dev/null
+++ b/src/stores/room-list/previews/ThirdPartyInviteEventPreview.ts
@@ -0,0 +1,42 @@
+/*
+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 { IPreview } from "./IPreview";
+import { TagID } from "../models";
+import { MatrixEvent } from "matrix-js-sdk/src/models/event";
+import { getSenderName, isSelf } from "./utils";
+import { _t } from "../../../languageHandler";
+import { isValid3pidInvite } from "../../../RoomInvite";
+
+export class ThirdPartyInviteEventPreview implements IPreview {
+ public getTextFor(event: MatrixEvent, tagId?: TagID): string {
+ if (!isValid3pidInvite(event)) {
+ const targetName = event.getPrevContent().display_name || _t("Someone");
+ if (isSelf(event)) {
+ return _t("You uninvited %(targetName)s", {targetName});
+ } else {
+ return _t("%(senderName)s uninvited %(targetName)s", {senderName: getSenderName(event), targetName});
+ }
+ } else {
+ const targetName = event.getContent().display_name;
+ if (isSelf(event)) {
+ return _t("You invited %(targetName)s", {targetName});
+ } else {
+ return _t("%(senderName)s invited %(targetName)s", {senderName: getSenderName(event), targetName});
+ }
+ }
+ }
+}
diff --git a/src/stores/room-list/previews/TopicEventPreview.ts b/src/stores/room-list/previews/TopicEventPreview.ts
new file mode 100644
index 0000000000..9b499aae8f
--- /dev/null
+++ b/src/stores/room-list/previews/TopicEventPreview.ts
@@ -0,0 +1,31 @@
+/*
+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 { IPreview } from "./IPreview";
+import { TagID } from "../models";
+import { MatrixEvent } from "matrix-js-sdk/src/models/event";
+import { getSenderName, isSelf } from "./utils";
+import { _t } from "../../../languageHandler";
+
+export class TopicEventPreview implements IPreview {
+ public getTextFor(event: MatrixEvent, tagId?: TagID): string {
+ if (isSelf(event)) {
+ return _t("You changed the room topic");
+ } else {
+ return _t("%(senderName)s changed the room topic", {senderName: getSenderName(event)});
+ }
+ }
+}
diff --git a/src/stores/room-list/previews/utils.ts b/src/stores/room-list/previews/utils.ts
new file mode 100644
index 0000000000..ebbecd7bbd
--- /dev/null
+++ b/src/stores/room-list/previews/utils.ts
@@ -0,0 +1,49 @@
+/*
+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 { MatrixEvent } from "matrix-js-sdk/src/models/event";
+import { MatrixClientPeg } from "../../../MatrixClientPeg";
+import { DefaultTagID, TagID } from "../models";
+
+export function isSelf(event: MatrixEvent): boolean {
+ const selfUserId = MatrixClientPeg.get().getUserId();
+ if (event.getType() === 'm.room.member') {
+ return event.getStateKey() === selfUserId;
+ }
+ return event.getSender() === selfUserId;
+}
+
+export function isSelfTarget(event: MatrixEvent): boolean {
+ const selfUserId = MatrixClientPeg.get().getUserId();
+ return event.getStateKey() === selfUserId;
+}
+
+export function shouldPrefixMessagesIn(roomId: string, tagId: TagID): boolean {
+ if (tagId !== DefaultTagID.DM) return true;
+
+ // We don't prefix anything in 1:1s
+ const room = MatrixClientPeg.get().getRoom(roomId);
+ if (!room) return true;
+ return room.currentState.getJoinedMemberCount() !== 2;
+}
+
+export function getSenderName(event: MatrixEvent): string {
+ return event.sender ? event.sender.name : event.getSender();
+}
+
+export function getTargetName(event: MatrixEvent): string {
+ return event.target ? event.target.name : event.getStateKey();
+}