diff --git a/package.json b/package.json index 6f14588c..279af451 100644 --- a/package.json +++ b/package.json @@ -143,7 +143,7 @@ "react-virtuoso": "^1.10.4", "redux": "^4.1.0", "revolt-api": "0.5.3-alpha.10", - "revolt.js": "5.1.0-alpha.15", + "revolt.js": "5.2.0-patch.0", "rimraf": "^3.0.2", "sass": "^1.35.1", "shade-blend-color": "^1.0.0", diff --git a/src/components/navigation/left/HomeSidebar.tsx b/src/components/navigation/left/HomeSidebar.tsx index 837638ad..d378fd75 100644 --- a/src/components/navigation/left/HomeSidebar.tsx +++ b/src/components/navigation/left/HomeSidebar.tsx @@ -5,7 +5,7 @@ import { Notepad, } from "@styled-icons/boxicons-solid"; import { observer } from "mobx-react-lite"; -import { Link, Redirect, useLocation, useParams } from "react-router-dom"; +import { Link, useLocation, useParams } from "react-router-dom"; import { RelationshipStatus } from "revolt-api/types/Users"; import { Text } from "preact-i18n"; @@ -16,48 +16,37 @@ import PaintCounter from "../../../lib/PaintCounter"; import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice"; import { useApplicationState } from "../../../mobx/State"; -import { dispatch } from "../../../redux"; -import { connectState } from "../../../redux/connector"; -import { Unreads } from "../../../redux/reducers/unreads"; import { useIntermediate } from "../../../context/intermediate/Intermediate"; import { AppContext } from "../../../context/revoltjs/RevoltClient"; import Category from "../../ui/Category"; import placeholderSVG from "../items/placeholder.svg"; -import { mapChannelWithUnread, useUnreads } from "./common"; import { GenericSidebarBase, GenericSidebarList } from "../SidebarBase"; import ButtonItem, { ChannelButton } from "../items/ButtonItem"; import ConnectionStatus from "../items/ConnectionStatus"; -type Props = { - unreads: Unreads; -}; - -const HomeSidebar = observer((props: Props) => { +export default observer(() => { const { pathname } = useLocation(); const client = useContext(AppContext); - const layout = useApplicationState().layout; - const { channel } = useParams<{ channel: string }>(); + const state = useApplicationState(); + const { channel: currentChannel } = useParams<{ channel: string }>(); const { openScreen } = useIntermediate(); - const channels = [...client.channels.values()] - .filter( - (x) => - x.channel_type === "DirectMessage" || - x.channel_type === "Group", - ) - .map((x) => mapChannelWithUnread(x, props.unreads)); + const channels = [...client.channels.values()].filter( + (x) => x.channel_type === "DirectMessage" || x.channel_type === "Group", + ); - const obj = client.channels.get(channel); - if (channel && !obj) return ; - if (obj) useUnreads({ ...props, channel: obj }); + const obj = client.channels.get(currentChannel); + // ! FIXME: move this globally // Track what page the user was last on (in home page). - useEffect(() => layout.setLastHomePath(pathname), [pathname]); + useEffect(() => state.layout.setLastHomePath(pathname), [pathname]); - channels.sort((b, a) => a.timestamp.localeCompare(b.timestamp)); + channels.sort((b, a) => + a.last_message_id_or_past.localeCompare(b.last_message_id_or_past), + ); return ( @@ -127,31 +116,37 @@ const HomeSidebar = observer((props: Props) => { {channels.length === 0 && ( )} - {channels.map((x) => { + {channels.map((channel) => { let user; - if (x.channel.channel_type === "DirectMessage") { - if (!x.channel.active) return null; - user = x.channel.recipient; + if (channel.channel_type === "DirectMessage") { + if (!channel.active) return null; + user = channel.recipient; - if (!user) { - console.warn( - `Skipped DM ${x.channel._id} because user was missing.`, - ); - return null; - } + if (!user) return null; } + const isUnread = channel.isUnread(state.notifications); + const mentionCount = channel.getMentions( + state.notifications, + ).length; + return ( + key={channel._id} + active={channel._id === currentChannel} + to={`/channel/${channel._id}`}> 0 + ? "mention" + : isUnread + ? "unread" + : undefined + } + alertCount={mentionCount} + active={channel._id === currentChannel} /> ); @@ -161,13 +156,3 @@ const HomeSidebar = observer((props: Props) => { ); }); - -export default connectState( - HomeSidebar, - (state) => { - return { - unreads: state.unreads, - }; - }, - true, -); diff --git a/src/components/navigation/left/ServerListSidebar.tsx b/src/components/navigation/left/ServerListSidebar.tsx index 3a27f84b..ddb0948f 100644 --- a/src/components/navigation/left/ServerListSidebar.tsx +++ b/src/components/navigation/left/ServerListSidebar.tsx @@ -13,8 +13,6 @@ import PaintCounter from "../../../lib/PaintCounter"; import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice"; import { useApplicationState } from "../../../mobx/State"; -import { connectState } from "../../../redux/connector"; -import { Unreads } from "../../../redux/reducers/unreads"; import { useIntermediate } from "../../../context/intermediate/Intermediate"; import { useClient } from "../../../context/revoltjs/RevoltClient"; @@ -25,7 +23,6 @@ import UserHover from "../../common/user/UserHover"; import UserIcon from "../../common/user/UserIcon"; import IconButton from "../../ui/IconButton"; import LineDivider from "../../ui/LineDivider"; -import { mapChannelWithUnread } from "./common"; import { Children } from "../../../types/Preact"; @@ -193,47 +190,14 @@ function Swoosh() { ); } -interface Props { - unreads: Unreads; -} - -export const ServerListSidebar = observer(({ unreads }: Props) => { +export default observer(() => { const client = useClient(); const state = useApplicationState(); const { server: server_id } = useParams<{ server?: string }>(); const server = server_id ? client.servers.get(server_id) : undefined; - const activeServers = [...client.servers.values()]; - const channels = [...client.channels.values()].map((x) => - mapChannelWithUnread(x, unreads), - ); - - const unreadChannels = channels - .filter((x) => x.unread) - .filter((x) => !state.notifications.isMuted(x.channel)) - .map((x) => x.channel?._id); - - const servers = activeServers.map((server) => { - let alertCount = 0; - for (const id of server.channel_ids) { - const channel = channels.find((x) => x.channel?._id === id); - if (channel?.alertCount) { - alertCount += channel.alertCount; - } - } - - return { - server, - unread: (typeof server.channel_ids.find((x) => - unreadChannels.includes(x), - ) !== "undefined" - ? alertCount > 0 - ? "mention" - : "unread" - : undefined) as "mention" | "unread" | undefined, - alertCount, - }; - }); + const servers = [...client.servers.values()]; + const channels = [...client.channels.values()]; const history = useHistory(); const path = useLocation().pathname; @@ -241,16 +205,16 @@ export const ServerListSidebar = observer(({ unreads }: Props) => { let homeUnread: "mention" | "unread" | undefined; let alertCount = 0; - for (const x of channels) { - if (x.channel?.channel_type === "Group" && x.unread) { + for (const channel of channels) { + if (channel?.channel_type === "Group" && channel.unread) { homeUnread = "unread"; - alertCount += x.alertCount ?? 0; + alertCount += channel.mentions.length; } if ( - x.channel?.channel_type === "DirectMessage" && - x.channel.active && - x.unread + channel.channel_type === "DirectMessage" && + channel.active && + channel.unread ) { alertCount++; } @@ -294,32 +258,40 @@ export const ServerListSidebar = observer(({ unreads }: Props) => { - {servers.map((entry) => { - const active = entry.server._id === server?._id; + {servers.map((server) => { + const active = server._id === server_id; + + const isUnread = server.isUnread(state.notifications); + const mentionCount = server.getMentions( + state.notifications, + ).length; return ( + to={state.layout.getServerPath(server._id)}> - + unread={ + mentionCount > 0 + ? "mention" + : isUnread + ? "unread" + : undefined + } + count={mentionCount}> + @@ -353,9 +325,3 @@ export const ServerListSidebar = observer(({ unreads }: Props) => { ); }); - -export default connectState(ServerListSidebar, (state) => { - return { - unreads: state.unreads, - }; -}); diff --git a/src/components/navigation/left/ServerSidebar.tsx b/src/components/navigation/left/ServerSidebar.tsx index 33e3f4b7..7ed142ef 100644 --- a/src/components/navigation/left/ServerSidebar.tsx +++ b/src/components/navigation/left/ServerSidebar.tsx @@ -11,25 +11,17 @@ import { internalEmit } from "../../../lib/eventEmitter"; import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice"; import { useApplicationState } from "../../../mobx/State"; -import { dispatch } from "../../../redux"; import { connectState } from "../../../redux/connector"; -import { Notifications } from "../../../redux/reducers/notifications"; -import { Unreads } from "../../../redux/reducers/unreads"; import { useClient } from "../../../context/revoltjs/RevoltClient"; import CollapsibleSection from "../../common/CollapsibleSection"; import ServerHeader from "../../common/ServerHeader"; import Category from "../../ui/Category"; -import { mapChannelWithUnread, useUnreads } from "./common"; import { ChannelButton } from "../items/ButtonItem"; import ConnectionStatus from "../items/ConnectionStatus"; -interface Props { - unreads: Unreads; -} - const ServerBase = styled.div` height: 100%; width: 240px; @@ -56,7 +48,7 @@ const ServerList = styled.div` } `; -const ServerSidebar = observer((props: Props) => { +export default observer(() => { const client = useClient(); const state = useApplicationState(); const { server: server_id, channel: channel_id } = @@ -76,9 +68,7 @@ const ServerSidebar = observer((props: Props) => { ); if (channel_id && !channel) return ; - // Handle unreads; FIXME: should definitely not be here - if (channel) useUnreads({ ...props, channel }); - + // ! FIXME: move this globally // Track which channel the user was last on. useEffect(() => { if (!channel_id) return; @@ -95,6 +85,8 @@ const ServerSidebar = observer((props: Props) => { if (!entry) return; const active = channel?._id === entry._id; + const isUnread = entry.isUnread(state.notifications); + const mentionCount = entry.getMentions(state.notifications); return ( { 0 + ? "mention" + : isUnread + ? "unread" + : undefined + } compact muted={state.notifications.isMuted(entry)} /> @@ -161,10 +158,3 @@ const ServerSidebar = observer((props: Props) => { ); }); - -export default connectState(ServerSidebar, (state) => { - return { - unreads: state.unreads, - notifications: state.notifications, - }; -}); diff --git a/src/components/navigation/left/common.ts b/src/components/navigation/left/common.ts index 774934f4..ee5e5b66 100644 --- a/src/components/navigation/left/common.ts +++ b/src/components/navigation/left/common.ts @@ -1,7 +1,7 @@ import { reaction } from "mobx"; import { Channel } from "revolt.js/dist/maps/Channels"; -import { useLayoutEffect, useRef } from "preact/hooks"; +import { useLayoutEffect } from "preact/hooks"; import { dispatch } from "../../../redux"; import { Unreads } from "../../../redux/reducers/unreads"; diff --git a/src/mobx/stores/NotificationOptions.ts b/src/mobx/stores/NotificationOptions.ts index 9bb3b23e..51c45511 100644 --- a/src/mobx/stores/NotificationOptions.ts +++ b/src/mobx/stores/NotificationOptions.ts @@ -195,12 +195,17 @@ export default class NotificationOptions implements Store, Persistent { * @returns Whether this object is muted */ isMuted(target?: Channel | Server) { + var value: NotificationState | undefined; if (target instanceof Channel) { - return this.computeForChannel(target) === "muted"; + value = this.computeForChannel(target); } else if (target instanceof Server) { - return this.computeForServer(target._id) === "muted"; - } else { - return false; + value = this.computeForServer(target._id); } + + if (value === "muted") { + return true; + } + + return false; } } diff --git a/src/mobx/stores/ServerConfig.ts b/src/mobx/stores/ServerConfig.ts index 6e4b5d9d..6b44bec1 100644 --- a/src/mobx/stores/ServerConfig.ts +++ b/src/mobx/stores/ServerConfig.ts @@ -41,6 +41,7 @@ export default class ServerConfig */ createClient() { const client = new Client({ + unreads: true, autoReconnect: false, apiURL: import.meta.env.VITE_API_URL, debug: import.meta.env.DEV, diff --git a/src/pages/channels/Channel.tsx b/src/pages/channels/Channel.tsx index 9f4767f0..cf162c6d 100644 --- a/src/pages/channels/Channel.tsx +++ b/src/pages/channels/Channel.tsx @@ -1,5 +1,6 @@ import { Hash } from "@styled-icons/boxicons-regular"; import { Ghost } from "@styled-icons/boxicons-solid"; +import { reaction } from "mobx"; import { observer } from "mobx-react-lite"; import { useParams } from "react-router-dom"; import { Channel as ChannelI } from "revolt.js/dist/maps/Channels"; @@ -10,7 +11,6 @@ import { useEffect, useState } from "preact/hooks"; import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice"; -import { useApplicationState } from "../../mobx/State"; import { dispatch, getState } from "../../redux"; import { useClient } from "../../context/revoltjs/RevoltClient"; @@ -93,6 +93,23 @@ const TextChannel = observer(({ channel }: { channel: ChannelI }) => { getState().sectionToggle[CHANNELS_SIDEBAR_KEY] ?? true, ); + // Mark channel as read. + useEffect(() => { + const checkUnread = () => + channel.unread && + channel.client.unreads!.markRead( + channel._id, + channel.last_message_id!, + true, + ); + + checkUnread(); + return reaction( + () => channel.last_message_id, + () => checkUnread(), + ); + }, [channel]); + return (