diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js
index f4334ffe87..63f23f22f3 100644
--- a/src/components/structures/MessagePanel.js
+++ b/src/components/structures/MessagePanel.js
@@ -1027,6 +1027,97 @@ class CreationGrouper {
}
}
+class RedactionGrouper {
+ static canStartGroup = function(panel, ev) {
+ return panel._shouldShowEvent(ev) && ev.isRedacted();
+ }
+
+ constructor(panel, ev, prevEvent, lastShownEvent) {
+ this.panel = panel;
+ this.readMarker = panel._readMarkerForEvent(
+ ev.getId(),
+ ev === lastShownEvent,
+ );
+ this.events = [ev];
+ this.prevEvent = prevEvent;
+ this.lastShownEvent = lastShownEvent;
+ }
+
+ shouldGroup(ev) {
+ if (this.panel._wantsDateSeparator(this.events[0], ev.getDate())) {
+ return false;
+ }
+ return ev.isRedacted();
+ }
+
+ add(ev) {
+ this.readMarker = this.readMarker || this.panel._readMarkerForEvent(
+ ev.getId(),
+ ev === this.lastShownEvent,
+ );
+ this.events.push(ev);
+ }
+
+ getTiles() {
+ if (!this.events || !this.events.length) return [];
+
+ const DateSeparator = sdk.getComponent('messages.DateSeparator');
+ const EventListSummary = sdk.getComponent('views.elements.EventListSummary');
+
+ const panel = this.panel;
+ const ret = [];
+ const lastShownEvent = this.lastShownEvent;
+
+ if (panel._wantsDateSeparator(this.prevEvent, this.events[0].getDate())) {
+ const ts = this.events[0].getTs();
+ ret.push(
+
,
+ );
+ }
+
+ const key = "redactioneventlistsummary-" + (
+ this.prevEvent ? this.events[0].getId() : "initial"
+ );
+
+ const senders = new Set();
+ let eventTiles = this.events.map((e) => {
+ senders.add(e.sender);
+ // In order to prevent DateSeparators from appearing in the expanded form,
+ // render each member event as if the previous one was itself.
+ // This way, the timestamp of the previous event === the
+ // timestamp of the current event, and no DateSeparator is inserted.
+ return panel._getTilesForEvent(e, e, e === lastShownEvent);
+ }).reduce((a, b) => a.concat(b), []);
+
+ if (eventTiles.length === 0) {
+ eventTiles = null;
+ }
+
+ ret.push(
+
+ { eventTiles }
+ ,
+ );
+
+ if (this.readMarker) {
+ ret.push(this.readMarker);
+ }
+
+ return ret;
+ }
+
+ getNewPrevEvent() {
+ return this.events[0];
+ }
+}
+
// Wrap consecutive member events in a ListSummary, ignore if redacted
class MemberGrouper {
static canStartGroup = function(panel, ev) {
@@ -1137,4 +1228,4 @@ class MemberGrouper {
}
// all the grouper classes that we use
-const groupers = [CreationGrouper, MemberGrouper];
+const groupers = [CreationGrouper, MemberGrouper, RedactionGrouper];
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 34a346fef0..63b19831bb 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -2553,6 +2553,8 @@
"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.",
+ "%(count)s messages deleted.|other": "%(count)s messages deleted.",
+ "%(count)s messages deleted.|one": "%(count)s message deleted.",
"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!",
"To set up a filter, drag a community avatar over to the filter panel on the far left hand side of the screen. You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.": "To set up a filter, drag a community avatar over to the filter panel on the far left hand side of the screen. You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.",