more local echo fixes for redacting a reaction + cleanup
This commit is contained in:
parent
94635a18e0
commit
bbcf0d2572
4 changed files with 78 additions and 63 deletions
|
@ -14,8 +14,6 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {getRelationFromContent} from "./relations.js";
|
|
||||||
|
|
||||||
export class PendingAnnotations {
|
export class PendingAnnotations {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.aggregatedAnnotations = new Map();
|
this.aggregatedAnnotations = new Map();
|
||||||
|
@ -25,7 +23,7 @@ export class PendingAnnotations {
|
||||||
|
|
||||||
/** adds either a pending annotation entry, or a remote annotation entry with a pending redaction */
|
/** adds either a pending annotation entry, or a remote annotation entry with a pending redaction */
|
||||||
add(entry) {
|
add(entry) {
|
||||||
const {key} = entry.ownOrRedactedRelation;
|
const {key} = (entry.redactingEntry || entry).relation;
|
||||||
if (!key) {
|
if (!key) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -42,7 +40,7 @@ export class PendingAnnotations {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._entries.splice(idx, 1);
|
this._entries.splice(idx, 1);
|
||||||
const {key} = entry.ownOrRedactedRelation;
|
const {key} = (entry.redactingEntry || entry).relation;
|
||||||
let count = this.aggregatedAnnotations.get(key);
|
let count = this.aggregatedAnnotations.get(key);
|
||||||
if (count !== undefined) {
|
if (count !== undefined) {
|
||||||
const addend = entry.isRedaction ? 1 : -1;
|
const addend = entry.isRedaction ? 1 : -1;
|
||||||
|
@ -56,8 +54,7 @@ export class PendingAnnotations {
|
||||||
|
|
||||||
findForKey(key) {
|
findForKey(key) {
|
||||||
return this._entries.find(e => {
|
return this._entries.find(e => {
|
||||||
const relation = getRelationFromContent(e.content);
|
if (e.relation?.key === key) {
|
||||||
if (relation && relation.key === key) {
|
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -65,8 +62,7 @@ export class PendingAnnotations {
|
||||||
|
|
||||||
findRedactionForKey(key) {
|
findRedactionForKey(key) {
|
||||||
return this._entries.find(e => {
|
return this._entries.find(e => {
|
||||||
const relation = e.redactingRelation;
|
if (e.redactingEntry?.relation?.key === key) {
|
||||||
if (relation && relation.key === key) {
|
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -22,7 +22,7 @@ import {TimelineReader} from "./persistence/TimelineReader.js";
|
||||||
import {PendingEventEntry} from "./entries/PendingEventEntry.js";
|
import {PendingEventEntry} from "./entries/PendingEventEntry.js";
|
||||||
import {RoomMember} from "../members/RoomMember.js";
|
import {RoomMember} from "../members/RoomMember.js";
|
||||||
import {PowerLevels} from "./PowerLevels.js";
|
import {PowerLevels} from "./PowerLevels.js";
|
||||||
import {getRelation, getRelationFromContent, ANNOTATION_RELATION_TYPE} from "./relations.js";
|
import {getRelation, ANNOTATION_RELATION_TYPE} from "./relations.js";
|
||||||
import {REDACTION_TYPE} from "../common.js";
|
import {REDACTION_TYPE} from "../common.js";
|
||||||
|
|
||||||
export class Timeline {
|
export class Timeline {
|
||||||
|
@ -120,42 +120,36 @@ export class Timeline {
|
||||||
// so if we are redacting a relation, we can pass the redaction
|
// so if we are redacting a relation, we can pass the redaction
|
||||||
// to the relation target and the removal of the relation can
|
// to the relation target and the removal of the relation can
|
||||||
// be taken into account for local echo.
|
// be taken into account for local echo.
|
||||||
let redactingRelation;
|
let redactingEntry;
|
||||||
if (pe.eventType === REDACTION_TYPE) {
|
if (pe.eventType === REDACTION_TYPE) {
|
||||||
if (pe.relatedEventId) {
|
redactingEntry = await this._getOrLoadEntry(pe.relatedTxnId, pe.relatedEventId);
|
||||||
const txn = await this._storage.readWriteTxn([
|
|
||||||
this._storage.storeNames.timelineEvents,
|
|
||||||
]);
|
|
||||||
const redactionTargetEntry = await txn.timelineEvents.getByEventId(this._roomId, pe.relatedEventId);
|
|
||||||
if (redactionTargetEntry) {
|
|
||||||
redactingRelation = getRelation(redactionTargetEntry.event);
|
|
||||||
}
|
|
||||||
} else if (pe.relatedTxnId) {
|
|
||||||
// also look for redacting relation in pending events, in case the target is already being sent
|
|
||||||
for (const p of this._localEntries) {
|
|
||||||
if (p.id === pe.relatedTxnId) {
|
|
||||||
redactingRelation = getRelationFromContent(p.content);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const pee = new PendingEventEntry({
|
const pee = new PendingEventEntry({
|
||||||
pendingEvent: pe, member: this._ownMember,
|
pendingEvent: pe, member: this._ownMember,
|
||||||
clock: this._clock, redactingRelation
|
clock: this._clock, redactingEntry
|
||||||
});
|
});
|
||||||
this._applyAndEmitLocalRelationChange(pee, target => target.addLocalRelation(pee));
|
this._applyAndEmitLocalRelationChange(pee, target => target.addLocalRelation(pee));
|
||||||
return pee;
|
return pee;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
_applyAndEmitLocalRelationChange(pee, updater) {
|
_applyAndEmitLocalRelationChange(pee, updater) {
|
||||||
|
// this is the contract of findAndUpdate, used in _findAndUpdateRelatedEntry
|
||||||
const updateOrFalse = e => {
|
const updateOrFalse = e => {
|
||||||
const params = updater(e);
|
const params = updater(e);
|
||||||
return params ? params : false;
|
return params ? params : false;
|
||||||
};
|
};
|
||||||
|
this._findAndUpdateRelatedEntry(pee.pendingEvent.relatedTxnId, pee.relatedEventId, updateOrFalse);
|
||||||
|
// also look for a relation target to update with this redaction
|
||||||
|
const {redactingEntry} = pee;
|
||||||
|
if (redactingEntry) {
|
||||||
|
// redactingEntry might be a PendingEventEntry or an EventEntry, so don't assume pendingEvent
|
||||||
|
const relatedTxnId = redactingEntry.pendingEvent?.relatedTxnId;
|
||||||
|
this._findAndUpdateRelatedEntry(relatedTxnId, redactingEntry.relatedEventId, updateOrFalse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_findAndUpdateRelatedEntry(relatedTxnId, relatedEventId, updateOrFalse) {
|
||||||
let found = false;
|
let found = false;
|
||||||
const {relatedTxnId} = pee.pendingEvent;
|
|
||||||
// first, look in local entries based on txn id
|
// first, look in local entries based on txn id
|
||||||
if (relatedTxnId) {
|
if (relatedTxnId) {
|
||||||
found = this._localEntries.findAndUpdate(
|
found = this._localEntries.findAndUpdate(
|
||||||
|
@ -164,18 +158,9 @@ export class Timeline {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// if not found here, look in remote entries based on event id
|
// if not found here, look in remote entries based on event id
|
||||||
if (!found && pee.relatedEventId) {
|
if (!found && relatedEventId) {
|
||||||
this._remoteEntries.findAndUpdate(
|
this._remoteEntries.findAndUpdate(
|
||||||
e => e.id === pee.relatedEventId,
|
e => e.id === relatedEventId,
|
||||||
updateOrFalse
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// also look for a relation target to update with this redaction
|
|
||||||
if (pee.redactingRelation) {
|
|
||||||
const eventId = pee.redactingRelation.event_id;
|
|
||||||
// TODO: also support reacting to pending entries
|
|
||||||
this._remoteEntries.findAndUpdate(
|
|
||||||
e => e.id === eventId,
|
|
||||||
updateOrFalse
|
updateOrFalse
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -233,8 +218,8 @@ export class Timeline {
|
||||||
relationTarget.addLocalRelation(pee);
|
relationTarget.addLocalRelation(pee);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (pee.redactingRelation) {
|
if (pee.redactingEntry) {
|
||||||
const eventId = pee.redactingRelation.event_id;
|
const eventId = pee.redactingEntry.relatedEventId;
|
||||||
const relationTarget = entries.find(e => e.id === eventId);
|
const relationTarget = entries.find(e => e.id === eventId);
|
||||||
if (relationTarget) {
|
if (relationTarget) {
|
||||||
relationTarget.addLocalRelation(pee);
|
relationTarget.addLocalRelation(pee);
|
||||||
|
@ -278,6 +263,32 @@ export class Timeline {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _getOrLoadEntry(txnId, eventId) {
|
||||||
|
if (txnId) {
|
||||||
|
// also look for redacting relation in pending events, in case the target is already being sent
|
||||||
|
for (const p of this._localEntries) {
|
||||||
|
if (p.id === txnId) {
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (eventId) {
|
||||||
|
const loadedEntry = this.getByEventId(eventId);
|
||||||
|
if (loadedEntry) {
|
||||||
|
return loadedEntry;
|
||||||
|
} else {
|
||||||
|
const txn = await this._storage.readWriteTxn([
|
||||||
|
this._storage.storeNames.timelineEvents,
|
||||||
|
]);
|
||||||
|
const redactionTargetEntry = await txn.timelineEvents.getByEventId(this._roomId, eventId);
|
||||||
|
if (redactionTargetEntry) {
|
||||||
|
return new EventEntry(redactionTargetEntry, this._fragmentIdComparer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
getByEventId(eventId) {
|
getByEventId(eventId) {
|
||||||
for (let i = 0; i < this._remoteEntries.length; i += 1) {
|
for (let i = 0; i < this._remoteEntries.length; i += 1) {
|
||||||
const entry = this._remoteEntries.get(i);
|
const entry = this._remoteEntries.get(i);
|
||||||
|
|
|
@ -16,9 +16,11 @@ limitations under the License.
|
||||||
|
|
||||||
import {BaseEntry} from "./BaseEntry.js";
|
import {BaseEntry} from "./BaseEntry.js";
|
||||||
import {REDACTION_TYPE} from "../../common.js";
|
import {REDACTION_TYPE} from "../../common.js";
|
||||||
import {createAnnotation, ANNOTATION_RELATION_TYPE} from "../relations.js";
|
import {createAnnotation, ANNOTATION_RELATION_TYPE, getRelationFromContent} from "../relations.js";
|
||||||
import {PendingAnnotations} from "../PendingAnnotations.js";
|
import {PendingAnnotations} from "../PendingAnnotations.js";
|
||||||
|
|
||||||
|
/** Deals mainly with local echo for relations and redactions,
|
||||||
|
* so it is shared between PendingEventEntry and EventEntry */
|
||||||
export class BaseEventEntry extends BaseEntry {
|
export class BaseEventEntry extends BaseEntry {
|
||||||
constructor(fragmentIdComparer) {
|
constructor(fragmentIdComparer) {
|
||||||
super(fragmentIdComparer);
|
super(fragmentIdComparer);
|
||||||
|
@ -59,9 +61,9 @@ export class BaseEventEntry extends BaseEntry {
|
||||||
return "isRedacted";
|
return "isRedacted";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const relation = entry.ownOrRedactedRelation;
|
const relationEntry = entry.redactingEntry || entry;
|
||||||
if (relation && relation.event_id === this.id) {
|
if (relationEntry.isRelationForId(this.id)) {
|
||||||
if (relation.rel_type === ANNOTATION_RELATION_TYPE) {
|
if (relationEntry.relation.rel_type === ANNOTATION_RELATION_TYPE) {
|
||||||
if (!this._pendingAnnotations) {
|
if (!this._pendingAnnotations) {
|
||||||
this._pendingAnnotations = new PendingAnnotations();
|
this._pendingAnnotations = new PendingAnnotations();
|
||||||
}
|
}
|
||||||
|
@ -87,9 +89,9 @@ export class BaseEventEntry extends BaseEntry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const relation = entry.ownOrRedactedRelation;
|
const relationEntry = entry.redactingEntry || entry;
|
||||||
if (relation && relation.event_id === this.id) {
|
if (relationEntry.isRelationForId(this.id)) {
|
||||||
if (relation.rel_type === ANNOTATION_RELATION_TYPE && this._pendingAnnotations) {
|
if (relationEntry.relation.rel_type === ANNOTATION_RELATION_TYPE && this._pendingAnnotations) {
|
||||||
this._pendingAnnotations.remove(entry);
|
this._pendingAnnotations.remove(entry);
|
||||||
if (this._pendingAnnotations.isEmpty) {
|
if (this._pendingAnnotations.isEmpty) {
|
||||||
this._pendingAnnotations = null;
|
this._pendingAnnotations = null;
|
||||||
|
@ -121,6 +123,14 @@ export class BaseEventEntry extends BaseEntry {
|
||||||
return createAnnotation(this.id, key);
|
return createAnnotation(this.id, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isRelationForId(id) {
|
||||||
|
return id && this.relation?.event_id === id;
|
||||||
|
}
|
||||||
|
|
||||||
|
get relation() {
|
||||||
|
return getRelationFromContent(this.content);
|
||||||
|
}
|
||||||
|
|
||||||
get pendingAnnotations() {
|
get pendingAnnotations() {
|
||||||
return this._pendingAnnotations?.aggregatedAnnotations;
|
return this._pendingAnnotations?.aggregatedAnnotations;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,16 +16,15 @@ limitations under the License.
|
||||||
|
|
||||||
import {PENDING_FRAGMENT_ID} from "./BaseEntry.js";
|
import {PENDING_FRAGMENT_ID} from "./BaseEntry.js";
|
||||||
import {BaseEventEntry} from "./BaseEventEntry.js";
|
import {BaseEventEntry} from "./BaseEventEntry.js";
|
||||||
import {getRelationFromContent} from "../relations.js";
|
|
||||||
|
|
||||||
export class PendingEventEntry extends BaseEventEntry {
|
export class PendingEventEntry extends BaseEventEntry {
|
||||||
constructor({pendingEvent, member, clock, redactingRelation}) {
|
constructor({pendingEvent, member, clock, redactingEntry}) {
|
||||||
super(null);
|
super(null);
|
||||||
this._pendingEvent = pendingEvent;
|
this._pendingEvent = pendingEvent;
|
||||||
/** @type {RoomMember} */
|
/** @type {RoomMember} */
|
||||||
this._member = member;
|
this._member = member;
|
||||||
this._clock = clock;
|
this._clock = clock;
|
||||||
this._redactingRelation = redactingRelation;
|
this._redactingEntry = redactingEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
get fragmentId() {
|
get fragmentId() {
|
||||||
|
@ -84,19 +83,18 @@ export class PendingEventEntry extends BaseEventEntry {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isRelationForId(id) {
|
||||||
|
if (id && id === this._pendingEvent.relatedTxnId) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.isRelationForId(id);
|
||||||
|
}
|
||||||
|
|
||||||
get relatedEventId() {
|
get relatedEventId() {
|
||||||
return this._pendingEvent.relatedEventId;
|
return this._pendingEvent.relatedEventId;
|
||||||
}
|
}
|
||||||
|
|
||||||
get redactingRelation() {
|
get redactingEntry() {
|
||||||
return this._redactingRelation;
|
return this._redactingEntry;
|
||||||
}
|
|
||||||
/**
|
|
||||||
* returns either the relationship on this entry,
|
|
||||||
* or the relationship this entry is redacting.
|
|
||||||
*
|
|
||||||
* Useful while aggregating relations for local echo. */
|
|
||||||
get ownOrRedactedRelation() {
|
|
||||||
return this.redactingRelation || getRelationFromContent(this._pendingEvent.content);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue