From 5e81dbcbdaf7581e031077da73270cc0d3be48cc Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Tue, 28 Apr 2026 19:56:27 -0400 Subject: [PATCH] SidebarChat: kill off until someone can have a look --- src/equicordplugins/sidebarChat/index.tsx | 594 --------------------- src/equicordplugins/sidebarChat/store.ts | 155 ------ src/equicordplugins/sidebarChat/styles.css | 19 - 3 files changed, 768 deletions(-) delete mode 100644 src/equicordplugins/sidebarChat/index.tsx delete mode 100644 src/equicordplugins/sidebarChat/store.ts delete mode 100644 src/equicordplugins/sidebarChat/styles.css diff --git a/src/equicordplugins/sidebarChat/index.tsx b/src/equicordplugins/sidebarChat/index.tsx deleted file mode 100644 index 17a438c9a..000000000 --- a/src/equicordplugins/sidebarChat/index.tsx +++ /dev/null @@ -1,594 +0,0 @@ -/* - * Vencord, a Discord client mod - * Copyright (c) 2024 Vendicated and contributors - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -import "./styles.css"; - -import { NavContextMenuPatchCallback } from "@api/ContextMenu"; -import { HeaderBarButton } from "@api/HeaderBar"; -import ErrorBoundary from "@components/ErrorBoundary"; -import { Devs, EquicordDevs } from "@utils/constants"; -import { classNameFactory } from "@utils/css"; -import { getCurrentChannel } from "@utils/discord"; -import definePlugin from "@utils/types"; -import { Channel, Guild, User } from "@vencord/discord-types"; -import { ChannelType } from "@vencord/discord-types/enums"; -import { - DefaultExtractAndLoadChunksRegex, - extractAndLoadChunksLazy, - findByPropsLazy, - findComponentByCodeLazy, - findCssClassesLazy, - findStoreLazy, -} from "@webpack"; -import { - ChannelActionCreators, - ChannelRouter, - ChannelStore, - FluxDispatcher, - GuildStore, - Menu, - MessageActions, - MessageStore, - PermissionsBits, - PermissionStore, - PopoutActions, - PopoutWindowStore, - RelationshipStore, - SelectedChannelStore, - SelectedGuildStore, - useCallback, - useEffect, - useLayoutEffect, - UserStore, - useState, - useStateFromStores, -} from "@webpack/common"; - -import { getOpenPopoutWindowKeys, getPersistedPopoutChannelIds, getPopoutWindowKey, isPopoutWindowOpen, settings, SidebarStore, syncPersistedPopoutWindows } from "./store"; - -const cl = classNameFactory("vc-sidebar-chat-"); - -const HeaderBar = findComponentByCodeLazy("toolbarClassName:", "}),onDoubleClick:"); -const ForumView = findComponentByCodeLazy("sidebarState"); - -const ArrowsLeftRightIcon = ({ color, ...rest }) => { - return ( - - ); -}; - -const WindowLaunchIcon = findComponentByCodeLazy("1-1h6a1 1 0 1 0 0-2H5Z"); -const XSmallIcon = findComponentByCodeLazy("1.4L12 13.42l5.3 5.3Z"); -const Chat = findComponentByCodeLazy("filterAfterTimestamp:", "chatInputType"); -const Resize = findComponentByCodeLazy("sidebarType:", "RESIZE_HANDLE_WIDTH)"); -const ChannelHeader = findComponentByCodeLazy("`channel-${"); -const PopoutWindow = findComponentByCodeLazy("Missing guestWindow reference"); -const FullChannelView = findComponentByCodeLazy("showFollowButton:"); -const WanderingCubesLoading = findComponentByCodeLazy('="wanderingCubes"'); - -const ChatInputTypes = findByPropsLazy("FORM", "NORMAL"); -const Sidebars = findByPropsLazy("ThreadSidebar", "MessageRequestSidebar"); -const ChatClasses = findCssClassesLazy("threadSidebarOpen", "loader"); - -const ChannelSectionStore = findStoreLazy("ChannelSectionStore"); - -const requireForumView = extractAndLoadChunksLazy( - ["Missing channel in Channel.renderHeaderToolbar"], - new RegExp(DefaultExtractAndLoadChunksRegex.source + '.{1,150}name:"ForumChannel"') -); - -function getChannelTitle(channel: Channel | null | undefined) { - if (!channel) return "Chat"; - - if (channel.isPrivate()) { - const recipientId = channel.getRecipientId?.(); - if (!channel.name && recipientId) { - const user = UserStore.getUser(recipientId); - if (user) { - return RelationshipStore.getNickname(recipientId) || user.globalName || user.username || "DM"; - } - } - - return channel.name || "DM"; - } - - return channel.name || "Chat"; -} - -function canOpenPopout(channel: Channel) { - if (channel.isPrivate()) return true; - return !channel.isCategory() && !channel.isDirectory() && !channel.isVocal(); -} - -function getPopoutMenuLabel(channelId: string) { - return isPopoutWindowOpen(channelId) ? "Close popout chat" : "Popout chat"; -} - -let restorePersistedPopoutsInterval: number | null = null; -let restoringPersistedPopouts = false; - -function clearPersistedPopoutRestoreLoop() { - restoringPersistedPopouts = false; - if (restorePersistedPopoutsInterval !== null) { - window.clearInterval(restorePersistedPopoutsInterval); - restorePersistedPopoutsInterval = null; - } -} - -async function waitForChannel(channelId: string, timeoutMs = 2500) { - const startedAt = Date.now(); - while (Date.now() - startedAt <= timeoutMs) { - const channel = ChannelStore.getChannel(channelId); - if (channel) return channel; - - await new Promise(resolve => setTimeout(resolve, 80)); - } - - return null; -} - -async function waitForDmChannel(userId: string, timeoutMs = 2500) { - const startedAt = Date.now(); - while (Date.now() - startedAt <= timeoutMs) { - const channelId = ChannelStore.getDMFromUserId?.(userId); - if (channelId) return channelId; - - await new Promise(resolve => setTimeout(resolve, 80)); - } - - return null; -} - -async function openPopoutFromUserMenu(userId: string) { - try { - const channelId = await Promise.resolve(ChannelActionCreators.getOrEnsurePrivateChannel(userId)); - if (!channelId) return; - - const channel = await waitForChannel(channelId); - if (channel) openPopout(channel.id); - return; - } catch { - const fallbackChannelId = await waitForDmChannel(userId); - if (!fallbackChannelId) return; - - const channel = await waitForChannel(fallbackChannelId); - if (channel) openPopout(channel.id); - } -} - -function closePopout(channelId: string, syncPersistence = true) { - const windowKey = getPopoutWindowKey(channelId); - PopoutActions.close(windowKey); - if (syncPersistence && !restoringPersistedPopouts) { - syncPersistedPopoutWindows(); - } -} - -function openPopout(channelId: string, syncPersistence = true) { - const channel = ChannelStore.getChannel(channelId); - if (!channel || !canOpenPopout(channel)) return; - - const windowKey = getPopoutWindowKey(channelId); - - if (isPopoutWindowOpen(channelId)) { - closePopout(channelId, syncPersistence); - return; - } - - const title = getChannelTitle(channel); - - PopoutActions.open( - windowKey, - () => , - { - defaultWidth: 854, - defaultHeight: 480, - } - ); - - PopoutActions.setAlwaysOnTop(windowKey, settings.store.popoutAlwaysOnTop); - if (syncPersistence && !restoringPersistedPopouts) { - syncPersistedPopoutWindows(); - } -} - -function restorePersistedPopouts() { - if (!settings.store.persistPopoutWindows) return; - - clearPersistedPopoutRestoreLoop(); - - const pendingRestoreIds = new Set(getPersistedPopoutChannelIds()); - if (pendingRestoreIds.size === 0) return; - - restoringPersistedPopouts = true; - - const attemptRestore = () => { - for (const channelId of pendingRestoreIds) { - const channel = ChannelStore.getChannel(channelId); - if (!channel || !canOpenPopout(channel)) continue; - - pendingRestoreIds.delete(channelId); - openPopout(channelId, false); - } - - if (pendingRestoreIds.size === 0) { - clearPersistedPopoutRestoreLoop(); - syncPersistedPopoutWindows(); - } - }; - - attemptRestore(); - - if (pendingRestoreIds.size > 0) { - restorePersistedPopoutsInterval = window.setInterval(attemptRestore, 250); - } -} - -const createSidebarChatContextMenuItem = (id: string, guildId: string | null) => { - return ( - { - FluxDispatcher.dispatch({ - // @ts-ignore - type: "VC_SIDEBAR_CHAT_NEW", - guildId, - id, - }); - }} - /> - ); -}; - -const createPopoutChatContextMenuItem = (id: string, label: string, action: () => void | Promise) => { - return ( - { - void action(); - }} - /> - ); -}; - -const UserContextPatch: NavContextMenuPatchCallback = (children, args: { user: User; }) => { - const checks = [ - args.user, - args.user.id !== UserStore.getCurrentUser().id, - ]; - if (checks.some(check => !check)) return; - const channelId = ChannelStore.getDMFromUserId?.(args.user.id) ?? null; - const isOpen = channelId ? isPopoutWindowOpen(channelId) : false; - - children.push(createSidebarChatContextMenuItem(args.user.id, null)); - children.push(createPopoutChatContextMenuItem( - args.user.id, - isOpen ? "Close popout chat" : "Popout chat", - () => { - if (channelId && isOpen) { - closePopout(channelId); - return; - } - - return openPopoutFromUserMenu(args.user.id); - } - )); -}; - -const ChannelContextPatch: NavContextMenuPatchCallback = (children, args: { channel: Channel; }) => { - const checks = [ - args.channel, - args.channel.type !== ChannelType.GUILD_CATEGORY, - PermissionStore.can(PermissionsBits.VIEW_CHANNEL, args.channel) || args.channel.type === ChannelType.GROUP_DM, - ]; - if (checks.some(check => !check)) return; - children.push(createSidebarChatContextMenuItem(args.channel.id, args.channel.guild_id)); - children.push(createPopoutChatContextMenuItem( - args.channel.id, - getPopoutMenuLabel(args.channel.id), - () => openPopout(args.channel.id) - )); -}; - -export default definePlugin({ - name: "SidebarChat", - authors: [Devs.Joona, EquicordDevs.justjxke], - description: "Open a channel or DM as a sidebar or a popout.", - tags: ["Appearance", "Chat", "Servers"], - dependencies: ["HeaderBarAPI"], - patches: [ - { - find: 'case"pendingFriends":', - group: true, - replacement: [ - { - match: /ChannelRenderer"\),/, - replace: "$&vc_SidebarChat=$self.renderSidebar()," - }, - { - match: /return(\(0,\i\.jsxs?\)\(\i,{}\))}/, - replace: "return [$1,vc_SidebarChat]}" - }, - { - match: /(?<=guild_products.{0,1600})(case \i\.\i.{0,50}return)(.+?\}\));(?=.+?params\.messageId)/g, - replace: "$1[$2,vc_SidebarChat];", - predicate: () => settings.store.patchCommunity, - }, - { - match: /(case \i\.\i\.GAME_SERVERS:.{0,50}\.CHANNEL.{0,25}return)(.*?);/, - replace: "$1[$2,vc_SidebarChat];", - predicate: () => settings.store.patchCommunity, - }, - ], - }, - ], - - headerBarButton: { - icon: WindowLaunchIcon, - render: () => ( - <> - - - - ) - }, - - stop() { - clearPersistedPopoutRestoreLoop(); - syncPersistedPopoutWindows(); - for (const windowKey of getOpenPopoutWindowKeys()) { - PopoutActions.close(windowKey); - } - }, - - async start() { - restorePersistedPopouts(); - }, - - settings, - contextMenus: { - "user-context": UserContextPatch, - "channel-context": ChannelContextPatch, - "thread-context": ChannelContextPatch, - "gdm-context": ChannelContextPatch, - }, - - toolboxActions: { - "Open Previous Chat"() { - FluxDispatcher.dispatch({ - // @ts-ignore - type: "VC_SIDEBAR_CHAT_PREVIOUS", - }); - } - }, - - renderSidebar() { - const { guild, channel /* width*/ } = useStateFromStores( - [SidebarStore, GuildStore, ChannelStore], () => { - const { channelId, guildId } = SidebarStore.getState(); - return { - guild: GuildStore.getGuild(guildId), - channel: ChannelStore.getChannel(channelId) - }; - }, [] - ); - - const [channelSidebar, guildSidebar] = useStateFromStores( - [ChannelSectionStore, SelectedChannelStore, ChannelStore], () => { - const currentChannelId = SelectedChannelStore.getChannelId(); - const currentGuildId = SelectedGuildStore.getGuildId(); - return [ - ChannelSectionStore.getSidebarState(currentChannelId), - ChannelSectionStore.getGuildSidebarState(currentGuildId), - ]; - }, [] - ); - - useEffect(() => { - if (!channel?.id || MessageStore.getLastMessage(channel.id)) return; - MessageActions.fetchMessages({ - channelId: channel.id, - limit: 50, - }); - }, [channel?.id]); - - const [width, setWidth] = useState(window.innerWidth); - - useLayoutEffect(() => { - const handleResize = () => setWidth(window.innerWidth); - - window.addEventListener("resize", handleResize); - return () => window.removeEventListener("resize", handleResize); - }, []); - - const [View, setViewComponent] = useState(null); - - useEffect(() => { - if (!channel) return; - - if (channel.isForumLikeChannel()) { - requireForumView().then(() => { - setViewComponent( - - ); - }); - - setViewComponent( -
- -
- ); - } else { - setViewComponent( - - ); - } - }, [channel]); - - if (!channel || channelSidebar || guildSidebar) return null; - - return ( - - -
- {View} - - - ); - }, -}); - -const Header = ({ guild, channel }: { guild: Guild; channel: Channel; }) => { - const recipientId = channel.isPrivate() ? channel.getRecipientId() as string : null; - - const name = useStateFromStores([UserStore, RelationshipStore], () => getChannelTitle(channel), [channel.id, channel.name]); - - const parentChannel = useStateFromStores( - [ChannelStore], () => ChannelStore.getChannel(channel?.parent_id), - [channel?.parent_id] - ); - - const closeSidebar = () => FluxDispatcher.dispatch({ type: "VC_SIDEBAR_CHAT_CLOSE", }); - - const isPopoutOpen = useStateFromStores( - [PopoutWindowStore], () => isPopoutWindowOpen(channel.id), - [channel.id] - ); - - const openPopoutClick = useCallback(() => openPopout(channel.id), [channel.id]); - - const switchChannels = useCallback(() => { - const mainChannel = getCurrentChannel()!; - FluxDispatcher.dispatch({ - // @ts-ignore - type: "VC_SIDEBAR_CHAT_NEW", - guildId: mainChannel.guild_id, - id: mainChannel.id, - }); - ChannelRouter.transitionToChannel(channel.id); - }, [channel.id]); - - return ( - - - - - - } - > - - - ); -}; - -const RenderPopout = ErrorBoundary.wrap(({ channel, name, windowKey }: { channel: Channel; name: string; windowKey: string; }) => { - // Copy from an unexported function of the one they use in the experiment - // right click a channel and search withTitleBar:!0,windowKey - useEffect(() => { - if (!channel?.id || MessageStore.getLastMessage(channel.id)) return; - - MessageActions.fetchMessages({ - channelId: channel.id, - limit: 50, - }); - }, [channel?.id]); - - return ( - -
- -
-
- ); -}); - -function PopoutHeaderButton() { - const channelState = useStateFromStores( - [SelectedChannelStore, ChannelStore, PopoutWindowStore], - () => { - const channelId = SelectedChannelStore.getChannelId(); - const channel = channelId ? ChannelStore.getChannel(channelId) : null; - - return { - channel, - isOpen: channel ? isPopoutWindowOpen(channel.id) : false, - label: getChannelTitle(channel) - }; - }, - [] - ); - - if (!channelState.channel || !canOpenPopout(channelState.channel)) return null; - - const { channel, isOpen, label } = channelState; - - return ( - openPopout(channel.id)} - /> - ); -} - -const WrappedPopoutHeaderButton = ErrorBoundary.wrap(PopoutHeaderButton, { noop: true }); - -function PopoutPersistenceSync() { - const openWindowKeySignature = useStateFromStores( - [PopoutWindowStore], - () => getOpenPopoutWindowKeys().join("|"), - [] - ); - - useEffect(() => { - if (restoringPersistedPopouts) return; - syncPersistedPopoutWindows(); - }, [openWindowKeySignature]); - - return null; -} diff --git a/src/equicordplugins/sidebarChat/store.ts b/src/equicordplugins/sidebarChat/store.ts deleted file mode 100644 index afb059f2b..000000000 --- a/src/equicordplugins/sidebarChat/store.ts +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Vencord, a Discord client mod - * Copyright (c) 2024 Vendicated and contributors - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -import { definePluginSettings } from "@api/Settings"; -import { proxyLazy } from "@utils/lazy"; -import { OptionType } from "@utils/types"; -import { Flux as TFlux } from "@vencord/discord-types"; -import { ChannelActionCreators, Flux as FluxWP, FluxDispatcher, PopoutActions, PopoutWindowStore } from "@webpack/common"; - -interface IFlux extends TFlux { - PersistedStore: TFlux["Store"]; -} - -export const settings = definePluginSettings({ - persistSidebar: { - type: OptionType.BOOLEAN, - description: "Keep the sidebar chat open across Discord restarts", - default: true, - }, - persistPopoutWindows: { - type: OptionType.BOOLEAN, - description: "Restore open popout chats after Discord restarts.", - default: true, - onChange: value => { - if (!value) { - settings.store.persistedPopoutWindowIds = []; - return; - } - - syncPersistedPopoutWindows(); - } - }, - persistedPopoutWindowIds: { - type: OptionType.CUSTOM, - description: "Persisted popout chat channel IDs.", - default: [] as string[], - hidden: true - }, - popoutAlwaysOnTop: { - type: OptionType.BOOLEAN, - description: "Keep popout chat windows above all others.", - default: true, - onChange: value => { - setAlwaysOnTopForOpenPopouts(value); - } - }, - patchCommunity: { - type: OptionType.BOOLEAN, - description: "Patch things like the Channel Browser or Members tab that community servers have.", - default: true, - restartNeeded: true, - } -}); - -export const SidebarStore = proxyLazy(() => { - const current = { - guildId: "", - channelId: "", - width: 0 - }; - - let previous = { ...current }; - - class SidebarStore extends (FluxWP as IFlux).PersistedStore { - static persistKey = "SidebarStore"; - - // @ts-ignore - initialize(previousState: { guildId?: string; channelId?: string; width?: number; } | undefined) { - if (!settings.store.persistSidebar || !previousState) return; - const { guildId, channelId, width } = previousState; - current.guildId = guildId || ""; - current.channelId = channelId || ""; - current.width = width || 0; - } - - getState() { - return current; - } - } - - const store = new SidebarStore(FluxDispatcher, { - // @ts-ignore - async VC_SIDEBAR_CHAT_NEW({ guildId: newGId, id }: { guildId: string | null; id: string; }) { - previous = { ...current }; - - current.guildId = newGId || ""; - - if (current.guildId) { - current.channelId = id; - store.emitChange(); - return; - } - - current.channelId = await ChannelActionCreators.getOrEnsurePrivateChannel(id); - store.emitChange(); - }, - - VC_SIDEBAR_CHAT_PREVIOUS() { - if (previous.channelId) { - current.guildId = previous.guildId; - current.channelId = previous.channelId; - } - store.emitChange(); - }, - - VC_SIDEBAR_CHAT_CLOSE() { - previous = { ...current }; - current.guildId = ""; - current.channelId = ""; - store.emitChange(); - }, - }); - - return store; -}); - -const WINDOW_PREFIX = "DISCORD_VC_SC-"; - -export function getPopoutWindowKey(channelId: string) { - return `${WINDOW_PREFIX}${channelId}`; -} - -export function getOpenPopoutWindowKeys() { - return PopoutWindowStore.getWindowKeys().filter(key => key.startsWith(WINDOW_PREFIX)); -} - -export function isPopoutWindowOpen(channelId: string) { - return PopoutWindowStore.getWindowOpen(getPopoutWindowKey(channelId)); -} - -export function getPersistedPopoutChannelIds() { - return settings.store.persistedPopoutWindowIds ?? []; -} - -export function getOpenPopoutChannelIds() { - return getOpenPopoutWindowKeys().map(key => key.slice(WINDOW_PREFIX.length)); -} - -export function syncPersistedPopoutWindows() { - if (!settings.store.persistPopoutWindows) { - settings.store.persistedPopoutWindowIds = []; - return; - } - - settings.store.persistedPopoutWindowIds = getOpenPopoutChannelIds(); -} - -export function setAlwaysOnTopForOpenPopouts(value: boolean) { - for (const windowKey of getOpenPopoutWindowKeys()) { - PopoutActions.setAlwaysOnTop(windowKey, value); - } -} diff --git a/src/equicordplugins/sidebarChat/styles.css b/src/equicordplugins/sidebarChat/styles.css deleted file mode 100644 index 3c5ab7d40..000000000 --- a/src/equicordplugins/sidebarChat/styles.css +++ /dev/null @@ -1,19 +0,0 @@ -[class*="content"] > [class*="page"] > * { - min-width: 0; -} - -.vc-sidebar-chat-popout { - align-items: stretch; - background-color: var(--background-primary); -} - -.vc-sidebar-chat-window { - display: flex; - flex: 1; - min-height: 0; - min-width: 0; - overflow: hidden; - width: 100%; - background-color: var(--background-primary); - color: var(--text-default); -}