summaryrefslogtreecommitdiff
path: root/includes/external/matrix/node_modules/matrix-js-sdk/src/models/thread.ts
diff options
context:
space:
mode:
Diffstat (limited to 'includes/external/matrix/node_modules/matrix-js-sdk/src/models/thread.ts')
-rw-r--r--includes/external/matrix/node_modules/matrix-js-sdk/src/models/thread.ts669
1 files changed, 0 insertions, 669 deletions
diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/src/models/thread.ts b/includes/external/matrix/node_modules/matrix-js-sdk/src/models/thread.ts
deleted file mode 100644
index 9a4ead3..0000000
--- a/includes/external/matrix/node_modules/matrix-js-sdk/src/models/thread.ts
+++ /dev/null
@@ -1,669 +0,0 @@
-/*
-Copyright 2021 - 2023 The Matrix.org Foundation C.I.C.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-import { Optional } from "matrix-events-sdk";
-
-import { MatrixClient, PendingEventOrdering } from "../client";
-import { TypedReEmitter } from "../ReEmitter";
-import { RelationType } from "../@types/event";
-import { IThreadBundledRelationship, MatrixEvent, MatrixEventEvent } from "./event";
-import { Direction, EventTimeline } from "./event-timeline";
-import { EventTimelineSet, EventTimelineSetHandlerMap } from "./event-timeline-set";
-import { NotificationCountType, Room, RoomEvent } from "./room";
-import { RoomState } from "./room-state";
-import { ServerControlledNamespacedValue } from "../NamespacedValue";
-import { logger } from "../logger";
-import { ReadReceipt } from "./read-receipt";
-import { CachedReceiptStructure, ReceiptType } from "../@types/read_receipts";
-
-export enum ThreadEvent {
- New = "Thread.new",
- Update = "Thread.update",
- NewReply = "Thread.newReply",
- ViewThread = "Thread.viewThread",
- Delete = "Thread.delete",
-}
-
-type EmittedEvents = Exclude<ThreadEvent, ThreadEvent.New> | RoomEvent.Timeline | RoomEvent.TimelineReset;
-
-export type EventHandlerMap = {
- [ThreadEvent.Update]: (thread: Thread) => void;
- [ThreadEvent.NewReply]: (thread: Thread, event: MatrixEvent) => void;
- [ThreadEvent.ViewThread]: () => void;
- [ThreadEvent.Delete]: (thread: Thread) => void;
-} & EventTimelineSetHandlerMap;
-
-interface IThreadOpts {
- room: Room;
- client: MatrixClient;
- pendingEventOrdering?: PendingEventOrdering;
- receipts?: CachedReceiptStructure[];
-}
-
-export enum FeatureSupport {
- None = 0,
- Experimental = 1,
- Stable = 2,
-}
-
-export function determineFeatureSupport(stable: boolean, unstable: boolean): FeatureSupport {
- if (stable) {
- return FeatureSupport.Stable;
- } else if (unstable) {
- return FeatureSupport.Experimental;
- } else {
- return FeatureSupport.None;
- }
-}
-
-export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
- public static hasServerSideSupport = FeatureSupport.None;
- public static hasServerSideListSupport = FeatureSupport.None;
- public static hasServerSideFwdPaginationSupport = FeatureSupport.None;
-
- /**
- * A reference to all the events ID at the bottom of the threads
- */
- public readonly timelineSet: EventTimelineSet;
- public timeline: MatrixEvent[] = [];
-
- private _currentUserParticipated = false;
-
- private reEmitter: TypedReEmitter<EmittedEvents, EventHandlerMap>;
-
- private lastEvent: MatrixEvent | undefined;
- private replyCount = 0;
- private lastPendingEvent: MatrixEvent | undefined;
- private pendingReplyCount = 0;
-
- public readonly room: Room;
- public readonly client: MatrixClient;
- private readonly pendingEventOrdering: PendingEventOrdering;
-
- public initialEventsFetched = !Thread.hasServerSideSupport;
- /**
- * An array of events to add to the timeline once the thread has been initialised
- * with server suppport.
- */
- public replayEvents: MatrixEvent[] | null = [];
-
- public constructor(public readonly id: string, public rootEvent: MatrixEvent | undefined, opts: IThreadOpts) {
- super();
-
- if (!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.pendingEventOrdering ?? PendingEventOrdering.Chronological;
- this.timelineSet = new EventTimelineSet(
- this.room,
- {
- timelineSupport: true,
- pendingEvents: true,
- },
- this.client,
- this,
- );
- this.reEmitter = new TypedReEmitter(this);
-
- this.reEmitter.reEmit(this.timelineSet, [RoomEvent.Timeline, RoomEvent.TimelineReset]);
-
- this.room.on(MatrixEventEvent.BeforeRedaction, this.onBeforeRedaction);
- this.room.on(RoomEvent.Redaction, this.onRedaction);
- this.room.on(RoomEvent.LocalEchoUpdated, this.onLocalEcho);
- this.timelineSet.on(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);
- }
-
- private async fetchRootEvent(): Promise<void> {
- 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.error("Failed to fetch thread root to construct thread with", e);
- }
- await this.processEvent(this.rootEvent);
- }
-
- public static setServerSideSupport(status: FeatureSupport): void {
- 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);
- }
- }
-
- public static setServerSideListSupport(status: FeatureSupport): void {
- Thread.hasServerSideListSupport = status;
- }
-
- public static setServerSideFwdPaginationSupport(status: FeatureSupport): void {
- Thread.hasServerSideFwdPaginationSupport = status;
- }
-
- private onBeforeRedaction = (event: MatrixEvent, redaction: MatrixEvent): void => {
- if (
- 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);
- }
- };
-
- private onRedaction = async (event: MatrixEvent): Promise<void> => {
- 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();
- }
- };
-
- private onTimelineEvent = (
- event: MatrixEvent,
- room: Room | undefined,
- toStartOfTimeline: boolean | undefined,
- ): void => {
- // Add a synthesized receipt when paginating forward in the timeline
- if (!toStartOfTimeline) {
- room!.addLocalEchoReceipt(event.getSender()!, event, ReceiptType.Read);
- }
- this.onEcho(event, toStartOfTimeline ?? false);
- };
-
- private onLocalEcho = (event: MatrixEvent): void => {
- this.onEcho(event, false);
- };
-
- private onEcho = async (event: MatrixEvent, toStartOfTimeline: boolean): Promise<void> => {
- 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);
- };
-
- public get roomState(): RoomState {
- return this.room.getLiveTimeline().getState(EventTimeline.FORWARDS)!;
- }
-
- private addEventToTimeline(event: MatrixEvent, toStartOfTimeline: boolean): void {
- if (!this.findEventById(event.getId()!)) {
- this.timelineSet.addEventToTimeline(event, this.liveTimeline, {
- toStartOfTimeline,
- fromCache: false,
- roomState: this.roomState,
- });
- this.timeline = this.events;
- }
- }
-
- public addEvents(events: MatrixEvent[], toStartOfTimeline: boolean): void {
- 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.
- */
- public async addEvent(event: MatrixEvent, toStartOfTimeline: boolean, emit = true): Promise<void> {
- 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(RelationType.Annotation) || event.isRelation(RelationType.Replace)) {
- if (!this.initialEventsFetched) {
- /**
- * 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?.push(event);
- } else {
- this.addEventToTimeline(event, toStartOfTimeline);
- }
- // Apply annotations and replace relations to the relations of the timeline only
- this.timelineSet.relations?.aggregateParentEvent(event);
- this.timelineSet.relations?.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();
- }
- }
-
- public async processEvent(event: Optional<MatrixEvent>): Promise<void> {
- 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
- */
- private processReceipts(receipts: CachedReceiptStructure[] = []): void {
- for (const { eventId, receiptType, userId, receipt, synthetic } of receipts) {
- this.addReceiptToStructure(eventId, receiptType as ReceiptType, userId, receipt, synthetic);
- }
- }
-
- private getRootEventBundledRelationship(rootEvent = this.rootEvent): IThreadBundledRelationship | undefined {
- return rootEvent?.getServerAggregatedRelation<IThreadBundledRelationship>(THREAD_RELATION_TYPE.name);
- }
-
- private async processRootEvent(): Promise<void> {
- 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({
- ...bundledRelationship.latest_event,
- room_id: this.roomId,
- });
- this.updatePendingReplyCount();
- await this.processEvent(this.lastEvent);
- }
- }
-
- private updatePendingReplyCount(): void {
- const unfilteredPendingEvents =
- this.pendingEventOrdering === PendingEventOrdering.Detached ? this.room.getPendingEvents() : this.events;
- const pendingEvents = unfilteredPendingEvents.filter(
- (ev) =>
- ev.threadRootId === this.id &&
- ev.isRelation(THREAD_RELATION_TYPE.name) &&
- ev.status !== null &&
- ev.getId() !== 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.
- */
- public async resetLiveTimeline(
- backPaginationToken?: string | null,
- forwardPaginationToken?: string | null,
- ): Promise<void> {
- const oldLive = this.liveTimeline;
- this.timelineSet.resetLiveTimeline(backPaginationToken ?? undefined, 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: string | undefined;
- let oldForward: string | undefined;
- if (backPaginationToken) {
- const res = await this.client.createMessagesRequest(this.roomId, backPaginationToken, 1, Direction.Forward);
- newBackward = res.end;
- }
- if (forwardPaginationToken) {
- const res = await this.client.createMessagesRequest(
- this.roomId,
- forwardPaginationToken,
- 1,
- 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(Direction.Forward) === forwardPaginationToken) {
- oldLive.setPaginationToken(oldForward ?? null, Direction.Forward);
- }
- if (backPaginationToken && newLive.getPaginationToken(Direction.Backward) === backPaginationToken) {
- newLive.setPaginationToken(newBackward ?? null, Direction.Backward);
- }
- }
-
- private async updateThreadMetadata(): Promise<void> {
- 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, 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(RoomEvent.TimelineReset, this.room, this.timelineSet, true);
- } catch (e) {
- 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
- private async fetchEditsWhereNeeded(...events: MatrixEvent[]): Promise<unknown> {
- return Promise.all(
- events
- .filter((e) => e.isEncrypted())
- .map((event: MatrixEvent) => {
- if (event.isRelation()) return; // skip - relations don't get edits
- return this.client
- .relations(this.roomId, event.getId()!, RelationType.Replace, event.getType(), {
- limit: 1,
- })
- .then((relations) => {
- if (relations.events.length) {
- event.makeReplaced(relations.events[0]);
- }
- })
- .catch((e) => {
- logger.error("Failed to load edits for encrypted thread event", e);
- });
- }),
- );
- }
-
- public setEventMetadata(event: Optional<MatrixEvent>): void {
- if (event) {
- EventTimeline.setEventMetadata(event, this.roomState, false);
- event.setThread(this);
- }
- }
-
- public clearEventMetadata(event: Optional<MatrixEvent>): void {
- if (event) {
- event.setThread(undefined);
- delete event.event?.unsigned?.["m.relations"]?.[THREAD_RELATION_TYPE.name];
- }
- }
-
- /**
- * Finds an event by ID in the current thread
- */
- public findEventById(eventId: string): MatrixEvent | undefined {
- return this.timelineSet.findEventById(eventId);
- }
-
- /**
- * Return last reply to the thread, if known.
- */
- public lastReply(matches: (ev: MatrixEvent) => boolean = (): boolean => true): MatrixEvent | null {
- for (let i = this.timeline.length - 1; i >= 0; i--) {
- const event = this.timeline[i];
- if (matches(event)) {
- return event;
- }
- }
- return null;
- }
-
- public get roomId(): string {
- 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
- */
- public get length(): number {
- 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.
- */
- public get replyToEvent(): Optional<MatrixEvent> {
- return this.lastPendingEvent ?? this.lastEvent ?? this.lastReply();
- }
-
- public get events(): MatrixEvent[] {
- return this.liveTimeline.getEvents();
- }
-
- public has(eventId: string): boolean {
- return this.timelineSet.findEventById(eventId) instanceof MatrixEvent;
- }
-
- public get hasCurrentUserParticipated(): boolean {
- return this._currentUserParticipated;
- }
-
- public get liveTimeline(): EventTimeline {
- return this.timelineSet.getLiveTimeline();
- }
-
- public getUnfilteredTimelineSet(): EventTimelineSet {
- return this.timelineSet;
- }
-
- public addReceipt(event: MatrixEvent, synthetic: boolean): void {
- 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.
- */
- public getEventReadUpTo(userId: string, ignoreSynthesized?: boolean): string | null {
- 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?.length - 1; i >= 0; --i) {
- 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() ?? 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.
- */
- public hasUserReadEvent(userId: string, eventId: string): boolean {
- if (userId === this.client.getUserId()) {
- // 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() ?? 0) < this.room.getOldestThreadedReceiptTs();
- const unthreadedReceiptTs = this.room.getLastUnthreadedReceiptFor(userId)?.ts ?? 0;
- const beforeLastUnthreadedReceipt = (this?.lastReply()?.getTs() ?? 0) < unthreadedReceiptTs;
- if (beforeFirstThreadedReceipt || beforeLastUnthreadedReceipt) {
- return true;
- }
- }
-
- return super.hasUserReadEvent(userId, eventId);
- }
-
- public setUnread(type: NotificationCountType, count: number): void {
- return this.room.setThreadUnreadNotificationCount(this.id, type, count);
- }
-}
-
-export const FILTER_RELATED_BY_SENDERS = new ServerControlledNamespacedValue(
- "related_by_senders",
- "io.element.relation_senders",
-);
-export const FILTER_RELATED_BY_REL_TYPES = new ServerControlledNamespacedValue(
- "related_by_rel_types",
- "io.element.relation_types",
-);
-export const THREAD_RELATION_TYPE = new ServerControlledNamespacedValue("m.thread", "io.element.thread");
-
-export enum ThreadFilterType {
- "My",
- "All",
-}
-
-export function threadFilterTypeToFilter(type: ThreadFilterType | null): "all" | "participated" {
- switch (type) {
- case ThreadFilterType.My:
- return "participated";
- default:
- return "all";
- }
-}