summaryrefslogtreecommitdiff
path: root/includes/external/matrix/node_modules/matrix-js-sdk/lib/models/thread.js
diff options
context:
space:
mode:
authorRaindropsSys <contact@minteck.org>2023-04-24 14:03:36 +0200
committerRaindropsSys <contact@minteck.org>2023-04-24 14:03:36 +0200
commit633c92eae865e957121e08de634aeee11a8b3992 (patch)
tree09d881bee1dae0b6eee49db1dfaf0f500240606c /includes/external/matrix/node_modules/matrix-js-sdk/lib/models/thread.js
parentc4657e4509733699c0f26a3c900bab47e915d5a0 (diff)
downloadpluralconnect-633c92eae865e957121e08de634aeee11a8b3992.tar.gz
pluralconnect-633c92eae865e957121e08de634aeee11a8b3992.tar.bz2
pluralconnect-633c92eae865e957121e08de634aeee11a8b3992.zip
Updated 18 files, added 1692 files and deleted includes/system/compare.inc (automated)
Diffstat (limited to 'includes/external/matrix/node_modules/matrix-js-sdk/lib/models/thread.js')
-rw-r--r--includes/external/matrix/node_modules/matrix-js-sdk/lib/models/thread.js581
1 files changed, 581 insertions, 0 deletions
diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/models/thread.js b/includes/external/matrix/node_modules/matrix-js-sdk/lib/models/thread.js
new file mode 100644
index 0000000..bc47b2e
--- /dev/null
+++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/models/thread.js
@@ -0,0 +1,581 @@
+"use strict";
+
+var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.ThreadFilterType = exports.ThreadEvent = exports.Thread = exports.THREAD_RELATION_TYPE = exports.FeatureSupport = exports.FILTER_RELATED_BY_SENDERS = exports.FILTER_RELATED_BY_REL_TYPES = void 0;
+exports.determineFeatureSupport = determineFeatureSupport;
+exports.threadFilterTypeToFilter = threadFilterTypeToFilter;
+var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
+var _client = require("../client");
+var _ReEmitter = require("../ReEmitter");
+var _event = require("../@types/event");
+var _event2 = require("./event");
+var _eventTimeline = require("./event-timeline");
+var _eventTimelineSet = require("./event-timeline-set");
+var _room = require("./room");
+var _NamespacedValue = require("../NamespacedValue");
+var _logger = require("../logger");
+var _readReceipt = require("./read-receipt");
+var _read_receipts = require("../@types/read_receipts");
+function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
+function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
+let ThreadEvent;
+exports.ThreadEvent = ThreadEvent;
+(function (ThreadEvent) {
+ ThreadEvent["New"] = "Thread.new";
+ ThreadEvent["Update"] = "Thread.update";
+ ThreadEvent["NewReply"] = "Thread.newReply";
+ ThreadEvent["ViewThread"] = "Thread.viewThread";
+ ThreadEvent["Delete"] = "Thread.delete";
+})(ThreadEvent || (exports.ThreadEvent = ThreadEvent = {}));
+let FeatureSupport;
+exports.FeatureSupport = FeatureSupport;
+(function (FeatureSupport) {
+ FeatureSupport[FeatureSupport["None"] = 0] = "None";
+ FeatureSupport[FeatureSupport["Experimental"] = 1] = "Experimental";
+ FeatureSupport[FeatureSupport["Stable"] = 2] = "Stable";
+})(FeatureSupport || (exports.FeatureSupport = FeatureSupport = {}));
+function determineFeatureSupport(stable, unstable) {
+ if (stable) {
+ return FeatureSupport.Stable;
+ } else if (unstable) {
+ return FeatureSupport.Experimental;
+ } else {
+ return FeatureSupport.None;
+ }
+}
+class Thread extends _readReceipt.ReadReceipt {
+ /**
+ * A reference to all the events ID at the bottom of the threads
+ */
+
+ /**
+ * An array of events to add to the timeline once the thread has been initialised
+ * with server suppport.
+ */
+
+ constructor(id, rootEvent, opts) {
+ var _opts$pendingEventOrd;
+ super();
+ this.id = id;
+ this.rootEvent = rootEvent;
+ (0, _defineProperty2.default)(this, "timelineSet", void 0);
+ (0, _defineProperty2.default)(this, "timeline", []);
+ (0, _defineProperty2.default)(this, "_currentUserParticipated", false);
+ (0, _defineProperty2.default)(this, "reEmitter", void 0);
+ (0, _defineProperty2.default)(this, "lastEvent", void 0);
+ (0, _defineProperty2.default)(this, "replyCount", 0);
+ (0, _defineProperty2.default)(this, "lastPendingEvent", void 0);
+ (0, _defineProperty2.default)(this, "pendingReplyCount", 0);
+ (0, _defineProperty2.default)(this, "room", void 0);
+ (0, _defineProperty2.default)(this, "client", void 0);
+ (0, _defineProperty2.default)(this, "pendingEventOrdering", void 0);
+ (0, _defineProperty2.default)(this, "initialEventsFetched", !Thread.hasServerSideSupport);
+ (0, _defineProperty2.default)(this, "replayEvents", []);
+ (0, _defineProperty2.default)(this, "onBeforeRedaction", (event, redaction) => {
+ if (event !== null && event !== void 0 && event.isRelation(THREAD_RELATION_TYPE.name) && this.room.eventShouldLiveIn(event).threadId === this.id && event.getId() !== this.id &&
+ // the root event isn't counted in the length so ignore this redaction
+ !redaction.status // only respect it when it succeeds
+ ) {
+ this.replyCount--;
+ this.updatePendingReplyCount();
+ this.emit(ThreadEvent.Update, this);
+ }
+ });
+ (0, _defineProperty2.default)(this, "onRedaction", async event => {
+ if (event.threadRootId !== this.id) return; // ignore redactions for other timelines
+ if (this.replyCount <= 0) {
+ for (const threadEvent of this.timeline) {
+ this.clearEventMetadata(threadEvent);
+ }
+ this.lastEvent = this.rootEvent;
+ this._currentUserParticipated = false;
+ this.emit(ThreadEvent.Delete, this);
+ } else {
+ await this.updateThreadMetadata();
+ }
+ });
+ (0, _defineProperty2.default)(this, "onTimelineEvent", (event, room, toStartOfTimeline) => {
+ // Add a synthesized receipt when paginating forward in the timeline
+ if (!toStartOfTimeline) {
+ room.addLocalEchoReceipt(event.getSender(), event, _read_receipts.ReceiptType.Read);
+ }
+ this.onEcho(event, toStartOfTimeline !== null && toStartOfTimeline !== void 0 ? toStartOfTimeline : false);
+ });
+ (0, _defineProperty2.default)(this, "onLocalEcho", event => {
+ this.onEcho(event, false);
+ });
+ (0, _defineProperty2.default)(this, "onEcho", async (event, toStartOfTimeline) => {
+ if (event.threadRootId !== this.id) return; // ignore echoes for other timelines
+ if (this.lastEvent === event) return; // ignore duplicate events
+ await this.updateThreadMetadata();
+ if (!event.isRelation(THREAD_RELATION_TYPE.name)) return; // don't send a new reply event for reactions or edits
+ if (toStartOfTimeline) return; // ignore messages added to the start of the timeline
+ this.emit(ThreadEvent.NewReply, this, event);
+ });
+ if (!(opts !== null && opts !== void 0 && opts.room)) {
+ // Logging/debugging for https://github.com/vector-im/element-web/issues/22141
+ // Hope is that we end up with a more obvious stack trace.
+ throw new Error("element-web#22141: A thread requires a room in order to function");
+ }
+ this.room = opts.room;
+ this.client = opts.client;
+ this.pendingEventOrdering = (_opts$pendingEventOrd = opts.pendingEventOrdering) !== null && _opts$pendingEventOrd !== void 0 ? _opts$pendingEventOrd : _client.PendingEventOrdering.Chronological;
+ this.timelineSet = new _eventTimelineSet.EventTimelineSet(this.room, {
+ timelineSupport: true,
+ pendingEvents: true
+ }, this.client, this);
+ this.reEmitter = new _ReEmitter.TypedReEmitter(this);
+ this.reEmitter.reEmit(this.timelineSet, [_room.RoomEvent.Timeline, _room.RoomEvent.TimelineReset]);
+ this.room.on(_event2.MatrixEventEvent.BeforeRedaction, this.onBeforeRedaction);
+ this.room.on(_room.RoomEvent.Redaction, this.onRedaction);
+ this.room.on(_room.RoomEvent.LocalEchoUpdated, this.onLocalEcho);
+ this.timelineSet.on(_room.RoomEvent.Timeline, this.onTimelineEvent);
+ this.processReceipts(opts.receipts);
+
+ // even if this thread is thought to be originating from this client, we initialise it as we may be in a
+ // gappy sync and a thread around this event may already exist.
+ this.updateThreadMetadata();
+ this.setEventMetadata(this.rootEvent);
+ }
+ async fetchRootEvent() {
+ this.rootEvent = this.room.findEventById(this.id);
+ // If the rootEvent does not exist in the local stores, then fetch it from the server.
+ try {
+ const eventData = await this.client.fetchRoomEvent(this.roomId, this.id);
+ const mapper = this.client.getEventMapper();
+ this.rootEvent = mapper(eventData); // will merge with existing event object if such is known
+ } catch (e) {
+ _logger.logger.error("Failed to fetch thread root to construct thread with", e);
+ }
+ await this.processEvent(this.rootEvent);
+ }
+ static setServerSideSupport(status) {
+ Thread.hasServerSideSupport = status;
+ if (status !== FeatureSupport.Stable) {
+ FILTER_RELATED_BY_SENDERS.setPreferUnstable(true);
+ FILTER_RELATED_BY_REL_TYPES.setPreferUnstable(true);
+ THREAD_RELATION_TYPE.setPreferUnstable(true);
+ }
+ }
+ static setServerSideListSupport(status) {
+ Thread.hasServerSideListSupport = status;
+ }
+ static setServerSideFwdPaginationSupport(status) {
+ Thread.hasServerSideFwdPaginationSupport = status;
+ }
+ get roomState() {
+ return this.room.getLiveTimeline().getState(_eventTimeline.EventTimeline.FORWARDS);
+ }
+ addEventToTimeline(event, toStartOfTimeline) {
+ if (!this.findEventById(event.getId())) {
+ this.timelineSet.addEventToTimeline(event, this.liveTimeline, {
+ toStartOfTimeline,
+ fromCache: false,
+ roomState: this.roomState
+ });
+ this.timeline = this.events;
+ }
+ }
+ addEvents(events, toStartOfTimeline) {
+ events.forEach(ev => this.addEvent(ev, toStartOfTimeline, false));
+ this.updateThreadMetadata();
+ }
+
+ /**
+ * Add an event to the thread and updates
+ * the tail/root references if needed
+ * Will fire "Thread.update"
+ * @param event - The event to add
+ * @param toStartOfTimeline - whether the event is being added
+ * to the start (and not the end) of the timeline.
+ * @param emit - whether to emit the Update event if the thread was updated or not.
+ */
+ async addEvent(event, toStartOfTimeline, emit = true) {
+ this.setEventMetadata(event);
+ const lastReply = this.lastReply();
+ const isNewestReply = !lastReply || event.localTimestamp >= lastReply.localTimestamp;
+
+ // Add all incoming events to the thread's timeline set when there's no server support
+ if (!Thread.hasServerSideSupport) {
+ // all the relevant membership info to hydrate events with a sender
+ // is held in the main room timeline
+ // We want to fetch the room state from there and pass it down to this thread
+ // timeline set to let it reconcile an event with its relevant RoomMember
+ this.addEventToTimeline(event, toStartOfTimeline);
+ this.client.decryptEventIfNeeded(event, {});
+ } else if (!toStartOfTimeline && this.initialEventsFetched && isNewestReply) {
+ this.addEventToTimeline(event, false);
+ this.fetchEditsWhereNeeded(event);
+ } else if (event.isRelation(_event.RelationType.Annotation) || event.isRelation(_event.RelationType.Replace)) {
+ var _this$timelineSet$rel, _this$timelineSet$rel2;
+ if (!this.initialEventsFetched) {
+ var _this$replayEvents;
+ /**
+ * A thread can be fully discovered via a single sync response
+ * And when that's the case we still ask the server to do an initialisation
+ * as it's the safest to ensure we have everything.
+ * However when we are in that scenario we might loose annotation or edits
+ *
+ * This fix keeps a reference to those events and replay them once the thread
+ * has been initialised properly.
+ */
+ (_this$replayEvents = this.replayEvents) === null || _this$replayEvents === void 0 ? void 0 : _this$replayEvents.push(event);
+ } else {
+ this.addEventToTimeline(event, toStartOfTimeline);
+ }
+ // Apply annotations and replace relations to the relations of the timeline only
+ (_this$timelineSet$rel = this.timelineSet.relations) === null || _this$timelineSet$rel === void 0 ? void 0 : _this$timelineSet$rel.aggregateParentEvent(event);
+ (_this$timelineSet$rel2 = this.timelineSet.relations) === null || _this$timelineSet$rel2 === void 0 ? void 0 : _this$timelineSet$rel2.aggregateChildEvent(event, this.timelineSet);
+ return;
+ }
+
+ // If no thread support exists we want to count all thread relation
+ // added as a reply. We can't rely on the bundled relationships count
+ if ((!Thread.hasServerSideSupport || !this.rootEvent) && event.isRelation(THREAD_RELATION_TYPE.name)) {
+ this.replyCount++;
+ }
+ if (emit) {
+ this.emit(ThreadEvent.NewReply, this, event);
+ this.updateThreadMetadata();
+ }
+ }
+ async processEvent(event) {
+ if (event) {
+ this.setEventMetadata(event);
+ await this.fetchEditsWhereNeeded(event);
+ }
+ this.timeline = this.events;
+ }
+
+ /**
+ * Processes the receipts that were caught during initial sync
+ * When clients become aware of a thread, they try to retrieve those read receipts
+ * and apply them to the current thread
+ * @param receipts - A collection of the receipts cached from initial sync
+ */
+ processReceipts(receipts = []) {
+ for (const {
+ eventId,
+ receiptType,
+ userId,
+ receipt,
+ synthetic
+ } of receipts) {
+ this.addReceiptToStructure(eventId, receiptType, userId, receipt, synthetic);
+ }
+ }
+ getRootEventBundledRelationship(rootEvent = this.rootEvent) {
+ return rootEvent === null || rootEvent === void 0 ? void 0 : rootEvent.getServerAggregatedRelation(THREAD_RELATION_TYPE.name);
+ }
+ async processRootEvent() {
+ const bundledRelationship = this.getRootEventBundledRelationship();
+ if (Thread.hasServerSideSupport && bundledRelationship) {
+ this.replyCount = bundledRelationship.count;
+ this._currentUserParticipated = !!bundledRelationship.current_user_participated;
+ const mapper = this.client.getEventMapper();
+ // re-insert roomId
+ this.lastEvent = mapper(_objectSpread(_objectSpread({}, bundledRelationship.latest_event), {}, {
+ room_id: this.roomId
+ }));
+ this.updatePendingReplyCount();
+ await this.processEvent(this.lastEvent);
+ }
+ }
+ updatePendingReplyCount() {
+ const unfilteredPendingEvents = this.pendingEventOrdering === _client.PendingEventOrdering.Detached ? this.room.getPendingEvents() : this.events;
+ const pendingEvents = unfilteredPendingEvents.filter(ev => {
+ var _this$lastEvent;
+ return ev.threadRootId === this.id && ev.isRelation(THREAD_RELATION_TYPE.name) && ev.status !== null && ev.getId() !== ((_this$lastEvent = this.lastEvent) === null || _this$lastEvent === void 0 ? void 0 : _this$lastEvent.getId());
+ });
+ this.lastPendingEvent = pendingEvents.length ? pendingEvents[pendingEvents.length - 1] : undefined;
+ this.pendingReplyCount = pendingEvents.length;
+ }
+
+ /**
+ * Reset the live timeline of all timelineSets, and start new ones.
+ *
+ * <p>This is used when /sync returns a 'limited' timeline. 'Limited' means that there's a gap between the messages
+ * /sync returned, and the last known message in our timeline. In such a case, our live timeline isn't live anymore
+ * and has to be replaced by a new one. To make sure we can continue paginating our timelines correctly, we have to
+ * set new pagination tokens on the old and the new timeline.
+ *
+ * @param backPaginationToken - token for back-paginating the new timeline
+ * @param forwardPaginationToken - token for forward-paginating the old live timeline,
+ * if absent or null, all timelines are reset, removing old ones (including the previous live
+ * timeline which would otherwise be unable to paginate forwards without this token).
+ * Removing just the old live timeline whilst preserving previous ones is not supported.
+ */
+ async resetLiveTimeline(backPaginationToken, forwardPaginationToken) {
+ const oldLive = this.liveTimeline;
+ this.timelineSet.resetLiveTimeline(backPaginationToken !== null && backPaginationToken !== void 0 ? backPaginationToken : undefined, forwardPaginationToken !== null && forwardPaginationToken !== void 0 ? forwardPaginationToken : undefined);
+ const newLive = this.liveTimeline;
+
+ // FIXME: Remove the following as soon as https://github.com/matrix-org/synapse/issues/14830 is resolved.
+ //
+ // The pagination API for thread timelines currently can't handle the type of pagination tokens returned by sync
+ //
+ // To make this work anyway, we'll have to transform them into one of the types that the API can handle.
+ // One option is passing the tokens to /messages, which can handle sync tokens, and returns the right format.
+ // /messages does not return new tokens on requests with a limit of 0.
+ // This means our timelines might overlap a slight bit, but that's not an issue, as we deduplicate messages
+ // anyway.
+
+ let newBackward;
+ let oldForward;
+ if (backPaginationToken) {
+ const res = await this.client.createMessagesRequest(this.roomId, backPaginationToken, 1, _eventTimeline.Direction.Forward);
+ newBackward = res.end;
+ }
+ if (forwardPaginationToken) {
+ const res = await this.client.createMessagesRequest(this.roomId, forwardPaginationToken, 1, _eventTimeline.Direction.Backward);
+ oldForward = res.start;
+ }
+ // Only replace the token if we don't have paginated away from this position already. This situation doesn't
+ // occur today, but if the above issue is resolved, we'd have to go down this path.
+ if (forwardPaginationToken && oldLive.getPaginationToken(_eventTimeline.Direction.Forward) === forwardPaginationToken) {
+ var _oldForward;
+ oldLive.setPaginationToken((_oldForward = oldForward) !== null && _oldForward !== void 0 ? _oldForward : null, _eventTimeline.Direction.Forward);
+ }
+ if (backPaginationToken && newLive.getPaginationToken(_eventTimeline.Direction.Backward) === backPaginationToken) {
+ var _newBackward;
+ newLive.setPaginationToken((_newBackward = newBackward) !== null && _newBackward !== void 0 ? _newBackward : null, _eventTimeline.Direction.Backward);
+ }
+ }
+ async updateThreadMetadata() {
+ this.updatePendingReplyCount();
+ if (Thread.hasServerSideSupport) {
+ // Ensure we show *something* as soon as possible, we'll update it as soon as we get better data, but we
+ // don't want the thread preview to be empty if we can avoid it
+ if (!this.initialEventsFetched) {
+ await this.processRootEvent();
+ }
+ await this.fetchRootEvent();
+ }
+ await this.processRootEvent();
+ if (!this.initialEventsFetched) {
+ this.initialEventsFetched = true;
+ // fetch initial event to allow proper pagination
+ try {
+ // if the thread has regular events, this will just load the last reply.
+ // if the thread is newly created, this will load the root event.
+ if (this.replyCount === 0 && this.rootEvent) {
+ this.timelineSet.addEventsToTimeline([this.rootEvent], true, this.liveTimeline, null);
+ this.liveTimeline.setPaginationToken(null, _eventTimeline.Direction.Backward);
+ } else {
+ await this.client.paginateEventTimeline(this.liveTimeline, {
+ backwards: true,
+ limit: Math.max(1, this.length)
+ });
+ }
+ for (const event of this.replayEvents) {
+ this.addEvent(event, false);
+ }
+ this.replayEvents = null;
+ // just to make sure that, if we've created a timeline window for this thread before the thread itself
+ // existed (e.g. when creating a new thread), we'll make sure the panel is force refreshed correctly.
+ this.emit(_room.RoomEvent.TimelineReset, this.room, this.timelineSet, true);
+ } catch (e) {
+ _logger.logger.error("Failed to load start of newly created thread: ", e);
+ this.initialEventsFetched = false;
+ }
+ }
+ this.emit(ThreadEvent.Update, this);
+ }
+
+ // XXX: Workaround for https://github.com/matrix-org/matrix-spec-proposals/pull/2676/files#r827240084
+ async fetchEditsWhereNeeded(...events) {
+ return Promise.all(events.filter(e => e.isEncrypted()).map(event => {
+ if (event.isRelation()) return; // skip - relations don't get edits
+ return this.client.relations(this.roomId, event.getId(), _event.RelationType.Replace, event.getType(), {
+ limit: 1
+ }).then(relations => {
+ if (relations.events.length) {
+ event.makeReplaced(relations.events[0]);
+ }
+ }).catch(e => {
+ _logger.logger.error("Failed to load edits for encrypted thread event", e);
+ });
+ }));
+ }
+ setEventMetadata(event) {
+ if (event) {
+ _eventTimeline.EventTimeline.setEventMetadata(event, this.roomState, false);
+ event.setThread(this);
+ }
+ }
+ clearEventMetadata(event) {
+ if (event) {
+ var _event$event, _event$event$unsigned, _event$event$unsigned2;
+ event.setThread(undefined);
+ (_event$event = event.event) === null || _event$event === void 0 ? true : (_event$event$unsigned = _event$event.unsigned) === null || _event$event$unsigned === void 0 ? true : (_event$event$unsigned2 = _event$event$unsigned["m.relations"]) === null || _event$event$unsigned2 === void 0 ? true : delete _event$event$unsigned2[THREAD_RELATION_TYPE.name];
+ }
+ }
+
+ /**
+ * Finds an event by ID in the current thread
+ */
+ findEventById(eventId) {
+ return this.timelineSet.findEventById(eventId);
+ }
+
+ /**
+ * Return last reply to the thread, if known.
+ */
+ lastReply(matches = () => true) {
+ for (let i = this.timeline.length - 1; i >= 0; i--) {
+ const event = this.timeline[i];
+ if (matches(event)) {
+ return event;
+ }
+ }
+ return null;
+ }
+ get roomId() {
+ return this.room.roomId;
+ }
+
+ /**
+ * The number of messages in the thread
+ * Only count rel_type=m.thread as we want to
+ * exclude annotations from that number
+ */
+ get length() {
+ return this.replyCount + this.pendingReplyCount;
+ }
+
+ /**
+ * A getter for the last event of the thread.
+ * This might be a synthesized event, if so, it will not emit any events to listeners.
+ */
+ get replyToEvent() {
+ var _ref, _this$lastPendingEven;
+ return (_ref = (_this$lastPendingEven = this.lastPendingEvent) !== null && _this$lastPendingEven !== void 0 ? _this$lastPendingEven : this.lastEvent) !== null && _ref !== void 0 ? _ref : this.lastReply();
+ }
+ get events() {
+ return this.liveTimeline.getEvents();
+ }
+ has(eventId) {
+ return this.timelineSet.findEventById(eventId) instanceof _event2.MatrixEvent;
+ }
+ get hasCurrentUserParticipated() {
+ return this._currentUserParticipated;
+ }
+ get liveTimeline() {
+ return this.timelineSet.getLiveTimeline();
+ }
+ getUnfilteredTimelineSet() {
+ return this.timelineSet;
+ }
+ addReceipt(event, synthetic) {
+ throw new Error("Unsupported function on the thread model");
+ }
+
+ /**
+ * Get the ID of the event that a given user has read up to within this thread,
+ * or null if we have received no read receipt (at all) from them.
+ * @param userId - The user ID to get read receipt event ID for
+ * @param ignoreSynthesized - If true, return only receipts that have been
+ * sent by the server, not implicit ones generated
+ * by the JS SDK.
+ * @returns ID of the latest event that the given user has read, or null.
+ */
+ getEventReadUpTo(userId, ignoreSynthesized) {
+ const isCurrentUser = userId === this.client.getUserId();
+ const lastReply = this.timeline[this.timeline.length - 1];
+ if (isCurrentUser && lastReply) {
+ // If the last activity in a thread is prior to the first threaded read receipt
+ // sent in the room (suggesting that it was sent before the user started
+ // using a client that supported threaded read receipts), we want to
+ // consider this thread as read.
+ const beforeFirstThreadedReceipt = lastReply.getTs() < this.room.getOldestThreadedReceiptTs();
+ const lastReplyId = lastReply.getId();
+ // Some unsent events do not have an ID, we do not want to consider them read
+ if (beforeFirstThreadedReceipt && lastReplyId) {
+ return lastReplyId;
+ }
+ }
+ const readUpToId = super.getEventReadUpTo(userId, ignoreSynthesized);
+
+ // Check whether the unthreaded read receipt for that user is more recent
+ // than the read receipt inside that thread.
+ if (lastReply) {
+ const unthreadedReceipt = this.room.getLastUnthreadedReceiptFor(userId);
+ if (!unthreadedReceipt) {
+ return readUpToId;
+ }
+ for (let i = ((_this$timeline = this.timeline) === null || _this$timeline === void 0 ? void 0 : _this$timeline.length) - 1; i >= 0; --i) {
+ var _this$timeline, _ev$getId;
+ const ev = this.timeline[i];
+ // If we encounter the `readUpToId` we do not need to look further
+ // there is no "more recent" unthreaded read receipt
+ if (ev.getId() === readUpToId) return readUpToId;
+
+ // Inspecting events from most recent to oldest, we're checking
+ // whether an unthreaded read receipt is more recent that the current event.
+ // We usually prefer relying on the order of the DAG but in this scenario
+ // it is not possible and we have to rely on timestamp
+ if (ev.getTs() < unthreadedReceipt.ts) return (_ev$getId = ev.getId()) !== null && _ev$getId !== void 0 ? _ev$getId : readUpToId;
+ }
+ }
+ return readUpToId;
+ }
+
+ /**
+ * Determine if the given user has read a particular event.
+ *
+ * It is invalid to call this method with an event that is not part of this thread.
+ *
+ * This is not a definitive check as it only checks the events that have been
+ * loaded client-side at the time of execution.
+ * @param userId - The user ID to check the read state of.
+ * @param eventId - The event ID to check if the user read.
+ * @returns True if the user has read the event, false otherwise.
+ */
+ hasUserReadEvent(userId, eventId) {
+ if (userId === this.client.getUserId()) {
+ var _this$lastReply$getTs, _this$lastReply, _this$room$getLastUnt, _this$room$getLastUnt2, _this$lastReply$getTs2, _this$lastReply2;
+ // Consider an event read if it's part of a thread that is before the
+ // first threaded receipt sent in that room. It is likely that it is
+ // part of a thread that was created before MSC3771 was implemented.
+ // Or before the last unthreaded receipt for the logged in user
+ const beforeFirstThreadedReceipt = ((_this$lastReply$getTs = (_this$lastReply = this.lastReply()) === null || _this$lastReply === void 0 ? void 0 : _this$lastReply.getTs()) !== null && _this$lastReply$getTs !== void 0 ? _this$lastReply$getTs : 0) < this.room.getOldestThreadedReceiptTs();
+ const unthreadedReceiptTs = (_this$room$getLastUnt = (_this$room$getLastUnt2 = this.room.getLastUnthreadedReceiptFor(userId)) === null || _this$room$getLastUnt2 === void 0 ? void 0 : _this$room$getLastUnt2.ts) !== null && _this$room$getLastUnt !== void 0 ? _this$room$getLastUnt : 0;
+ const beforeLastUnthreadedReceipt = ((_this$lastReply$getTs2 = this === null || this === void 0 ? void 0 : (_this$lastReply2 = this.lastReply()) === null || _this$lastReply2 === void 0 ? void 0 : _this$lastReply2.getTs()) !== null && _this$lastReply$getTs2 !== void 0 ? _this$lastReply$getTs2 : 0) < unthreadedReceiptTs;
+ if (beforeFirstThreadedReceipt || beforeLastUnthreadedReceipt) {
+ return true;
+ }
+ }
+ return super.hasUserReadEvent(userId, eventId);
+ }
+ setUnread(type, count) {
+ return this.room.setThreadUnreadNotificationCount(this.id, type, count);
+ }
+}
+exports.Thread = Thread;
+(0, _defineProperty2.default)(Thread, "hasServerSideSupport", FeatureSupport.None);
+(0, _defineProperty2.default)(Thread, "hasServerSideListSupport", FeatureSupport.None);
+(0, _defineProperty2.default)(Thread, "hasServerSideFwdPaginationSupport", FeatureSupport.None);
+const FILTER_RELATED_BY_SENDERS = new _NamespacedValue.ServerControlledNamespacedValue("related_by_senders", "io.element.relation_senders");
+exports.FILTER_RELATED_BY_SENDERS = FILTER_RELATED_BY_SENDERS;
+const FILTER_RELATED_BY_REL_TYPES = new _NamespacedValue.ServerControlledNamespacedValue("related_by_rel_types", "io.element.relation_types");
+exports.FILTER_RELATED_BY_REL_TYPES = FILTER_RELATED_BY_REL_TYPES;
+const THREAD_RELATION_TYPE = new _NamespacedValue.ServerControlledNamespacedValue("m.thread", "io.element.thread");
+exports.THREAD_RELATION_TYPE = THREAD_RELATION_TYPE;
+let ThreadFilterType;
+exports.ThreadFilterType = ThreadFilterType;
+(function (ThreadFilterType) {
+ ThreadFilterType[ThreadFilterType["My"] = 0] = "My";
+ ThreadFilterType[ThreadFilterType["All"] = 1] = "All";
+})(ThreadFilterType || (exports.ThreadFilterType = ThreadFilterType = {}));
+function threadFilterTypeToFilter(type) {
+ switch (type) {
+ case ThreadFilterType.My:
+ return "participated";
+ default:
+ return "all";
+ }
+}
+//# sourceMappingURL=thread.js.map \ No newline at end of file