From e6ef8657cdd7b0e6a52e3a69c46bbc69ecefca52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Z=20=E2=9D=A4=27s=20Life?= Date: Wed, 22 Oct 2025 22:08:04 -0400 Subject: [PATCH] channelTabs: Allow Setting Tab Limit & Limit One Tab Per Server (#467) * [Feat] channelTabs: Allow Setting Tab Limit * Created a settings option to set a limit on tabs, so you don't overwhelm your computer by having millions of tabs open. * [Feat] channelTabs: Allow Limiting Servers to One Tab Each * [Fix] channelTabs: Bypass Server Tab Limit when using Open in New Tab * [QOL] channelTabs: Switch to makeRange for Some Values * Remove comment --------- Co-authored-by: thororen <78185467+thororen1234@users.noreply.github.com> --- src/equicordplugins/channelTabs/index.tsx | 2 +- .../channelTabs/util/constants.tsx | 18 ++++++++- src/equicordplugins/channelTabs/util/tabs.tsx | 38 ++++++++++++++++++- 3 files changed, 54 insertions(+), 4 deletions(-) diff --git a/src/equicordplugins/channelTabs/index.tsx b/src/equicordplugins/channelTabs/index.tsx index 7bcc9e911..3a1759a90 100644 --- a/src/equicordplugins/channelTabs/index.tsx +++ b/src/equicordplugins/channelTabs/index.tsx @@ -28,7 +28,7 @@ const contextMenuPatch: NavContextMenuPatchCallback = (children, props: { channe action={() => createTab({ guildId: channel.guild_id || "@me", // Normalize for DMs/Group Chats channelId: channel.id - }, settings.store.openInNewTabAutoSwitch, messageId)} + }, settings.store.openInNewTabAutoSwitch, messageId, true, true)} // The true values are important for bypassing tab limits /> ); }; diff --git a/src/equicordplugins/channelTabs/util/constants.tsx b/src/equicordplugins/channelTabs/util/constants.tsx index 514d68e66..5573a7103 100644 --- a/src/equicordplugins/channelTabs/util/constants.tsx +++ b/src/equicordplugins/channelTabs/util/constants.tsx @@ -8,7 +8,7 @@ import { definePluginSettings } from "@api/Settings"; import { Heading } from "@components/Heading"; import { Paragraph } from "@components/Paragraph"; import { Logger } from "@utils/Logger"; -import { OptionType } from "@utils/types"; +import { makeRange, OptionType } from "@utils/types"; import { SearchableSelect, useState } from "@webpack/common"; import { JSX } from "react"; @@ -223,7 +223,7 @@ export const settings = definePluginSettings({ hotkeyCount: { type: OptionType.SLIDER, description: "Number of tabs accessible via keyboard shortcuts", - markers: [1, 2, 3, 4, 5, 6, 7, 8, 9], + markers: makeRange(1, 9, 1), default: 3, stickToMarkers: true, }, @@ -357,5 +357,19 @@ export const settings = definePluginSettings({ description: "Open all newly created tabs in compact mode by default", default: false, restartNeeded: false + }, + oneTabPerServer: { + type: OptionType.BOOLEAN, + description: "Limit to one tab per server, so opening a new channel in that server will use the existing tab.", + default: false, + restartNeeded: false + }, + maxOpenTabs: { + type: OptionType.SLIDER, + description: "Maximum number of open tabs (0 = unlimited)", + markers: makeRange(0, 20, 1), + default: 0, + stickToMarkers: true, + restartNeeded: false } }); diff --git a/src/equicordplugins/channelTabs/util/tabs.tsx b/src/equicordplugins/channelTabs/util/tabs.tsx index 4bf403ac5..5b50c4d17 100644 --- a/src/equicordplugins/channelTabs/util/tabs.tsx +++ b/src/equicordplugins/channelTabs/util/tabs.tsx @@ -113,7 +113,43 @@ let clearGhostTabs = () => { logger.warn("Clear ghost tab function not set"); }; -export function createTab(props: BasicChannelTabsProps | ChannelTabsProps, switchToTab?: boolean, messageId?: string, save = true) { +export function createTab(props: BasicChannelTabsProps | ChannelTabsProps, switchToTab?: boolean, messageId?: string, save = true, bypassOneTabPerServer = false) { + // Important for the "One tab per server" feature, has to be before the maxOpenTabs check! + if (!bypassOneTabPerServer && settings.store.oneTabPerServer && props.guildId && props.guildId !== "@me") { + const existingTab = openTabs.find(tab => tab.guildId === props.guildId); + if (existingTab) { + existingTab.channelId = props.channelId; + existingTab.messageId = messageId; + existingTab.compact = "compact" in props ? props.compact : settings.store.openNewTabsInCompactMode; + if (switchToTab) { + moveToTab(existingTab.id); + } else { + update(save); + } + return; + } + } + + const maxTabs = settings.store.maxOpenTabs; + const isLimitEnabled = maxTabs > 0; + const wouldExceedLimit = isLimitEnabled && openTabs.length >= maxTabs; + + if (wouldExceedLimit) { + const currentTab = openTabs.find(t => t.id === currentlyOpenTab); + if (currentTab) { + currentTab.channelId = props.channelId; + currentTab.guildId = props.guildId; + currentTab.messageId = messageId; + currentTab.compact = "compact" in props ? props.compact : settings.store.openNewTabsInCompactMode; + if (switchToTab) { + update(save); + } else { + moveToTab(currentTab.id); + } + } + return; + } + const id = genId(); openTabs.push({ ...props, id, messageId, compact: "compact" in props ? props.compact : settings.store.openNewTabsInCompactMode }); if (switchToTab) moveToTab(id);