Skip to content

Commit 54f2ff6

Browse files
committed
Update relation collections after redaction
This watches for redactions of relations and updates the relations collection to match, including various aggregations. In addition, a redaction event is emitted on the redaction collection to notify consumers of the change. Part of element-hq/element-web#9574 Part of element-hq/element-web#9485
1 parent e0f9f62 commit 54f2ff6

File tree

3 files changed

+67
-11
lines changed

3 files changed

+67
-11
lines changed

src/models/event-timeline-set.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -744,6 +744,7 @@ EventTimelineSet.prototype._aggregateRelations = function(event) {
744744
relationsWithEventType = relationsWithRelType[eventType] = new Relations(
745745
relationType,
746746
eventType,
747+
this.room,
747748
);
748749
}
749750

src/models/relations.js

Lines changed: 65 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17+
import EventEmitter from 'events';
18+
1719
/**
1820
* A container for relation events that supports easy access to common ways of
1921
* aggregating such events. Each instance holds events that of a single relation
@@ -22,21 +24,29 @@ limitations under the License.
2224
* The typical way to get one of these containers is via
2325
* EventTimelineSet#getRelationsForEvent.
2426
*/
25-
export default class Relations {
27+
export default class Relations extends EventEmitter {
2628
/**
2729
* @param {String} relationType
2830
* The type of relation involved, such as "m.annotation", "m.reference",
2931
* "m.replace", etc.
3032
* @param {String} eventType
3133
* The relation event's type, such as "m.reaction", etc.
34+
* @param {?Room} room
35+
* Room for this container. May be null for non-room cases, such as the
36+
* notification timeline.
3237
*/
33-
constructor(relationType, eventType) {
38+
constructor(relationType, eventType, room) {
39+
super();
3440
this.relationType = relationType;
3541
this.eventType = eventType;
36-
this._events = [];
42+
this._relations = new Set();
3743
this._annotationsByKey = {};
3844
this._annotationsBySender = {};
3945
this._sortedAnnotationsByKey = [];
46+
47+
if (room) {
48+
room.on("Room.beforeRedaction", this._onBeforeRedaction);
49+
}
4050
}
4151

4252
/**
@@ -66,11 +76,11 @@ export default class Relations {
6676
this._aggregateAnnotation(key, event);
6777
}
6878

69-
this._events.push(event);
79+
this._relations.add(event);
7080
}
7181

7282
/**
73-
* Get all events in this collection.
83+
* Get all relation events in this collection.
7484
*
7585
* These are currently in the order of insertion to this collection, which
7686
* won't match timeline order in the case of scrollback.
@@ -79,8 +89,8 @@ export default class Relations {
7989
* @return {Array}
8090
* Relation events in insertion order.
8191
*/
82-
getEvents() {
83-
return this._events;
92+
getRelations() {
93+
return [...this._relations];
8494
}
8595

8696
_aggregateAnnotation(key, event) {
@@ -90,16 +100,16 @@ export default class Relations {
90100

91101
let eventsForKey = this._annotationsByKey[key];
92102
if (!eventsForKey) {
93-
eventsForKey = this._annotationsByKey[key] = [];
103+
eventsForKey = this._annotationsByKey[key] = new Set();
94104
this._sortedAnnotationsByKey.push([key, eventsForKey]);
95105
}
96-
// Add the new event to the list for this key
97-
eventsForKey.push(event);
106+
// Add the new event to the set for this key
107+
eventsForKey.add(event);
98108
// Re-sort the [key, events] pairs in descending order of event count
99109
this._sortedAnnotationsByKey.sort((a, b) => {
100110
const aEvents = a[1];
101111
const bEvents = b[1];
102-
return bEvents.length - aEvents.length;
112+
return bEvents.size - aEvents.size;
103113
});
104114

105115
const sender = event.getSender();
@@ -111,6 +121,49 @@ export default class Relations {
111121
eventsFromSender.push(event);
112122
}
113123

124+
/**
125+
* For relations that are about to be redacted, remove them from aggregation
126+
* data sets and emit an update event.
127+
*
128+
* @param {MatrixEvent} redactedEvent
129+
* The original relation event that is about to be redacted.
130+
*/
131+
_onBeforeRedaction = (redactedEvent) => {
132+
if (!this._relations.has(redactedEvent)) {
133+
return;
134+
}
135+
136+
if (this.relationType === "m.annotation") {
137+
// Remove the redacted annotation from aggregation by key
138+
const content = redactedEvent.getContent();
139+
const relation = content && content["m.relates_to"];
140+
if (!relation) {
141+
return;
142+
}
143+
144+
const key = relation.key;
145+
const eventsForKey = this._annotationsByKey[key];
146+
if (!eventsForKey) {
147+
return;
148+
}
149+
eventsForKey.delete(redactedEvent);
150+
151+
// Re-sort the [key, events] pairs in descending order of event count
152+
this._sortedAnnotationsByKey.sort((a, b) => {
153+
const aEvents = a[1];
154+
const bEvents = b[1];
155+
return bEvents.size - aEvents.size;
156+
});
157+
}
158+
159+
// Dispatch a redaction event on this collection. `setTimeout` is used
160+
// to wait until the next event loop iteration by which time the event
161+
// has actually been marked as redacted.
162+
setTimeout(() => {
163+
this.emit("Relations.redaction");
164+
}, 0);
165+
}
166+
114167
/**
115168
* Get all events in this collection grouped by key and sorted by descending
116169
* event count in each group.
@@ -119,6 +172,7 @@ export default class Relations {
119172
*
120173
* @return {Array}
121174
* An array of [key, events] pairs sorted by descending event count.
175+
* The events are stored in a Set (which preserves insertion order).
122176
*/
123177
getSortedAnnotationsByKey() {
124178
if (this.relationType !== "m.annotation") {

src/models/room.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1011,6 +1011,7 @@ Room.prototype._addLiveEvent = function(event, duplicateStrategy) {
10111011
// if we know about this event, redact its contents now.
10121012
const redactedEvent = this.getUnfilteredTimelineSet().findEventById(redactId);
10131013
if (redactedEvent) {
1014+
this.emit("Room.beforeRedaction", redactedEvent, event, this);
10141015
redactedEvent.makeRedacted(event);
10151016
this.emit("Room.redaction", event, this);
10161017

0 commit comments

Comments
 (0)