1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-12-05 17:02:07 +03:00

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 https://github.com/vector-im/riot-web/issues/9574
Part of https://github.com/vector-im/riot-web/issues/9485
This commit is contained in:
J. Ryan Stinnett
2019-05-09 15:08:16 +01:00
parent 761806c678
commit 53d8cf0852
3 changed files with 67 additions and 11 deletions

View File

@@ -744,6 +744,7 @@ EventTimelineSet.prototype._aggregateRelations = function(event) {
relationsWithEventType = relationsWithRelType[eventType] = new Relations( relationsWithEventType = relationsWithRelType[eventType] = new Relations(
relationType, relationType,
eventType, eventType,
this.room,
); );
} }

View File

@@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import EventEmitter from 'events';
/** /**
* A container for relation events that supports easy access to common ways of * A container for relation events that supports easy access to common ways of
* aggregating such events. Each instance holds events that of a single relation * aggregating such events. Each instance holds events that of a single relation
@@ -22,21 +24,29 @@ limitations under the License.
* The typical way to get one of these containers is via * The typical way to get one of these containers is via
* EventTimelineSet#getRelationsForEvent. * EventTimelineSet#getRelationsForEvent.
*/ */
export default class Relations { export default class Relations extends EventEmitter {
/** /**
* @param {String} relationType * @param {String} relationType
* The type of relation involved, such as "m.annotation", "m.reference", * The type of relation involved, such as "m.annotation", "m.reference",
* "m.replace", etc. * "m.replace", etc.
* @param {String} eventType * @param {String} eventType
* The relation event's type, such as "m.reaction", etc. * The relation event's type, such as "m.reaction", etc.
* @param {?Room} room
* Room for this container. May be null for non-room cases, such as the
* notification timeline.
*/ */
constructor(relationType, eventType) { constructor(relationType, eventType, room) {
super();
this.relationType = relationType; this.relationType = relationType;
this.eventType = eventType; this.eventType = eventType;
this._events = []; this._relations = new Set();
this._annotationsByKey = {}; this._annotationsByKey = {};
this._annotationsBySender = {}; this._annotationsBySender = {};
this._sortedAnnotationsByKey = []; this._sortedAnnotationsByKey = [];
if (room) {
room.on("Room.beforeRedaction", this._onBeforeRedaction);
}
} }
/** /**
@@ -66,11 +76,11 @@ export default class Relations {
this._aggregateAnnotation(key, event); this._aggregateAnnotation(key, event);
} }
this._events.push(event); this._relations.add(event);
} }
/** /**
* Get all events in this collection. * Get all relation events in this collection.
* *
* These are currently in the order of insertion to this collection, which * These are currently in the order of insertion to this collection, which
* won't match timeline order in the case of scrollback. * won't match timeline order in the case of scrollback.
@@ -79,8 +89,8 @@ export default class Relations {
* @return {Array} * @return {Array}
* Relation events in insertion order. * Relation events in insertion order.
*/ */
getEvents() { getRelations() {
return this._events; return [...this._relations];
} }
_aggregateAnnotation(key, event) { _aggregateAnnotation(key, event) {
@@ -90,16 +100,16 @@ export default class Relations {
let eventsForKey = this._annotationsByKey[key]; let eventsForKey = this._annotationsByKey[key];
if (!eventsForKey) { if (!eventsForKey) {
eventsForKey = this._annotationsByKey[key] = []; eventsForKey = this._annotationsByKey[key] = new Set();
this._sortedAnnotationsByKey.push([key, eventsForKey]); this._sortedAnnotationsByKey.push([key, eventsForKey]);
} }
// Add the new event to the list for this key // Add the new event to the set for this key
eventsForKey.push(event); eventsForKey.add(event);
// Re-sort the [key, events] pairs in descending order of event count // Re-sort the [key, events] pairs in descending order of event count
this._sortedAnnotationsByKey.sort((a, b) => { this._sortedAnnotationsByKey.sort((a, b) => {
const aEvents = a[1]; const aEvents = a[1];
const bEvents = b[1]; const bEvents = b[1];
return bEvents.length - aEvents.length; return bEvents.size - aEvents.size;
}); });
const sender = event.getSender(); const sender = event.getSender();
@@ -111,6 +121,49 @@ export default class Relations {
eventsFromSender.push(event); eventsFromSender.push(event);
} }
/**
* For relations that are about to be redacted, remove them from aggregation
* data sets and emit an update event.
*
* @param {MatrixEvent} redactedEvent
* The original relation event that is about to be redacted.
*/
_onBeforeRedaction = (redactedEvent) => {
if (!this._relations.has(redactedEvent)) {
return;
}
if (this.relationType === "m.annotation") {
// Remove the redacted annotation from aggregation by key
const content = redactedEvent.getContent();
const relation = content && content["m.relates_to"];
if (!relation) {
return;
}
const key = relation.key;
const eventsForKey = this._annotationsByKey[key];
if (!eventsForKey) {
return;
}
eventsForKey.delete(redactedEvent);
// Re-sort the [key, events] pairs in descending order of event count
this._sortedAnnotationsByKey.sort((a, b) => {
const aEvents = a[1];
const bEvents = b[1];
return bEvents.size - aEvents.size;
});
}
// Dispatch a redaction event on this collection. `setTimeout` is used
// to wait until the next event loop iteration by which time the event
// has actually been marked as redacted.
setTimeout(() => {
this.emit("Relations.redaction");
}, 0);
}
/** /**
* Get all events in this collection grouped by key and sorted by descending * Get all events in this collection grouped by key and sorted by descending
* event count in each group. * event count in each group.
@@ -119,6 +172,7 @@ export default class Relations {
* *
* @return {Array} * @return {Array}
* An array of [key, events] pairs sorted by descending event count. * An array of [key, events] pairs sorted by descending event count.
* The events are stored in a Set (which preserves insertion order).
*/ */
getSortedAnnotationsByKey() { getSortedAnnotationsByKey() {
if (this.relationType !== "m.annotation") { if (this.relationType !== "m.annotation") {

View File

@@ -1011,6 +1011,7 @@ Room.prototype._addLiveEvent = function(event, duplicateStrategy) {
// if we know about this event, redact its contents now. // if we know about this event, redact its contents now.
const redactedEvent = this.getUnfilteredTimelineSet().findEventById(redactId); const redactedEvent = this.getUnfilteredTimelineSet().findEventById(redactId);
if (redactedEvent) { if (redactedEvent) {
this.emit("Room.beforeRedaction", redactedEvent, event, this);
redactedEvent.makeRedacted(event); redactedEvent.makeRedacted(event);
this.emit("Room.redaction", event, this); this.emit("Room.redaction", event, this);