From 2f6a56d65e7e1a3e812d884396f2702f5afdd545 Mon Sep 17 00:00:00 2001 From: Puyodead1 Date: Wed, 17 Jul 2024 20:48:27 -0400 Subject: [PATCH] mark channel as read on open --- .../ChannelList/ChannelListItem.tsx | 4 ++-- src/components/messaging/Chat.tsx | 6 ++++- src/stores/GatewayConnectionStore.ts | 19 ++++++++++++++++ src/stores/objects/Channel.ts | 22 ++++++++++++++++++- src/stores/objects/ReadState.ts | 7 ++++-- 5 files changed, 52 insertions(+), 6 deletions(-) diff --git a/src/components/ChannelList/ChannelListItem.tsx b/src/components/ChannelList/ChannelListItem.tsx index 6f5fc81..5a18378 100644 --- a/src/components/ChannelList/ChannelListItem.tsx +++ b/src/components/ChannelList/ChannelListItem.tsx @@ -4,13 +4,13 @@ import { useNavigate } from "react-router-dom"; import styled from "styled-components"; import { ContextMenuContext } from "../../contexts/ContextMenuContext"; import { modalController } from "../../controllers/modals"; +import { useAppStore } from "../../hooks/useAppStore"; import Channel from "../../stores/objects/Channel"; import { Permissions } from "../../utils/Permissions"; import Icon from "../Icon"; import SidebarPill from "../SidebarPill"; import Floating from "../floating/Floating"; import FloatingTrigger from "../floating/FloatingTrigger"; -import { useAppStore } from "../../hooks/useAppStore"; const ListItem = styled.div<{ isCategory?: boolean }>` padding: ${(props) => (props.isCategory ? "16px 8px 0 0" : "1px 8px 0 0")}; @@ -90,7 +90,7 @@ function ChannelListItem({ channel, isCategory, active }: Props) { alignItems: "center", }} > - + {channel.channelIcon && !isCategory && ( { + channel.markAsRead(); + }, [channel, guild]); + return ( diff --git a/src/stores/GatewayConnectionStore.ts b/src/stores/GatewayConnectionStore.ts index 1d2d3e9..2fef56e 100644 --- a/src/stores/GatewayConnectionStore.ts +++ b/src/stores/GatewayConnectionStore.ts @@ -143,6 +143,8 @@ export default class GatewayConnectionStore { this.dispatchHandlers.set(GatewayDispatchEvents.ChannelCreate, this.onChannelCreate); this.dispatchHandlers.set(GatewayDispatchEvents.ChannelUpdate, this.onChannelUpdate); this.dispatchHandlers.set(GatewayDispatchEvents.ChannelDelete, this.onChannelDelete); + // @ts-expect-error missing event in typings + this.dispatchHandlers.set("MESSAGE_ACK", this.onMessageAck); this.dispatchHandlers.set(GatewayDispatchEvents.MessageCreate, this.onMessageCreate); this.dispatchHandlers.set(GatewayDispatchEvents.MessageUpdate, this.onMessageUpdate); @@ -633,6 +635,23 @@ export default class GatewayConnectionStore { guild.removeChannel(data.id); }; + private onMessageAck = (data: { channel_id: string; message_id: string; version: number }) => { + // get readstate for channel + const readstate = this.app.readStateStore.get(data.channel_id); + if (!readstate) { + this.logger.warn(`[MessageAck] Readstate not found for channel ${data.channel_id}`); + return; + } + + runInAction(() => { + readstate.lastMessageId = data.message_id; + }); + + this.logger.debug( + `[MessageAck] Updated last message id for channel readstate ${data.channel_id} to ${data.message_id}`, + ); + }; + private onMessageCreate = (data: GatewayMessageCreateDispatchData) => { const guild = this.app.guilds.get(data.guild_id!); if (!guild) { diff --git a/src/stores/objects/Channel.ts b/src/stores/objects/Channel.ts index 5a767a8..35f42c8 100644 --- a/src/stores/objects/Channel.ts +++ b/src/stores/objects/Channel.ts @@ -311,7 +311,8 @@ export default class Channel { return listId; } - hasUnread() { + @computed + get unread() { const readState = this.app.readStateStore.get(this.id); if (!readState) { // this.logger.warn(`Failed to find readstate for channel ${this.id}`); // this just causes unnecessary spam @@ -320,4 +321,23 @@ export default class Channel { return readState.lastMessageId !== this.lastMessageId; } + + markAsRead() { + const readState = this.app.readStateStore.get(this.id); + if (!readState) { + this.logger.warn(`Failed to find readstate for channel ${this.id}`); // this just causes unnecessary spam + return; + } + + this.app.rest + .post(Routes.channelMessage(this.id, readState.lastMessageId) + "/ack", { + mention_count: readState.mentionCount, + }) + .then((r) => { + this.logger.debug(`Acked ${this.lastMessageId} for channel ${this.id}`, r); + }) + .catch((e) => { + this.logger.error(`Failed to ack ${this.lastMessageId} for channel ${this.id}`, e); + }); + } } diff --git a/src/stores/objects/ReadState.ts b/src/stores/objects/ReadState.ts index a4b255d..9b6c530 100644 --- a/src/stores/objects/ReadState.ts +++ b/src/stores/objects/ReadState.ts @@ -1,8 +1,10 @@ -import type { APIReadState } from "@spacebarchat/spacebar-api-types/v9"; +import { type APIReadState } from "@spacebarchat/spacebar-api-types/v9"; import { action, observable } from "mobx"; +import Logger from "../../utils/Logger"; import AppStore from "../AppStore"; export default class ReadState { + private readonly logger: Logger; private readonly app: AppStore; id: string; @@ -11,9 +13,10 @@ export default class ReadState { @observable mentionCount: number | null; constructor(app: AppStore, data: APIReadState) { + this.logger = new Logger("ReadState"); this.app = app; - this.id = data.id; + this.id = data.id; // channel id this.lastMessageId = data.last_message_id; this.lastPinTimestamp = data.last_pin_timestamp; this.mentionCount = data.mention_count;