From 6096013c65f95add5eaa85142c332f119a017eb8 Mon Sep 17 00:00:00 2001 From: Wes Date: Wed, 11 Jun 2025 14:47:52 -0600 Subject: [PATCH] feat(ChannelTabs): Improves tab management/behaviour (#285) * basically instead of changing the current tab: -if there is a tab for that channel: swap to the channel tab -if there isnt a tab for that channel it creates a new one -there is a 3 second (you can change it in settings) grace period for when you are quickly switching tabs so your tabs dont get filled also added background (and border to the active tab) to the tabs so it works better with custom themes that have image backgrounds * made the improvements settings * better regex (ty thororen) * Update style.css --------- Co-authored-by: thororen <78185467+thororen1234@users.noreply.github.com> --- .../components/ChannelTabsContainer.tsx | 17 ++++++-- src/equicordplugins/channelTabs/index.tsx | 21 +++++++++- src/equicordplugins/channelTabs/style.css | 4 ++ .../channelTabs/util/constants.tsx | 27 +++++++++++- src/equicordplugins/channelTabs/util/tabs.tsx | 41 +++++++++++++++++-- 5 files changed, 100 insertions(+), 10 deletions(-) diff --git a/src/equicordplugins/channelTabs/components/ChannelTabsContainer.tsx b/src/equicordplugins/channelTabs/components/ChannelTabsContainer.tsx index 20ad46e5..8ee35fc3 100644 --- a/src/equicordplugins/channelTabs/components/ChannelTabsContainer.tsx +++ b/src/equicordplugins/channelTabs/components/ChannelTabsContainer.tsx @@ -52,16 +52,25 @@ export default function ChannelsTabsContainer(props: BasicChannelTabsProps) { }, []); useEffect(() => { - (Vencord.Plugins.plugins.ChannelTabs as any).containerHeight = ref.current?.clientHeight; + if (ref.current) { + try { + (Vencord.Plugins.plugins.ChannelTabs as any).containerHeight = ref.current.clientHeight; + } catch { } + } }, [userId, showBookmarkBar]); useEffect(() => { _update(); }, [widerTabsAndBookmarks]); + useEffect(() => { + if (userId) { + handleChannelSwitch(props); + saveTabs(userId); + } + }, [userId, props.channelId, props.guildId]); + if (!userId) return null; - handleChannelSwitch(props); - saveTabs(userId); return (
); -} +} \ No newline at end of file diff --git a/src/equicordplugins/channelTabs/index.tsx b/src/equicordplugins/channelTabs/index.tsx index 6c5aaf10..f0cc1b4f 100644 --- a/src/equicordplugins/channelTabs/index.tsx +++ b/src/equicordplugins/channelTabs/index.tsx @@ -15,7 +15,7 @@ import { Channel, Message } from "discord-types/general"; import { JSX } from "react"; import ChannelsTabsContainer from "./components/ChannelTabsContainer"; -import { BasicChannelTabsProps, createTab, settings } from "./util"; +import { BasicChannelTabsProps, createTab, handleChannelSwitch, settings } from "./util"; import * as ChannelTabsUtils from "./util"; const contextMenuPatch: NavContextMenuPatchCallback = (children, props: { channel: Channel, messageId?: string; }) => @@ -52,6 +52,14 @@ export default definePlugin({ replace: "$1$4$self.render,{currentChannel:$2,children:$3})$5" } }, + // intercept channel navigation to switch/create tabs + { + find: "transitionToGuild", + replacement: { + match: /transitionToGuild\(([^,]+),([^)]+)\)/, + replace: "$&;$self.handleNavigation($1,$2)" + } + }, // ctrl click to open in new tab in inbox unread { find: ".messageContainer,onKeyDown", @@ -113,5 +121,14 @@ export default definePlugin({ createTab(tab, false, message.id); }, + handleNavigation(guildId: string, channelId: string) { + if (!guildId || !channelId) return; + + // wait for discord to update channel data + requestAnimationFrame(() => { + handleChannelSwitch({ guildId, channelId }); + }); + }, + util: ChannelTabsUtils, -}); +}); \ No newline at end of file diff --git a/src/equicordplugins/channelTabs/style.css b/src/equicordplugins/channelTabs/style.css index 2d4f187a..b99f4c2e 100644 --- a/src/equicordplugins/channelTabs/style.css +++ b/src/equicordplugins/channelTabs/style.css @@ -145,6 +145,9 @@ width: 12rem; min-width: 0; margin-right: 0.25rem; + background-color: var(--background-primary); + border: 1px solid var(--background-modifier-accent); + transition: background-color 0.15s ease; } .vc-channeltabs-tab.vc-channeltabs-wider:not(.vc-channeltabs-tab-compact) { @@ -159,6 +162,7 @@ .vc-channeltabs-tab-selected { background: var(--bg-overlay-selected); background-color: var(--background-modifier-selected); + border-color: var(--brand-500); } /* channel type container */ diff --git a/src/equicordplugins/channelTabs/util/constants.tsx b/src/equicordplugins/channelTabs/util/constants.tsx index 04e2b078..7d72f16c 100644 --- a/src/equicordplugins/channelTabs/util/constants.tsx +++ b/src/equicordplugins/channelTabs/util/constants.tsx @@ -71,7 +71,32 @@ export const settings = definePluginSettings({ type: OptionType.BOOLEAN, default: false, restartNeeded: false + }, + switchToExistingTab: { + type: OptionType.BOOLEAN, + description: "Switch to tab if it already exists for the channel you're navigating to", + default: false, + restartNeeded: false + }, + createNewTabIfNotExists: { + type: OptionType.BOOLEAN, + description: "Create a new tab if one doesn't exist for the channel you're navigating to", + default: false, + restartNeeded: false + }, + enableRapidNavigation: { + type: OptionType.BOOLEAN, + description: "Enable rapid navigation behavior - quickly navigating between channels will replace the current tab instead of creating new ones", + default: false, + restartNeeded: false + }, + rapidNavigationThreshold: { + type: OptionType.SLIDER, + description: "Time window (in milliseconds) for rapid navigation. Within this time, new channels replace the current tab instead of creating new ones.", + markers: [500, 1000, 1500, 2000, 3000, 5000, 10000], + default: 3000, + stickToMarkers: false, } }); -export const CircleQuestionIcon = findComponentByCodeLazy("10.58l-3.3-3.3a1"); +export const CircleQuestionIcon = findComponentByCodeLazy("10.58l-3.3-3.3a1"); \ No newline at end of file diff --git a/src/equicordplugins/channelTabs/util/tabs.tsx b/src/equicordplugins/channelTabs/util/tabs.tsx index 3deb257f..74773495 100644 --- a/src/equicordplugins/channelTabs/util/tabs.tsx +++ b/src/equicordplugins/channelTabs/util/tabs.tsx @@ -128,11 +128,46 @@ export function closeTabsToTheLeft(id: number) { else update(); } +let lastNavigationTime = 0; + export function handleChannelSwitch(ch: BasicChannelTabsProps) { const tab = openTabs.find(c => c.id === currentlyOpenTab); - if (tab === undefined) return logger.error("Couldn't find the currently open channel " + currentlyOpenTab, openTabs); + const existingTab = openTabs.find(tab => tab.channelId === ch.channelId && tab.guildId === ch.guildId); - if (tab.channelId !== ch.channelId) openTabs[openTabs.indexOf(tab)] = { id: tab.id, compact: tab.compact, ...ch }; + // First check: switch to existing tab if setting enabled and tab exists + if (settings.store.switchToExistingTab && existingTab) { + moveToTab(existingTab.id); + return; + } + + // Second check: create new tab if setting enabled + if (settings.store.createNewTabIfNotExists) { + // Apply rapid navigation logic when creating new tabs + const now = Date.now(); + const isRapidNavigation = now - lastNavigationTime < settings.store.rapidNavigationThreshold; + lastNavigationTime = now; + + if (isRapidNavigation && settings.store.enableRapidNavigation) { + // Replace current tab content instead of creating new one + const currentTab = openTabs.find(t => t.id === currentlyOpenTab); + if (currentTab) { + currentTab.channelId = ch.channelId; + currentTab.guildId = ch.guildId; + update(); + return; + } + } + + // Create new tab (normal behavior) + createTab(ch, true); + return; + } + + // Default behavior: replace current tab content + if (tab && tab.channelId !== ch.channelId) { + openTabs[openTabs.indexOf(tab)] = { id: tab.id, compact: tab.compact, ...ch }; + update(); + } } export function hasClosedTabs() { @@ -271,4 +306,4 @@ export function useGhostTabs() { setCount(0); }; return new Array(count).fill(
); -} +} \ No newline at end of file