Skip to content

Instantly share code, notes, and snippets.

@samfundev
Last active October 30, 2023 06:57
Show Gist options
  • Save samfundev/9f8ef7ad688630ea69bf5052c1415d54 to your computer and use it in GitHub Desktop.
Save samfundev/9f8ef7ad688630ea69bf5052c1415d54 to your computer and use it in GitHub Desktop.
/**
* @name ChannelTabs
* @displayName ChannelTabs
* @source https://gist.github.com/samfundev/9f8ef7ad688630ea69bf5052c1415d54
* @updateUrl https://gist.githubusercontent.com/samfundev/9f8ef7ad688630ea69bf5052c1415d54/raw
* @donate https://paypal.me/samfun123
* @authorId 76052829285916672
*/
/*@cc_on
@if (@_jscript)
// Offer to self-install for clueless users that try to run this directly.
var shell = WScript.CreateObject("WScript.Shell");
var fs = new ActiveXObject("Scripting.FileSystemObject");
var pathPlugins = shell.ExpandEnvironmentStrings("%APPDATA%\BetterDiscord\plugins");
var pathSelf = WScript.ScriptFullName;
// Put the user at ease by addressing them in the first person
shell.Popup("It looks like you've mistakenly tried to run me directly. \n(Don't do that!)", 0, "I'm a plugin for BetterDiscord", 0x30);
if (fs.GetParentFolderName(pathSelf) === fs.GetAbsolutePathName(pathPlugins)) {
shell.Popup("I'm in the correct folder already.", 0, "I'm already installed", 0x40);
} else if (!fs.FolderExists(pathPlugins)) {
shell.Popup("I can't find the BetterDiscord plugins folder.\nAre you sure it's even installed?", 0, "Can't install myself", 0x10);
} else if (shell.Popup("Should I copy myself to BetterDiscord's plugins folder for you?", 0, "Do you need some help?", 0x34) === 6) {
fs.CopyFile(pathSelf, fs.BuildPath(pathPlugins, fs.GetFileName(pathSelf)), true);
// Show the user where to put plugins in the future
shell.Exec("explorer " + pathPlugins);
shell.Popup("I'm installed!", 0, "Successfully installed", 0x40);
}
WScript.Quit();
@else@*/
module.exports = (() => {
const config = {
info: {
name: "ChannelTabs",
authors: [
{
name: "l0c4lh057",
discord_id: "226677096091484160",
github_username: "l0c4lh057",
twitter_username: "l0c4lh057"
},
{
name: "CarJem Generations",
discord_id: "519397452944769025",
github_username: "CarJem",
twitter_username: "carter5467_99"
},
{
name: "samfundev",
discord_id: "76052829285916672",
github_username: "samfundev",
}
],
version: "2.6.7",
description: "Allows you to have multiple tabs and bookmark channels",
github: "https://gist.github.com/samfundev/9f8ef7ad688630ea69bf5052c1415d54",
github_raw: "https://gist.githubusercontent.com/samfundev/9f8ef7ad688630ea69bf5052c1415d54/raw"
},
changelog: [
{
"title": "Fixed",
"type": "fixed",
"items": [
"Fixed appearance context causing a crash",
"Fixed some context menu options displaying incorrectly",
]
}
]
};
return !global.ZeresPluginLibrary ? class {
constructor(){ this._config = config; }
getName(){ return config.info.name; }
getAuthor(){ return config.info.authors.map(a => a.name).join(", "); }
getDescription(){ return config.info.description + " **Install [ZeresPluginLibrary](https://betterdiscord.app/Download?id=9) and restart discord to use this plugin!**"; }
getVersion(){ return config.info.version; }
load(){
BdApi.showConfirmationModal("Library plugin is needed",
[`The library plugin needed for ${config.info.name} is missing. Please click Download Now to install it.`], {
confirmText: "Download",
cancelText: "Cancel",
onConfirm: () => {
require("request").get("https://rauenzi.github.io/BDPluginLibrary/release/0PluginLibrary.plugin.js", async (error, response, body) => {
if (error) return require("electron").shell.openExternal("https://betterdiscord.app/Download?id=9");
await new Promise(r => require("fs").writeFile(require("path").join(BdApi.Plugins.folder, "0PluginLibrary.plugin.js"), body, r));
});
}
}
);
}
start(){}
stop(){}
} : (([Plugin, Api]) => {
const plugin = (Plugin, Api) => {
//#region Module/Variable Definitions
const { WebpackModules, PluginUtilities, DiscordModules, ReactComponents, ReactTools, Settings, Modals } = Api;
const { React, NavigationUtils, SelectedChannelStore, SelectedGuildStore, ChannelStore, GuildStore, UserStore, UserTypingStore, Permissions } = DiscordModules;
const { ContextMenu, Patcher, Webpack } = new BdApi("ChannelTabs");
const DiscordConstants = {
ChannelTypes: Webpack.getModule(Webpack.Filters.byProps("GUILD_TEXT"), { searchExports: true })
};
const Textbox = WebpackModules.find(m => m.defaultProps && m.defaultProps.type == "text", { searchExports: true });
const UnreadStateStore = WebpackModules.find(m => m.isEstimated);
const Flux = WebpackModules.getByProps("connectStores");
const MutedStore = WebpackModules.getByProps("isMuted", "isChannelMuted");
const PermissionUtils = WebpackModules.getByProps("can", "canManageUser");
const UserStatusStore = DiscordModules.UserStatusStore;
const Spinner = WebpackModules.getModule(m => m.toString().includes("spinningCircle"));
const Tooltip = WebpackModules.getModule((m) => m?.toString().includes("shouldShowTooltip") && m?.Positions);
const Slider = WebpackModules.getModule(m => m?.toString().includes(`"[UIKit]Slider.handleMouseDown(): assert failed: domNode nodeType !== Element"`), { searchExports: true });
const NavShortcuts = WebpackModules.getByProps("NAVIGATE_BACK", "NAVIGATE_FORWARD");
const Close = WebpackModules.find(m => m.toString().includes("M18.4 4L12 10.4L5.6 4L4 5.6L10.4 12L4 18.4L5.6 20L12 13.6L18.4 20L20 18.4L13.6 12L20 5.6L18.4 4Z"));
const PlusAlt = WebpackModules.find(m => m.toString().includes("15 10 10 10 10 15 8 15 8 10 3 10 3 8 8 8 8 3 10 3 10 8 15 8"));
const LeftCaret = WebpackModules.find(m => m.toString().includes("18.35 4.35 16 2 6 12 16 22 18.35 19.65 10.717 12"));
const RightCaret = WebpackModules.find(m => m.toString().includes("8.47 2 6.12 4.35 13.753 12 6.12 19.65 8.47 22 18.47 12"));
const DefaultUserIconGrey = "https://cdn.discordapp.com/embed/avatars/0.png";
const DefaultUserIconGreen = "https://cdn.discordapp.com/embed/avatars/1.png";
const DefaultUserIconBlue = "https://cdn.discordapp.com/embed/avatars/2.png";
const DefaultUserIconRed = "https://cdn.discordapp.com/embed/avatars/3.png";
const DefaultUserIconYellow = "https://cdn.discordapp.com/embed/avatars/4.png";
const SettingsMenuIcon = `<svg class="channelTabs-settingsIcon" aria-hidden="false" viewBox="0 0 80 80">
<rect fill="var(--interactive-normal)" x="20" y="15" width="50" height="10"></rect>
<rect fill="var(--interactive-normal)" x="20" y="35" width="50" height="10"></rect>
<rect fill="var(--interactive-normal)" x="20" y="55" width="50" height="10"></rect>
</svg>`;
var switching = false;
var patches = [];
var currentTabDragIndex = -1;
var currentTabDragDestinationIndex = -1;
var currentFavDragIndex = -1;
var currentFavDragDestinationIndex = -1;
var currentGroupDragIndex = -1;
var currentGroupDragDestinationIndex = -1;
var currentGroupOpened = -1;
//#endregion
//#region Context Menu Constructors
function CreateGuildContextMenuChildren(instance, props, channel)
{
return ContextMenu.buildMenuChildren([{
type: "group",
items: [
{
type: "submenu",
label: "ChannelTabs",
items: instance.mergeItems([
{
label: "Open channel in new tab",
action: ()=>TopBarRef.current && TopBarRef.current.saveChannel(props.guild.id, channel.id, "#" + channel.name, props.guild.getIconURL() || "")
},
{
label: "Save channel as bookmark",
action: ()=>TopBarRef.current && TopBarRef.current.addToFavs("#" + channel.name, props.guild.getIconURL() || "", `/channels/${props.guild.id}/${channel.id}`, channel.id)
}],
[{
label: "Save guild as bookmark",
action: ()=>TopBarRef.current && TopBarRef.current.addToFavs(props.guild.name, props.guild.getIconURL() || "", `/channels/${props.guild.id}`, undefined, props.guild.id)
}]
)
}
]
}]);
};
function CreateTextChannelContextMenuChildren(instance, props)
{
return ContextMenu.buildMenuChildren([{
type: "group",
items: [
{
type: "submenu",
label: "ChannelTabs",
items: instance.mergeItems([
{
label: "Open in new tab",
action: ()=>TopBarRef.current && TopBarRef.current.saveChannel(props.guild.id, props.channel.id, "#" + props.channel.name, props.guild.getIconURL() || "")
}],
[{
label: "Save channel as bookmark",
action: ()=>TopBarRef.current && TopBarRef.current.addToFavs("#" + props.channel.name, props.guild.getIconURL() || "", `/channels/${props.guild.id}/${props.channel.id}`, props.channel.id)
}]
)
}
]
}]);
};
function CreateDMContextMenuChildren(instance, props)
{
return ContextMenu.buildMenuChildren([{
type: "group",
items: [
{
type: "submenu",
label: "ChannelTabs",
items: instance.mergeItems(
[{
label: "Open in new tab",
action: ()=>TopBarRef.current && TopBarRef.current.saveChannel(props.channel.guild_id, props.channel.id, "@" + (props.channel.name || props.user.username), props.user.getAvatarURL(null, 40, false))
}],
[{
label: "Save DM as bookmark",
action: ()=>TopBarRef.current && TopBarRef.current.addToFavs("@" + (props.channel.name || props.user.username), props.user.getAvatarURL(null, 40, false), `/channels/@me/${props.channel.id}`, props.channel.id)
}]
)
}
]
}])
};
function CreateGroupContextMenuChildren(instance, props)
{
return ContextMenu.buildMenuChildren([{
type: "group",
items: [
{
type: "submenu",
label: "ChannelTabs",
items: instance.mergeItems(
[{
label: "Open in new tab",
action: ()=>TopBarRef.current && TopBarRef.current.saveChannel(props.channel.guild_id, props.channel.id, "@" + (props.channel.name || props.channel.rawRecipients.map(u=>u.username).join(", ")), ""/*TODO*/)
}],
[{
label: "Save bookmark",
action: ()=>TopBarRef.current && TopBarRef.current.addToFavs("@" + (props.channel.name || props.channel.rawRecipients.map(u=>u.username).join(", ")), ""/*TODO*/, `/channels/@me/${props.channel.id}`, props.channel.id)
}]
)
}
]
}])
};
function CreateTabContextMenu(props,e)
{
ContextMenu.open(
e,
ContextMenu.buildMenu([
{
type: "group",
items: mergeLists(
{
values: [
{
label: "Duplicate",
action: props.openInNewTab
},
{
label: "Add to favourites",
action: ()=>props.addToFavs(props.name, props.iconUrl, props.url, props.channelId)
},
{
label: "Minimize tab",
type: "toggle",
checked: () => props.minimized,
action: ()=> props.minimizeTab(props.tabIndex)
}
]
},
{
include: props.tabCount > 1,
values: [
{
type : "separator"
},
{
label: "Move left",
action: props.moveLeft
},
{
label: "Move right",
action: props.moveRight
}
]
},
{
include: props.tabCount > 1,
values: [
{
type : "separator"
},
{
type: "submenu",
label: "Close...",
id: "closeMenu",
color: "danger",
action: ()=>props.closeTab(props.tabIndex, "single"),
items: mergeLists(
{
values: [
{
label: "Close tab",
action: ()=>props.closeTab(props.tabIndex, "single"),
color: "danger"
},
{
label: "Close all other tabs",
action: ()=>props.closeTab(props.tabIndex, "other"),
color: "danger"
}
]
},
{
include: props.tabIndex != props.tabCount - 1,
values: [
{
label: "Close all tabs to right",
action: ()=>props.closeTab(props.tabIndex, "right"),
color: "danger"
}
]
},
{
include: props.tabIndex != 0,
values: [
{
label: "Close all tabs to left",
action: ()=>props.closeTab(props.tabIndex, "left"),
color: "danger"
}
]
}
)
}
]
}
)
}
]),
{
position: "right",
align: "top"
}
);
};
function CreateFavContextMenu(props,e)
{
ContextMenu.open(
e,
ContextMenu.buildMenu([
{
type: "group",
items: mergeLists(
{
values: [
{
label: "Open in new tab",
action: props.openInNewTab
},
{
label: "Rename",
action: props.rename
},
{
label: "Minimize favourite",
type: "toggle",
checked: () => props.minimized,
action: ()=> props.minimizeFav(props.favIndex)
},
{
type : "separator"
}
]
},
{
include: props.favCount > 1,
values: [
{
label: "Move left",
action: props.moveLeft
},
{
label: "Move right",
action: props.moveRight
},
{
type : "separator"
}
]
},
{
values: [
{
label: "Move To...",
id: "groupMoveTo",
type: "submenu",
items: mergeLists(
{
values: [
{
label: "Favorites Bar",
id: "entryNone",
color: "danger",
action: () => props.moveToFavGroup(props.favIndex, -1)
},
{
type: "separator"
}
]
},
{
values: FavMoveToGroupList({favIndex: props.favIndex, ...props})
}
)
},
{
type : "separator"
}
]
},
{
values: [
{
label: "Delete",
action: props.delete,
color: "danger"
}
]
}
)
}
]),
{
position: "right",
align: "top"
}
);
};
function CreateFavGroupContextMenu(props,e)
{
ContextMenu.open(
e,
ContextMenu.buildMenu([
{
type: "group",
items: mergeLists(
{
values: [
{
label: "Open all",
action: ()=>props.openFavGroupInNewTab(props.favGroup.groupId)
},
{
type : "separator"
}
]
},
{
include: props.groupCount > 1,
values: [
{
label: "Move left",
action: ()=>props.moveFavGroup(props.groupIndex, (props.groupIndex + props.groupCount - 1) % props.groupCount)
},
{
label: "Move right",
action: ()=>props.moveFavGroup(props.groupIndex, (props.groupIndex + 1) % props.groupCount)
},
{
type : "separator"
}
]
},
{
values: [
{
label: "Rename",
id: "renameGroup",
action: ()=>props.renameFavGroup(props.favGroup.name, props.favGroup.groupId)
},
{
type : "separator"
},
{
label: "Delete",
id: "deleteGroup",
action: ()=>props.removeFavGroup(props.favGroup.groupId),
color: "danger"
}
]
}
)
}
]),
{
position: "right",
align: "top"
}
);
};
function CreateFavBarContextMenu(props,e)
{
ContextMenu.open(
e,
ContextMenu.buildMenu([
{
type: "group",
items: [
{
label: "Add current tab as favourite",
action: ()=>props.addToFavs(getCurrentName(), getCurrentIconUrl(), location.pathname, SelectedChannelStore.getChannelId())
},
{
label: "Create a new group...",
action: props.addFavGroup
},
{
type: "separator"
},
{
label: "Hide Favorites",
action: props.hideFavBar,
color: "danger"
}
]
}
]),
{
position: "right",
align: "top"
}
);
};
function CreateSettingsContextMenu(instance, e)
{
ContextMenu.open(
e,
ContextMenu.buildMenu([
{
type: "group",
items: mergeLists(
{
values: [
{
label: config.info.name,
subtext: "Version " + config.info.version,
action: () => {
Modals.showChangelogModal(config.info.name, config.info.version, config.changelog);
}
},
{
type: "separator"
},
{
id: "shortcutLabel",
disabled: true,
label: "Shortcuts:"
},
{
id: "shortcutLabelKeys",
disabled: true,
render: () => {
return React.createElement("div", {style: { "color": "var(--text-muted)", "padding": "8px", "font-size": "12px", "white-space": "pre-wrap" }},
`Ctrl + W - Close Current Tab\n` +
`Ctrl + PgUp - Navigate to Left Tab\n` +
`Ctrl + PgDn - Navigate to Right Tab\n`);
}
},
{
type: "separator"
},
{
label: "Settings:",
id: "settingHeader",
disabled: true
},
{
type: "separator"
},
{
type: "submenu",
label: "Startup",
items: [
{
label: "Reopen Last Channel on Startup",
type: "toggle",
id: "reopenLastChannel",
checked: () => TopBarRef.current.state.reopenLastChannel,
action: () => {
instance.setState({
reopenLastChannel: !instance.state.reopenLastChannel
}, ()=>{
instance.props.plugin.settings.reopenLastChannel = !instance.props.plugin.settings.reopenLastChannel;
instance.props.plugin.saveSettings();
});
}
}
]
},
{
type: "submenu",
label: "Appearance",
items: [
{
label: "Use Compact Appearance",
type: "toggle",
id: "useCompactLook",
checked: () => TopBarRef.current.state.compactStyle,
action: () => {
instance.setState({
compactStyle: !instance.state.compactStyle
}, ()=>{
instance.props.plugin.settings.compactStyle = !instance.props.plugin.settings.compactStyle;
instance.props.plugin.removeStyle();
instance.props.plugin.applyStyle();
instance.props.plugin.saveSettings();
});
}
},
{
label: "Privacy Mode",
type: "toggle",
id: "privacyMode",
checked: () => TopBarRef.current.state.privacyMode,
action: () => {
instance.setState({
privacyMode: !instance.state.privacyMode
}, ()=>{
instance.props.plugin.settings.privacyMode = !instance.props.plugin.settings.privacyMode;
instance.props.plugin.removeStyle();
instance.props.plugin.applyStyle();
instance.props.plugin.saveSettings();
});
}
},
{
label: "Radial Status Indicators",
type: "toggle",
id: "radialStatusMode",
checked: () => TopBarRef.current.state.radialStatusMode,
action: () => {
instance.setState({
radialStatusMode: !instance.state.radialStatusMode
}, ()=>{
instance.props.plugin.settings.radialStatusMode = !instance.props.plugin.settings.radialStatusMode;
instance.props.plugin.removeStyle();
instance.props.plugin.applyStyle();
instance.props.plugin.saveSettings();
});
}
},
{
type: "separator"
},
{
label: "Minimum Tab Width",
style: { "pointer-events": "none" }
},
{
id: "tabWidthMin",
render: () => {
return React.createElement("div",
{
className: "channelTabs-sliderContainer"
},
React.createElement(Slider,
{
"aria-label": "Minimum Tab Width",
className: "channelTabs-slider",
mini: true,
orientation: "horizontal",
disabled: false,
initialValue: instance.props.plugin.settings.tabWidthMin,
minValue: 50,
maxValue: 220,
onValueRender: value => Math.floor(value / 10) * 10 + 'px',
onValueChange: value => {
value = Math.floor(value / 10) * 10,
instance.props.plugin.settings.tabWidthMin = value,
instance.props.plugin.saveSettings(),
instance.props.plugin.applyStyle("channelTabs-style-constants")
}
}
)
)
}
},
{
type: "separator"
},
{
label: "Show Tab Bar",
type: "toggle",
id: "showTabBar",
color: "danger",
checked: () => TopBarRef.current.state.showTabBar,
action: () => {
instance.setState({
showTabBar: !instance.state.showTabBar
}, ()=>{
instance.props.plugin.settings.showTabBar = !instance.props.plugin.settings.showTabBar;
instance.props.plugin.saveSettings();
});
}
},
{
label: "Show Fav Bar",
type: "toggle",
id: "showFavBar",
color: "danger",
checked: () => TopBarRef.current.state.showFavBar,
action: () => {
instance.setState({
showFavBar: !instance.state.showFavBar
}, ()=>{
instance.props.plugin.settings.showFavBar = !instance.props.plugin.settings.showFavBar;
instance.props.plugin.saveSettings();
});
}
},
{
label: "Show Quick Settings",
type: "toggle",
id: "showQuickSettings",
color: "danger",
checked: () => TopBarRef.current.state.showQuickSettings,
action: () => {
instance.setState({
showQuickSettings: !instance.state.showQuickSettings
}, ()=>{
instance.props.plugin.settings.showQuickSettings = !instance.props.plugin.settings.showQuickSettings;
instance.props.plugin.saveSettings();
});
}
},
{
label: "Show Navigation Buttons",
type: "toggle",
id: "showNavButtons",
checked: () => TopBarRef.current.state.showNavButtons,
action: () => {
instance.setState({
showNavButtons: !instance.state.showNavButtons
}, ()=>{
instance.props.plugin.settings.showNavButtons = !instance.props.plugin.settings.showNavButtons;
instance.props.plugin.removeStyle();
instance.props.plugin.applyStyle();
instance.props.plugin.saveSettings();
});
}
}
]
},
{
type: "submenu",
label: "Behavior",
items: [
{
label: "Always Focus New Tabs",
type: "toggle",
id: "alwaysFocusNewTabs",
checked: () => TopBarRef.current.state.alwaysFocusNewTabs,
action: () => {
instance.setState({
alwaysFocusNewTabs: !instance.state.alwaysFocusNewTabs
}, ()=>{
instance.props.plugin.settings.alwaysFocusNewTabs = !instance.props.plugin.settings.alwaysFocusNewTabs;
instance.props.plugin.saveSettings();
});
}
},
{
label: "Primary Forward/Back Navigation",
type: "toggle",
id: "useStandardNav",
checked: () => TopBarRef.current.state.useStandardNav,
action: () => {
instance.setState({
useStandardNav: !instance.state.useStandardNav
}, ()=>{
instance.props.plugin.settings.useStandardNav = !instance.props.plugin.settings.useStandardNav;
instance.props.plugin.saveSettings();
});
}
}
]
},
{
type: "submenu",
label: "Badge Visibility",
items: [
{
type: "separator",
id: "header1_1"
},
{
label: "Favs:",
id: "header1_2",
disabled: true
},
{
type: "separator",
id: "header1_3"
},
{
label: "Show Mentions",
type: "toggle",
id: "favs_Mentions",
checked: () => TopBarRef.current.state.showFavMentionBadges,
action: () => {
instance.setState({
showFavMentionBadges: !instance.state.showFavMentionBadges
}, ()=>{
instance.props.plugin.settings.showFavMentionBadges = !instance.props.plugin.settings.showFavMentionBadges;
instance.props.plugin.saveSettings();
});
}
},
{
label: "Show Unreads",
type: "toggle",
id: "favs_Unreads",
checked: () => TopBarRef.current.state.showFavUnreadBadges,
action: () => {
instance.setState({
showFavUnreadBadges: !instance.state.showFavUnreadBadges
}, ()=>{
instance.props.plugin.settings.showFavUnreadBadges = !instance.props.plugin.settings.showFavUnreadBadges;
instance.props.plugin.saveSettings();
});
}
},
{
label: "Show Typing",
type: "toggle",
id: "favs_Typing",
checked: () => TopBarRef.current.state.showFavTypingBadge,
action: () => {
instance.setState({
showFavTypingBadge: !instance.state.showFavTypingBadge
}, ()=>{
instance.props.plugin.settings.showFavTypingBadge = !instance.props.plugin.settings.showFavTypingBadge;
instance.props.plugin.saveSettings();
});
}
},
{
label: "Show Empty Mentions/Unreads",
type: "toggle",
id: "favs_Empty",
checked: () => TopBarRef.current.state.showEmptyFavBadges,
action: () => {
instance.setState({
showEmptyFavBadges: !instance.state.showEmptyFavBadges
}, ()=>{
instance.props.plugin.settings.showEmptyFavBadges = !instance.props.plugin.settings.showEmptyFavBadges;
instance.props.plugin.saveSettings();
});
}
},
{
type: "separator",
id: "header4_1"
},
{
label: "Fav Groups:",
id: "header4_2",
disabled: true
},
{
type: "separator",
id: "header4_3"
},
{
label: "Show Mentions",
type: "toggle",
id: "favGroups_Mentions",
checked: () => TopBarRef.current.state.showFavGroupMentionBadges,
action: () => {
instance.setState({
showFavGroupMentionBadges: !instance.state.showFavGroupMentionBadges
}, ()=>{
instance.props.plugin.settings.showFavGroupMentionBadges = !instance.props.plugin.settings.showFavGroupMentionBadges;
instance.props.plugin.saveSettings();
});
}
},
{
label: "Show Unreads",
type: "toggle",
id: "favGroups_Unreads",
checked: () => TopBarRef.current.state.showFavGroupUnreadBadges,
action: () => {
instance.setState({
showFavGroupUnreadBadges: !instance.state.showFavGroupUnreadBadges
}, ()=>{
instance.props.plugin.settings.showFavGroupUnreadBadges = !instance.props.plugin.settings.showFavGroupUnreadBadges;
instance.props.plugin.saveSettings();
});
}
},
{
label: "Show Typing",
type: "toggle",
id: "favGroups_Typing",
checked: () => TopBarRef.current.state.showFavGroupTypingBadge,
action: () => {
instance.setState({
showFavGroupTypingBadge: !instance.state.showFavGroupTypingBadge
}, ()=>{
instance.props.plugin.settings.showFavGroupTypingBadge = !instance.props.plugin.settings.showFavGroupTypingBadge;
instance.props.plugin.saveSettings();
});
}
},
{
label: "Show Empty Mentions/Unreads",
type: "toggle",
id: "favGroups_Empty",
checked: () => TopBarRef.current.state.showEmptyFavGroupBadges,
action: () => {
instance.setState({
showEmptyFavGroupBadges: !instance.state.showEmptyFavGroupBadges
}, ()=>{
instance.props.plugin.settings.showEmptyFavGroupBadges = !instance.props.plugin.settings.showEmptyFavGroupBadges;
instance.props.plugin.saveSettings();
});
}
},
{
type: "separator",
id: "header2_1"
},
{
label: "Tabs:",
id: "header2_2",
disabled: true
},
{
type: "separator",
id: "header2_3"
},
{
label: "Show Mentions",
type: "toggle",
id: "tabs_Mentions",
checked: () => TopBarRef.current.state.showTabMentionBadges,
action: () => {
instance.setState({
showTabMentionBadges: !instance.state.showTabMentionBadges
}, ()=>{
instance.props.plugin.settings.showTabMentionBadges = !instance.props.plugin.settings.showTabMentionBadges;
instance.props.plugin.saveSettings();
});
}
},
{
label: "Show Unreads",
type: "toggle",
id: "tabs_Unreads",
checked: () => TopBarRef.current.state.showTabUnreadBadges,
action: () => {
instance.setState({
showTabUnreadBadges: !instance.state.showTabUnreadBadges
}, ()=>{
instance.props.plugin.settings.showTabUnreadBadges = !instance.props.plugin.settings.showTabUnreadBadges;
instance.props.plugin.saveSettings();
});
}
},
{
label: "Show Typing",
type: "toggle",
id: "tabs_Typing",
checked: () => TopBarRef.current.state.showTabTypingBadge,
action: () => {
instance.setState({
showTabTypingBadge: !instance.state.showTabTypingBadge
}, ()=>{
instance.props.plugin.settings.showTabTypingBadge = !instance.props.plugin.settings.showTabTypingBadge;
instance.props.plugin.saveSettings();
});
}
},
{
label: "Show Empty Mentions/Unreads",
type: "toggle",
id: "tabs_Empty",
checked: () => TopBarRef.current.state.showEmptyTabBadges,
action: () => {
instance.setState({
showEmptyTabBadges: !instance.state.showEmptyTabBadges
}, ()=>{
instance.props.plugin.settings.showEmptyTabBadges = !instance.props.plugin.settings.showEmptyTabBadges;
instance.props.plugin.saveSettings();
});
}
},
{
type: "separator",
id: "header3_1"
},
{
label: "Active Tabs:",
id: "header3_2",
disabled: true
},
{
type: "separator",
id: "header3_3"
},
{
label: "Show Mentions",
type: "toggle",
id: "activeTabs_Mentions",
checked: () => TopBarRef.current.state.showActiveTabMentionBadges,
action: () => {
instance.setState({
showActiveTabMentionBadges: !instance.state.showActiveTabMentionBadges
}, ()=>{
instance.props.plugin.settings.showActiveTabMentionBadges = !instance.props.plugin.settings.showActiveTabMentionBadges;
instance.props.plugin.saveSettings();
});
}
},
{
label: "Show Unreads",
type: "toggle",
id: "activeTabs_Unreads",
checked: () => TopBarRef.current.state.showActiveTabUnreadBadges,
action: () => {
instance.setState({
showActiveTabUnreadBadges: !instance.state.showActiveTabUnreadBadges
}, ()=>{
instance.props.plugin.settings.showActiveTabUnreadBadges = !instance.props.plugin.settings.showActiveTabUnreadBadges;
instance.props.plugin.saveSettings();
});
}
},
{
label: "Show Typing",
type: "toggle",
id: "activeTabs_Typing",
checked: () => TopBarRef.current.state.showActiveTabTypingBadge,
action: () => {
instance.setState({
showActiveTabTypingBadge: !instance.state.showActiveTabTypingBadge
}, ()=>{
instance.props.plugin.settings.showActiveTabTypingBadge = !instance.props.plugin.settings.showActiveTabTypingBadge;
instance.props.plugin.saveSettings();
});
}
},
{
label: "Show Empty Mentions/Unreads",
type: "toggle",
id: "activeTabs_Empty",
checked: () => TopBarRef.current.state.showEmptyActiveTabBadges,
action: () => {
instance.setState({
showEmptyActiveTabBadges: !instance.state.showEmptyActiveTabBadges
}, ()=>{
instance.props.plugin.settings.showEmptyActiveTabBadges = !instance.props.plugin.settings.showEmptyActiveTabBadges;
instance.props.plugin.saveSettings();
});
}
}
]
},
]
}
)
}
]),
{
position: "right",
align: "top"
}
)
};
//#endregion
//#region Global Common Functions
const closeAllDropdowns = () =>
{
var dropdowns = document.getElementsByClassName("channelTabs-favGroup-content");
var i;
for (i = 0; i < dropdowns.length; i++) {
var openDropdown = dropdowns[i];
if (openDropdown.classList.contains('channelTabs-favGroupShow')) {
openDropdown.classList.remove('channelTabs-favGroupShow');
}
}
currentGroupOpened = -1;
};
const mergeLists = (...items)=>
{
return items.filter(item => item.include===undefined||item.include).flatMap(item => item.values);
};
const getGuildChannels = (...guildIds)=>
{
const channels = ChannelStore.getGuildChannels ? Object.values(ChannelStore.getGuildChannels()) : ChannelStore.getMutableGuildChannels ? Object.values(ChannelStore.getMutableGuildChannels()) : [];
return channels.filter(c => guildIds.includes(c.guild_id) && c.type !== DiscordConstants.ChannelTypes.GUILD_VOICE && c.type !== DiscordConstants.ChannelTypes.GUILD_CATEGORY);
};
const updateFavEntry = (fav)=>
{
if(fav.guildId)
{
const channelIds = getGuildChannels(fav.guildId).filter(channel=>(PermissionUtils.can(Permissions.VIEW_CHANNEL, channel)) && (!MutedStore.isChannelMuted(channel.guild_id, channel.id))).map(channel=>channel.id);
return {
unreadCount: channelIds.map(id=>UnreadStateStore.getUnreadCount(id)||UnreadStateStore.getMentionCount(id)||(UnreadStateStore.hasUnread(id)?1:0)).reduce((a,b)=>a+b, 0),
unreadEstimated: channelIds.some(id=>UnreadStateStore.isEstimated(id)) || channelIds.some(id=>UnreadStateStore.getUnreadCount(id)===0&&UnreadStateStore.hasUnread(id)),
hasUnread: channelIds.some(id=>UnreadStateStore.hasUnread(id)),
mentionCount: channelIds.map(id=>UnreadStateStore.getMentionCount(id)||0).reduce((a,b)=>a+b, 0),
selected: SelectedGuildStore.getGuildId()===fav.guildId,
isTyping: isChannelTyping(fav.channelId),
currentStatus: getCurrentUserStatus(fav.url)
};
}
else
{
return {
unreadCount: UnreadStateStore.getUnreadCount(fav.channelId) || UnreadStateStore.getMentionCount(fav.channelId) || (UnreadStateStore.hasUnread(fav.channelId) ? 1 : 0),
unreadEstimated: UnreadStateStore.isEstimated(fav.channelId) || (UnreadStateStore.hasUnread(fav.channelId) && UnreadStateStore.getUnreadCount(fav.channelId) === 0),
hasUnread: UnreadStateStore.hasUnread(fav.channelId),
mentionCount: UnreadStateStore.getMentionCount(fav.channelId),
selected: SelectedChannelStore.getChannelId()===fav.channelId,
isTyping: isChannelTyping(fav.channelId),
currentStatus: getCurrentUserStatus(fav.url)
};
}
};
const getCurrentUserStatus = (pathname = location.pathname)=>
{
const cId = (pathname.match(/^\/channels\/(\d+|@me|@favorites)\/(\d+)/) || [])[2];
if(cId)
{
const channel = ChannelStore.getChannel(cId);
if(channel?.guild_id)
{
return "none";
}
else if(channel?.isDM())
{
const user = UserStore.getUser(channel.getRecipientId());
const status = UserStatusStore.getStatus(user.id);
return status;
}
else if(channel?.isGroupDM())
{
return "none";
}
}
return "none";
};
const getChannelTypingTooltipText = (userIds) =>
{
if (userIds)
{
const usernames = userIds.map(userId => UserStore.getUser(userId)).filter(user => user).map(user => user.tag);
const remainingUserCount = userIds.length - usernames.length;
const text = (()=>{
if(usernames.length === 0){
return `${remainingUserCount} user${remainingUserCount > 1 ? "s" : ""}`;
}else if(userIds.length > 2){
const otherCount = usernames.length - 1 + remainingUserCount;
return `${usernames[0]} and ${otherCount} other${otherCount > 1 ? "s" : ""}`;
}else if(remainingUserCount === 0){
return usernames.join(", ");
}else{
return `${usernames.join(", ")} and ${remainingUserCount} other${remainingUserCount > 1 ? "s" : ""}`;
}
})();
return text;
}
return "Someone is Typing...";
};
const getChannelTypingUsers = (channel_id) =>
{
const channel = ChannelStore.getChannel(channel_id);
const selfId = UserStore.getCurrentUser()?.id;
if (channel)
{
const userIds = Object.keys(UserTypingStore.getTypingUsers(channel_id)).filter(uId => (uId !== selfId));
const typingUsers = [...new Set(userIds)];
return typingUsers;
}
return null;
};
const isChannelTyping = (channel_id) =>
{
const channel = ChannelStore.getChannel(channel_id);
const selfId = UserStore.getCurrentUser()?.id;
if (channel)
{
const userIds = Object.keys(UserTypingStore.getTypingUsers(channel_id)).filter(uId => (uId !== selfId));
const typingUsers = [...new Set(userIds)];
if (typingUsers) return typingUsers.length === 0 ? false : true;
}
return false;
};
const isChannelDM = (channel_id) =>
{
return (()=>{const c=ChannelStore.getChannel(channel_id); return c && (c.isDM()||c.isGroupDM());})()
};
const getCurrentName = (pathname = location.pathname)=>
{
const cId = (pathname.match(/^\/channels\/(\d+|@me|@favorites)\/(\d+)/) || [])[2];
if(cId){
const channel = ChannelStore.getChannel(cId);
if(channel?.name) return (channel.guildId ? "@" : "#") + channel.name;
else if(channel?.rawRecipients) return "@" + channel.rawRecipients.map(u=>u.username).join(", ");
else return pathname;
}else{
if(pathname === "/channels/@me") return "Friends";
else if(pathname.match(/^\/[a-z\-]+$/)) return pathname.substr(1).split("-").map(part => part.substr(0, 1).toUpperCase() + part.substr(1)).join(" ");
else return pathname;
}
};
const getCurrentIconUrl = (pathname = location.pathname)=>
{
const cId = (pathname.match(/^\/channels\/(\d+|@me|@favorites)\/(\d+)/) || [])[2];
if(cId){
const channel = ChannelStore.getChannel(cId);
if(!channel) return "";
if(channel.guild_id){
const guild = GuildStore.getGuild(channel.guild_id);
return guild.getIconURL(40, false) || DefaultUserIconBlue;
}else if(channel.isDM()){
const user = UserStore.getUser(channel.getRecipientId());
return user.getAvatarURL(null, 40, false);
}else if(channel.isGroupDM()){
if(channel.icon) return `https://cdn.discordapp.com/channel-icons/${channel.id}/${channel.icon}.webp`;
else return DefaultUserIconGreen;
}
}
return DefaultUserIconGrey;
};
//#endregion
//#region Tab Definitions
const GetTabStyles = (viewMode, item)=>
{
if (item === "unreadBadge")
{
if (viewMode === "classic") return " channelTabs-classicBadgeAlignment";
else if (viewMode === "alt") return " channelTabs-badgeAlignLeft";
}
else if (item === "mentionBadge")
{
if (viewMode === "classic") return " channelTabs-classicBadgeAlignment";
else if (viewMode === "alt") return " channelTabs-badgeAlignRight";
}
else if (item === "typingBadge")
{
if (viewMode === "classic") return " channelTabs-classicBadgeAlignment";
else if (viewMode === "alt") return " channelTabs-typingBadgeAlignment";
}
return "";
};
const TabIcon = props=>React.createElement(
"img",
{
className: "channelTabs-tabIcon",
src: !props.iconUrl ? DefaultUserIconGrey :props.iconUrl
}
);
const TabStatus = props=>React.createElement(
"rect",
{
width: 6,
height: 6,
x: 14,
y: 14,
className: "channelTabs-tabStatus"
+ (props.currentStatus == "online" ? " channelTabs-onlineIcon" : "")
+ (props.currentStatus == "idle" ? " channelTabs-idleIcon" : "")
+ (props.currentStatus == "dnd" ? " channelTabs-doNotDisturbIcon" : "")
+ (props.currentStatus == "offline" ? " channelTabs-offlineIcon" : "")
+ (props.currentStatus == "none" ? " channelTabs-noneIcon" : "")
}
);
const TabName = props=>React.createElement(
"span",
{
className: "channelTabs-tabName"
},
props.name
);
const TabClose = props=>props.tabCount < 2 ? null : React.createElement(
"div",
{
className: "channelTabs-closeTab",
onClick: e=>{
e.stopPropagation();
props.closeTab();
}
},
React.createElement(Close, {})
);
const TabUnreadBadge = props=>React.createElement("div", {
className: "channelTabs-unreadBadge" + (!props.hasUnread ? " channelTabs-noUnread" : "") + GetTabStyles(props.viewMode, "unreadBadge")
}, props.unreadCount + (props.unreadEstimated ? "+" : ""));
const TabMentionBadge = props=>React.createElement("div", {
className: "channelTabs-mentionBadge" + (props.mentionCount === 0 ? " channelTabs-noMention" : "") + GetTabStyles(props.viewMode, "mentionBadge")
}, props.mentionCount);
const TabTypingBadge = ({viewMode, isTyping, userIds})=>{
if (isTyping === false) return null;
const text = getChannelTypingTooltipText(userIds);
return React.createElement(
"div",
{
className: "channelTabs-TypingContainer" + GetTabStyles(viewMode, "typingBadge")
},
React.createElement(
Tooltip,
{
text,
position: "bottom"
},
tooltipProps => React.createElement(Spinner, {
...tooltipProps,
type: "pulsingEllipsis",
className: `channelTabs-typingBadge`,
animated: isTyping,
style: {
opacity: 0.7
}
})
)
);
};
const CozyTab = (props)=>{
return React.createElement(
"div",
{},
React.createElement("svg", {
className: "channelTabs-tabIconWrapper",
width: "20",
height: "20",
viewBox: "0 0 20 20"
},
props.currentStatus === "none"
? React.createElement("foreignObject", { x: 0, y: 0, width: 20, height: 20 }, React.createElement(TabIcon, { iconUrl: props.iconUrl }))
: React.createElement("foreignObject", { x: 0, y: 0, width: 20, height: 20, mask: "url(#svg-mask-avatar-status-round-20)" }, React.createElement(TabIcon, { iconUrl: props.iconUrl })),
props.currentStatus === "none" ? null : React.createElement(TabStatus, { currentStatus: props.currentStatus })
),
React.createElement(TabName, {name: props.name}),
React.createElement(
"div",
{
className: "channelTabs-gridContainer",
},
React.createElement(
"div",
{className: "channelTabs-gridItemBR"},
!(props.selected ? props.showActiveTabTypingBadge : props.showTabTypingBadge) ? null : React.createElement(TabTypingBadge, {viewMode: "alt", isTyping: props.hasUsersTyping, userIds: getChannelTypingUsers(props.channelId)})
),
React.createElement(
"div",
{className: "channelTabs-gridItemTL"},
!(props.selected ? props.showActiveTabUnreadBadges : props.showTabUnreadBadges) ? null : !props.channelId || (ChannelStore.getChannel(props.channelId)?.isPrivate() ?? true) ? null : !(props.selected ? props.showEmptyActiveTabBadges : props.showEmptyTabBadges) && !props.hasUnread ? null : React.createElement(TabUnreadBadge, {viewMode: "alt", unreadCount: props.unreadCount, unreadEstimated: props.unreadEstimated, hasUnread: props.hasUnread, mentionCount: props.mentionCount})
),
React.createElement(
"div",
{className: "channelTabs-gridItemTR"},
!(props.selected ? props.showActiveTabMentionBadges : props.showTabMentionBadges) ? null : !(props.selected ? props.showEmptyActiveTabBadges : props.showEmptyTabBadges) && (props.mentionCount === 0) ? null : React.createElement(TabMentionBadge, {viewMode: "alt", mentionCount: props.mentionCount})
),
React.createElement("div", {className: "channelTabs-gridItemBL"}))
)
};
const CompactTab = (props)=>{
return React.createElement(
"div",
{},
React.createElement("svg", {
className: "channelTabs-tabIconWrapper",
width: "20",
height: "20",
viewBox: "0 0 20 20"
},
props.currentStatus === "none"
? React.createElement("foreignObject", { x: 0, y: 0, width: 20, height: 20 }, React.createElement(TabIcon, { iconUrl: props.iconUrl }))
: React.createElement("foreignObject", { x: 0, y: 0, width: 20, height: 20, mask: "url(#svg-mask-avatar-status-round-20)" }, React.createElement(TabIcon, { iconUrl: props.iconUrl })),
props.currentStatus === "none" ? null : React.createElement(TabStatus, { currentStatus: props.currentStatus })
),
React.createElement(TabName, {name: props.name}),
!(props.selected ? props.showActiveTabTypingBadge : props.showTabTypingBadge) ? null : React.createElement(
React.Fragment,
{},
React.createElement(TabTypingBadge, {viewMode: "classic", isTyping: props.hasUsersTyping, userIds: getChannelTypingUsers(props.channelId)})
),
!(props.selected ? props.showActiveTabUnreadBadges : props.showTabUnreadBadges) ? null : React.createElement(
React.Fragment,
{},
!props.channelId || (ChannelStore.getChannel(props.channelId)?.isPrivate() ?? true) ? null : !(props.selected ? props.showEmptyActiveTabBadges : props.showEmptyTabBadges) && !props.hasUnread ? null : React.createElement(TabUnreadBadge, {viewMode: "classic", unreadCount: props.unreadCount, unreadEstimated: props.unreadEstimated, hasUnread: props.hasUnread, mentionCount: props.mentionCount})
),
!(props.selected ? props.showActiveTabMentionBadges : props.showTabMentionBadges) ? null : React.createElement(
React.Fragment,
{},
!(props.selected ? props.showEmptyActiveTabBadges : props.showEmptyTabBadges) && (props.mentionCount === 0) ? null : React.createElement(TabMentionBadge, {viewMode: "classic", mentionCount: props.mentionCount})
)
)
};
const Tab = props=>React.createElement(
"div",
{
className: "channelTabs-tab"
+ (props.selected ? " channelTabs-selected" : "")
+ (props.minimized ? " channelTabs-minimized" : "")
+ (props.hasUnread ? " channelTabs-unread" : "")
+ (props.mentionCount > 0 ? " channelTabs-mention" : ""),
"data-mention-count": props.mentionCount,
"data-unread-count": props.unreadCount,
"data-unread-estimated": props.unreadEstimated,
onClick: ()=>{if(!props.selected) props.switchToTab(props.tabIndex);},
onMouseUp: e=>{
if(e.button !== 1) return;
e.preventDefault();
props.closeTab(props.tabIndex);
},
onContextMenu: e=> {CreateTabContextMenu(props,e)},
onMouseOver: e=> {
if (currentTabDragIndex == props.tabIndex || currentTabDragIndex == -1) return;
currentTabDragDestinationIndex = props.tabIndex;
},
onMouseDown: e => {
let mouseMove = e2 => {
if (Math.sqrt((e.pageX - e2.pageX)**2) > 20 || Math.sqrt((e.pageY - e2.pageY)**2) > 20) {
currentTabDragIndex = props.tabIndex;
document.removeEventListener("mousemove", mouseMove);
document.removeEventListener("mouseup", mouseUp);
let dragging = e3 => {
if (currentTabDragIndex != currentTabDragDestinationIndex)
{
if (currentTabDragDestinationIndex != -1)
{
props.moveTab(currentTabDragIndex, currentTabDragDestinationIndex);
currentTabDragDestinationIndex = currentTabDragDestinationIndex;
currentTabDragIndex = currentTabDragDestinationIndex;
}
}
};
let releasing = e3 => {
document.removeEventListener("mousemove", dragging);
document.removeEventListener("mouseup", releasing);
currentTabDragIndex = -1;
currentTabDragDestinationIndex = -1;
};
document.addEventListener("mousemove", dragging);
document.addEventListener("mouseup", releasing);
}
};
let mouseUp = _ => {
document.removeEventListener("mousemove", mouseMove);
document.removeEventListener("mouseup", mouseUp);
};
document.addEventListener("mousemove", mouseMove);
document.addEventListener("mouseup", mouseUp);
},
},
props.compactStyle ? CompactTab(props) : CozyTab(props),
React.createElement(TabClose, {tabCount: props.tabCount, closeTab: ()=>props.closeTab(props.tabIndex)})
);
//#endregion
//#region Fav Definitions
const FavMoveToGroupList = props => {
var groups = props.favGroups.map(
(group, index) => {
var entry = {
label: group.name,
id: "entry" + index,
action: () => props.moveToFavGroup(props.favIndex, group.groupId)
};
return entry;
}
);
if (groups.length === 0) {
return [{
label: "No groups",
disabled: true
}]
}
return groups;
}
const FavIcon = props=>React.createElement(
"img",
{
className: "channelTabs-favIcon",
src: !props.iconUrl ? DefaultUserIconGrey :props.iconUrl
}
);
const FavStatus = props=>React.createElement(
"rect",
{
width: 6,
height: 6,
x: 14,
y: 14,
className: "channelTabs-favStatus"
+ (props.currentStatus == "online" ? " channelTabs-onlineIcon" : "")
+ (props.currentStatus == "idle" ? " channelTabs-idleIcon" : "")
+ (props.currentStatus == "dnd" ? " channelTabs-doNotDisturbIcon" : "")
+ (props.currentStatus == "offline" ? " channelTabs-offlineIcon" : "")
+ (props.currentStatus == "none" ? " channelTabs-noneIcon" : "")
}
);
const FavName = props=>React.createElement(
"span",
{
className: "channelTabs-favName"
},
props.name
);
const FavUnreadBadge = props=>React.createElement("div", {
className: "channelTabs-unreadBadge" + (!props.hasUnread ? " channelTabs-noUnread" : "")
}, props.unreadCount + (props.unreadEstimated ? "+" : ""));
const FavMentionBadge = props=>React.createElement("div", {
className: "channelTabs-mentionBadge" + (props.mentionCount === 0 ? " channelTabs-noMention" : "")
}, props.mentionCount);
const FavTypingBadge = ({isTyping, userIds})=>{
const text = getChannelTypingTooltipText(userIds);
return React.createElement(
Tooltip,
{
text,
position: "bottom"
},
tooltipProps => React.createElement("div", {
...tooltipProps,
className: "channelTabs-typingBadge" + (!isTyping ? " channelTabs-noTyping" : "")
}, React.createElement(Spinner, {
type: "pulsingEllipsis",
animated: (!isTyping ? false : true)
}))
)
};
const Fav = props=>React.createElement(
"div",
{
className: "channelTabs-fav"
+ (props.channelId ? " channelTabs-channel" : props.guildId ? " channelTabs-guild" : "")
+ (props.selected ? " channelTabs-selected" : "")
+ (props.minimized ? " channelTabs-minimized" : "")
+ (props.hasUnread ? " channelTabs-unread" : "")
+ (props.mentionCount > 0 ? " channelTabs-mention" : ""),
"data-mention-count": props.mentionCount,
"data-unread-count": props.unreadCount,
"data-unread-estimated": props.unreadEstimated,
onClick: ()=>props.guildId ? NavigationUtils.transitionToGuild(props.guildId, SelectedChannelStore.getChannelId(props.guildId)) : NavigationUtils.transitionTo(props.url),
onMouseUp: e=>{
if(e.button !== 1) return;
e.preventDefault();
props.openInNewTab();
},
onContextMenu: e=> {CreateFavContextMenu(props,e)},
onMouseOver: e=> {
if (currentFavDragIndex == props.favIndex || currentFavDragIndex == -1) return;
currentFavDragDestinationIndex = props.favIndex;
},
onMouseDown: e => {
let mouseMove = e2 => {
if (Math.sqrt((e.pageX - e2.pageX)**2) > 20 || Math.sqrt((e.pageY - e2.pageY)**2) > 20) {
currentFavDragIndex = props.favIndex;
document.removeEventListener("mousemove", mouseMove);
document.removeEventListener("mouseup", mouseUp);
let dragging = e3 => {
if (currentFavDragIndex != currentFavDragDestinationIndex)
{
if (currentFavDragDestinationIndex != -1)
{
props.moveFav(currentFavDragIndex, currentFavDragDestinationIndex);
currentFavDragDestinationIndex = currentFavDragDestinationIndex;
currentFavDragIndex = currentFavDragDestinationIndex;
}
}
};
let releasing = e3 => {
document.removeEventListener("mousemove", dragging);
document.removeEventListener("mouseup", releasing);
currentFavDragIndex = -1;
currentFavDragDestinationIndex = -1;
};
document.addEventListener("mousemove", dragging);
document.addEventListener("mouseup", releasing);
}
};
let mouseUp = _ => {
document.removeEventListener("mousemove", mouseMove);
document.removeEventListener("mouseup", mouseUp);
};
document.addEventListener("mousemove", mouseMove);
document.addEventListener("mouseup", mouseUp);
},
},
React.createElement("svg", {
className: "channelTabs-favIconWrapper",
width: "20",
height: "20",
viewBox: "0 0 20 20"
},
props.currentStatus === "none"
? React.createElement("foreignObject", { x: 0, y: 0, width: 20, height: 20 }, React.createElement(FavIcon, { iconUrl: props.iconUrl }))
: React.createElement("foreignObject", { x: 0, y: 0, width: 20, height: 20, mask: "url(#svg-mask-avatar-status-round-20)" }, React.createElement(FavIcon, { iconUrl: props.iconUrl })),
props.currentStatus === "none" ? null : React.createElement(FavStatus, { currentStatus: props.currentStatus })
),
React.createElement(FavName, {name: props.name}),
!(props.showFavUnreadBadges && (props.channelId || props.guildId)) ? null : React.createElement(
React.Fragment,
{},
isChannelDM(props.channelId) ? null : !props.showEmptyFavBadges && props.unreadCount === 0 ? null : React.createElement(FavUnreadBadge, {unreadCount: props.unreadCount, unreadEstimated: props.unreadEstimated, hasUnread: props.hasUnread})
),
!(props.showFavMentionBadges && (props.channelId || props.guildId)) ? null : React.createElement(
React.Fragment,
{},
!props.showEmptyFavBadges && props.mentionCount === 0 ? null : React.createElement(FavMentionBadge, {mentionCount: props.mentionCount})
),
!(props.showFavTypingBadge && (props.channelId || props.guildId)) ? null : React.createElement(
React.Fragment,
{},
React.createElement(FavTypingBadge, {isTyping: props.isTyping, userIds: getChannelTypingUsers(props.channelId)})
)
);
//#endregion
//#region Misc. Definitions
const NewTab = props=>React.createElement(
"div",
{
className: "channelTabs-newTab",
onClick: props.openNewTab
},
React.createElement(PlusAlt, {})
);
//#endregion
//#region FavItems/FavFolders Definitions
const NoFavItemsPlaceholder = props=>React.createElement("span", {
className: "channelTabs-noFavNotice"
}, "You don't have any favs yet. Right click a tab to mark it as favourite. You can disable this bar in the settings."
);
const FavItems = props=>{
var isDefault = (props.group === null);
return props.favs.filter(item => item).map(
(fav, favIndex) =>
{
var canCreate = (isDefault ? fav.groupId === -1 : fav.groupId === props.group.groupId);
return canCreate ? React.createElement(
Flux.connectStores([UnreadStateStore, UserTypingStore, SelectedChannelStore], ()=> updateFavEntry(fav))
(
result => React.createElement(
Fav,
{
name: fav.name,
iconUrl: fav.iconUrl,
url: fav.url,
favCount: props.favs.length,
favGroups: props.favGroups,
rename: ()=>props.rename(fav.name, favIndex),
delete: ()=>props.delete(favIndex),
openInNewTab: ()=>props.openInNewTab(fav),
moveLeft: ()=>props.move(favIndex, (favIndex + props.favs.length - 1) % props.favs.length),
moveRight: ()=>props.move(favIndex, (favIndex + 1) % props.favs.length),
minimizeFav: props.minimizeFav,
minimized: fav.minimized,
moveToFavGroup: props.moveToFavGroup,
moveFav: props.move,
favIndex,
channelId: fav.channelId,
guildId: fav.guildId,
groupId: fav.groupId,
showFavUnreadBadges: props.showFavUnreadBadges,
showFavMentionBadges: props.showFavMentionBadges,
showFavTypingBadge: props.showFavTypingBadge,
showEmptyFavBadges: props.showEmptyFavBadges,
isTyping: isChannelTyping(fav.channelId),
currentStatus: getCurrentUserStatus(fav.url),
...result
}
)
)
) : null;
}
);
};
const FavFolder = props=>React.createElement(
"div",
{
className: "channelTabs-favGroup",
onContextMenu: e=>{CreateFavGroupContextMenu(props,e)},
onMouseOver: e=> {
if (currentGroupDragIndex == props.groupIndex || currentGroupDragIndex == -1) return;
currentGroupDragDestinationIndex = props.groupIndex;
},
onMouseDown: e => {
let mouseMove = e2 => {
if (Math.sqrt((e.pageX - e2.pageX)**2) > 20 || Math.sqrt((e.pageY - e2.pageY)**2) > 20) {
currentGroupDragIndex = props.groupIndex;
document.removeEventListener("mousemove", mouseMove);
document.removeEventListener("mouseup", mouseUp);
let dragging = e3 => {
if (currentGroupDragIndex != currentGroupDragDestinationIndex)
{
if (currentGroupDragDestinationIndex != -1)
{
props.moveFavGroup(currentGroupDragIndex, currentGroupDragDestinationIndex);
currentGroupDragDestinationIndex = currentGroupDragDestinationIndex;
currentGroupDragIndex = currentGroupDragDestinationIndex;
}
}
};
let releasing = e3 => {
document.removeEventListener("mousemove", dragging);
document.removeEventListener("mouseup", releasing);
currentGroupDragIndex = -1;
currentGroupDragDestinationIndex = -1;
};
document.addEventListener("mousemove", dragging);
document.addEventListener("mouseup", releasing);
}
};
let mouseUp = _ => {
document.removeEventListener("mousemove", mouseMove);
document.removeEventListener("mouseup", mouseUp);
};
document.addEventListener("mousemove", mouseMove);
document.addEventListener("mouseup", mouseUp);
}
},
React.createElement(
"div",
{
className: "channelTabs-favGroupBtn",
onClick: () => {
closeAllDropdowns();
document.getElementById("favGroup-content-" + props.groupIndex).classList.toggle("channelTabs-favGroupShow");
currentGroupOpened = props.groupIndex;
}
},
props.favGroup.name,
props.showFavGroupMentionBadges ? props.mentionCountGroup == 0 && !props.showEmptyFavGroupBadges ? null : React.createElement(FavMentionBadge, {mentionCount: props.mentionCountGroup}) : null,
props.showFavGroupUnreadBadges ? props.unreadCountGroup == 0 && !props.showEmptyFavGroupBadges ? null : React.createElement(FavUnreadBadge, {unreadCount: props.unreadCountGroup, unreadEstimated: props.unreadEstimatedGroup, hasUnread: props.hasUnreadGroup}) : null,
props.showFavGroupTypingBadge && (props.isTypingGroup) ? React.createElement(FavTypingBadge, {isTyping: props.isTypingGroup, userIds: null}) : null
),
React.createElement(
"div",
{
className: "channelTabs-favGroup-content" + (currentGroupOpened === props.groupIndex ? " channelTabs-favGroupShow" : ""),
id: "favGroup-content-" + props.groupIndex
},
React.createElement(FavItems, {group: props.favGroup, ...props})
)
);
const FavFolders = (props)=>{
return props.favGroups.map((favGroup, index) =>
{
return React.createElement(Flux.connectStores([UnreadStateStore, SelectedChannelStore, UserTypingStore], () =>
{
var unreadCount = 0;
var unreadEstimated = 0;
var hasUnread = false;
var mentionCount = 0;
var isTyping = false;
props.favs.filter(item => item).forEach((fav, favIndex) =>
{
var canCreate = fav.groupId === favGroup.groupId;
if (canCreate)
{
var hasUnreads = isChannelDM(fav.channelId);
var result = updateFavEntry(fav);
if (!hasUnreads) unreadCount += result.unreadCount;
mentionCount += result.mentionCount;
if (!hasUnreads) unreadEstimated += result.unreadEstimated;
if (!hasUnreads) hasUnread = (result.hasUnread ? true : hasUnread);
isTyping = (result.isTyping ? true : isTyping);
}
}
);
return {
unreadCount,
mentionCount,
unreadEstimated,
mentionCount,
hasUnread,
isTyping
};
})
(
result =>
{
return React.createElement(FavFolder,
{
groupIndex: index,
groupCount: props.favGroups.length,
favGroup: favGroup,
unreadCountGroup: result.unreadCount,
unreadEstimatedGroup: result.unreadEstimated,
mentionCountGroup: result.mentionCount,
hasUnreadGroup: result.hasUnread,
isTypingGroup: result.isTyping,
showFavGroupUnreadBadges: props.showFavGroupUnreadBadges,
showFavGroupMentionBadges: props.showFavGroupMentionBadges,
showFavGroupTypingBadge: props.showFavGroupTypingBadge,
showEmptyFavGroupBadges: props.showEmptyFavGroupBadges,
...props
});
}
));
}
);
};
//#endregion
//#region FavBar/TopBar/TabBar Definitions
function nextTab(){
if(TopBarRef.current) TopBarRef.current.switchToTab((TopBarRef.current.state.selectedTabIndex + 1) % TopBarRef.current.state.tabs.length);
}
function previousTab(){
if(TopBarRef.current) TopBarRef.current.switchToTab((TopBarRef.current.state.selectedTabIndex - 1 + TopBarRef.current.state.tabs.length) % TopBarRef.current.state.tabs.length);
}
function closeCurrentTab(){
if(TopBarRef.current) TopBarRef.current.closeTab(TopBarRef.current.state.selectedTabIndex);
}
const TabBar = props=>React.createElement(
"div",
{
className: "channelTabs-tabContainer",
"data-tab-count": props.tabs.length
},
React.createElement("div", {
className: "channelTabs-tabNav"
},
React.createElement("div", {
className: "channelTabs-tabNavLeft",
onClick: () =>{ TopBarRef.current.state.useStandardNav ? NavShortcuts.NAVIGATE_BACK.action() : previousTab(); },
onContextMenu: () =>{ !TopBarRef.current.state.useStandardNav ? NavShortcuts.NAVIGATE_BACK.action() : previousTab(); }
},
React.createElement(LeftCaret, {})),
React.createElement("div", {
className: "channelTabs-tabNavRight",
onClick: () =>{ TopBarRef.current.state.useStandardNav ? NavShortcuts.NAVIGATE_FORWARD.action() : nextTab(); },
onContextMenu: () =>{ !TopBarRef.current.state.useStandardNav ? NavShortcuts.NAVIGATE_FORWARD.action() : nextTab(); }
},
React.createElement(RightCaret, {})),
React.createElement("div", {
className: "channelTabs-tabNavClose",
onClick: () =>{ closeCurrentTab() },
onContextMenu: props.openNewTab
},
React.createElement(Close, {}))
),
props.tabs.map((tab, tabIndex)=>React.createElement(Flux.connectStores([UnreadStateStore, UserTypingStore, UserStatusStore], ()=>({
unreadCount: UnreadStateStore.getUnreadCount(tab.channelId),
unreadEstimated: UnreadStateStore.isEstimated(tab.channelId),
hasUnread: UnreadStateStore.hasUnread(tab.channelId),
mentionCount: UnreadStateStore.getMentionCount(tab.channelId),
hasUsersTyping: isChannelTyping(tab.channelId),
currentStatus: getCurrentUserStatus(tab.url)
}))(result => React.createElement(
Tab,
{
switchToTab: props.switchToTab,
closeTab: props.closeTab,
addToFavs: props.addToFavs,
minimizeTab: props.minimizeTab,
moveLeft: ()=>props.move(tabIndex, (tabIndex + props.tabs.length - 1) % props.tabs.length),
moveRight: ()=>props.move(tabIndex, (tabIndex + 1) % props.tabs.length),
openInNewTab: ()=>props.openInNewTab(tab),
moveTab: props.move,
tabCount: props.tabs.length,
tabIndex,
name: tab.name,
iconUrl: tab.iconUrl,
currentStatus: result.currentStatus,
url: tab.url,
selected: tab.selected,
minimized: tab.minimized,
channelId: tab.channelId,
unreadCount: result.unreadCount,
unreadEstimated: result.unreadEstimated,
hasUnread: result.hasUnread,
mentionCount: result.mentionCount,
hasUsersTyping: result.hasUsersTyping,
showTabUnreadBadges: props.showTabUnreadBadges,
showTabMentionBadges: props.showTabMentionBadges,
showTabTypingBadge: props.showTabTypingBadge,
showEmptyTabBadges: props.showEmptyTabBadges,
showActiveTabUnreadBadges: props.showActiveTabUnreadBadges,
showActiveTabMentionBadges: props.showActiveTabMentionBadges,
showActiveTabTypingBadge: props.showActiveTabTypingBadge,
showEmptyActiveTabBadges: props.showEmptyActiveTabBadges,
compactStyle: props.compactStyle
}
)))),
React.createElement(NewTab, {
openNewTab: props.openNewTab
})
);
const FavBar = props=>React.createElement(
"div",
{
className: "channelTabs-favContainer" + (props.favs.length == 0 ? " channelTabs-noFavs" : ""),
"data-fav-count": props.favs.length,
onContextMenu: e=>{CreateFavBarContextMenu(props, e);}
},
React.createElement(FavFolders, props),
props.favs.length > 0 ? React.createElement(FavItems, {group: null, ...props}) : React.createElement(NoFavItemsPlaceholder, {}),
);
const TopBar = class TopBar extends React.Component {
//#region Constructor
constructor(props){
super(props);
this.state = {
selectedTabIndex: Math.max(props.tabs.findIndex(tab => tab.selected), 0),
tabs: props.tabs,
favs: props.favs,
favGroups: props.favGroups,
showTabBar: props.showTabBar,
showFavBar: props.showFavBar,
showFavUnreadBadges: props.showFavUnreadBadges,
showFavMentionBadges: props.showFavMentionBadges,
showFavTypingBadge: props.showFavTypingBadge,
showEmptyFavBadges: props.showEmptyFavBadges,
showTabUnreadBadges: props.showTabUnreadBadges,
showTabMentionBadges: props.showTabMentionBadges,
showTabTypingBadge: props.showTabTypingBadge,
showEmptyTabBadges: props.showEmptyTabBadges,
showActiveTabUnreadBadges: props.showActiveTabUnreadBadges,
showActiveTabMentionBadges: props.showActiveTabMentionBadges,
showActiveTabTypingBadge: props.showActiveTabTypingBadge,
showEmptyActiveTabBadges: props.showEmptyActiveTabBadges,
showFavGroupUnreadBadges: props.showFavGroupUnreadBadges,
showFavGroupMentionBadges: props.showFavGroupMentionBadges,
showFavGroupTypingBadge: props.showFavGroupTypingBadge,
showEmptyFavGroupBadges: props.showEmptyFavGroupBadges,
addFavGroup: props.addFavGroup,
compactStyle: props.compactStyle,
showQuickSettings: props.showQuickSettings,
showNavButtons: props.showNavButtons,
alwaysFocusNewTabs: props.alwaysFocusNewTabs,
useStandardNav: props.useStandardNav
};
this.switchToTab = this.switchToTab.bind(this);
this.closeTab = this.closeTab.bind(this);
this.saveChannel = this.saveChannel.bind(this);
this.renameFav = this.renameFav.bind(this);
this.deleteFav = this.deleteFav.bind(this);
this.addToFavs = this.addToFavs.bind(this);
this.minimizeTab = this.minimizeTab.bind(this);
this.minimizeFav = this.minimizeFav.bind(this);
this.moveTab = this.moveTab.bind(this);
this.moveFav = this.moveFav.bind(this);
this.addFavGroup = this.addFavGroup.bind(this);
this.moveToFavGroup = this.moveToFavGroup.bind(this);
this.renameFavGroup = this.renameFavGroup.bind(this);
this.removeFavGroup = this.removeFavGroup.bind(this);
this.moveFavGroup = this.moveFavGroup.bind(this);
this.openNewTab = this.openNewTab.bind(this);
this.openTabInNewTab = this.openTabInNewTab.bind(this);
this.openFavInNewTab = this.openFavInNewTab.bind(this);
this.openFavGroupInNewTab = this.openFavGroupInNewTab.bind(this);
this.hideFavBar = this.hideFavBar.bind(this);
}
//#endregion
//#region Tab Functions
minimizeTab(tabIndex){
this.setState({
tabs: this.state.tabs.map((tab, index) => {
if(index == tabIndex) return Object.assign({}, tab, {minimized: !tab.minimized});
else return Object.assign({}, tab); // or return tab;
})
}, this.props.plugin.saveSettings);
}
switchToTab(tabIndex){
this.setState({
tabs: this.state.tabs.map((tab, index) => {
if(index === tabIndex){
return Object.assign({}, tab, {selected: true});
}else{
return Object.assign({}, tab, {selected: false});
}
}),
selectedTabIndex: tabIndex
}, this.props.plugin.saveSettings);
switching = true;
NavigationUtils.transitionTo(this.state.tabs[tabIndex].url);
switching = false;
}
closeTab(tabIndex, mode){
if(this.state.tabs.length === 1) return;
if (mode === "single" || mode == null)
{
this.setState({
tabs: this.state.tabs.filter((tab, index)=>index !== tabIndex),
selectedTabIndex: Math.max(0, this.state.selectedTabIndex - (this.state.selectedTabIndex >= tabIndex ? 1 : 0))
}, ()=>{
if(!this.state.tabs[this.state.selectedTabIndex].selected){
this.switchToTab(this.state.selectedTabIndex);
}
this.props.plugin.saveSettings();
});
}
else if (mode == "other")
{
this.setState({
tabs: this.state.tabs.filter((tab, index)=>index === tabIndex),
selectedTabIndex: 0
}, ()=>{
if(!this.state.tabs[0].selected){
this.switchToTab(this.state.selectedTabIndex);
}
this.props.plugin.saveSettings();
});
}
else if (mode === "left")
{
this.setState({
tabs: this.state.tabs.filter((tab, index)=>index >= tabIndex),
selectedTabIndex: 0
}, ()=>{
if(!this.state.tabs[this.state.selectedTabIndex].selected){
this.switchToTab(this.state.selectedTabIndex);
}
this.props.plugin.saveSettings();
});
}
else if (mode === "right")
{
this.setState({
tabs: this.state.tabs.filter((tab, index)=>index <= tabIndex),
selectedTabIndex: tabIndex
}, ()=>{
if(!this.state.tabs[this.state.selectedTabIndex].selected){
this.switchToTab(this.state.selectedTabIndex);
}
this.props.plugin.saveSettings();
});
}
}
moveTab(fromIndex, toIndex){
if(fromIndex === toIndex) return;
const tabs = this.state.tabs.filter((tab, index)=>index !== fromIndex);
tabs.splice(toIndex, 0, this.state.tabs[fromIndex]);
this.setState({
tabs,
selectedTabIndex: tabs.findIndex(tab=>tab.selected)
}, this.props.plugin.saveSettings);
}
//#endregion
//#region Fav Functions
hideFavBar(){
this.setState({
showFavBar: false
}, ()=>{
this.props.plugin.settings.showFavBar = false;
this.props.plugin.saveSettings();
});
}
renameFav(currentName, favIndex){
let name = currentName;
BdApi.showConfirmationModal(
"What should the new name be?",
React.createElement(Textbox, {
onChange: newContent=>name = newContent.trim()
}),
{
onConfirm: ()=>{
if(!name) return;
this.setState({
favs: this.state.favs.map((fav, index)=>{
if(index === favIndex) return Object.assign({}, fav, {name});
else return Object.assign({}, fav);
})
}, this.props.plugin.saveSettings);
}
}
);
}
minimizeFav(favIndex){
this.setState({
favs: this.state.favs.map((fav, index) => {
if(index == favIndex) return Object.assign({}, fav, {minimized: !fav.minimized});
else return Object.assign({}, fav); // or return tab;
})
}, this.props.plugin.saveSettings);
}
deleteFav(favIndex){
this.setState({
favs: this.state.favs.filter((fav, index)=>index!==favIndex)
}, this.props.plugin.saveSettings);
}
/**
* The guildId parameter is only passed when the guild is saved and not the channel alone.
* This indicates that the currently selected channel needs to get selected instead of the
* provided channel id (which should be empty when a guildId is provided)
*/
addToFavs(name, iconUrl, url, channelId, guildId){
var groupId = -1;
this.setState({
favs: [...this.state.favs, {name, iconUrl, url, channelId, guildId, groupId}]
}, this.props.plugin.saveSettings);
}
moveFav(fromIndex, toIndex){
if(fromIndex === toIndex) return;
const favs = this.state.favs.filter((fav, index)=>index !== fromIndex);
favs.splice(toIndex, 0, this.state.favs[fromIndex]);
this.setState({favs}, this.props.plugin.saveSettings);
}
//#endregion
//#region Fav Group Functions
createFavGroupId()
{
var generatedId = this.state.favGroups.length;
var isUnique = false;
var duplicateFound = false;
while (!isUnique)
{
for (var i = 0; i < this.state.favGroups.length; i++)
{
var group = this.state.favGroups[i];
if (generatedId === group.groupId) duplicateFound = true;
}
if (!duplicateFound) isUnique = true;
else
{
generatedId++;
duplicateFound = false;
}
}
return generatedId;
}
addFavGroup()
{
let name = "New Group";
BdApi.showConfirmationModal(
"What should the new name be?",
React.createElement(Textbox, {
onChange: newContent=>name = newContent.trim()
}),
{
onConfirm: ()=>{
if(!name) return;
this.setState({
favGroups: [...this.state.favGroups, {name: name, groupId: this.createFavGroupId()}]
}, this.props.plugin.saveSettings);
}
}
);
}
renameFavGroup(currentName, groupId)
{
let name = currentName;
BdApi.showConfirmationModal(
"What should the new name be?",
React.createElement(Textbox, {
onChange: newContent=>name = newContent.trim()
}),
{
onConfirm: ()=>{
if(!name) return;
this.setState({
favGroups: this.state.favGroups.map((group, index)=>{
if(group.groupId === groupId) return Object.assign({}, group, {name});
else return Object.assign({}, group);
})
}, this.props.plugin.saveSettings);
}
}
);
}
removeFavGroup(groupId)
{
this.setState({
favGroups: this.state.favGroups.filter((group, index)=>group.groupId!==groupId)
}, this.props.plugin.saveSettings);
this.setState({
favs: this.state.favs.map((fav, index)=>{
if(fav.groupId === groupId) return Object.assign({}, fav, {groupId: -1});
else return Object.assign({}, fav);
})
}, this.props.plugin.saveSettings);
}
moveToFavGroup(favIndex, groupId)
{
this.setState({
favs: this.state.favs.map((fav, index)=>{
if (index === favIndex)
{
return Object.assign({}, fav, {groupId: groupId});
}
else
{
return Object.assign({}, fav);
}
})
}, this.props.plugin.saveSettings);
}
moveFavGroup(fromIndex, toIndex){
if(fromIndex === toIndex) return;
const favGroups = this.state.favGroups.filter((group, index)=>index !== fromIndex);
favGroups.splice(toIndex, 0, this.state.favGroups[fromIndex]);
this.setState({favGroups: favGroups}, this.props.plugin.saveSettings);
}
//#endregion
//#region New Tab Functions
saveChannel(guildId, channelId, name, iconUrl)
{
if (this.state.alwaysFocusNewTabs)
{
//Open and Focus New Tab
const newTabIndex = this.state.tabs.length;
this.setState({
tabs: [...this.state.tabs.map(tab=>Object.assign(tab, {selected: false})), {
url: `/channels/${guildId || "@me"}/${channelId}`,
name,
iconUrl,
channelId,
minimized: false,
groupId: -1
}],
selectedTabIndex: newTabIndex
}, ()=>{
this.props.plugin.saveSettings();
this.switchToTab(newTabIndex);
});
}
else
{
//Open New Tab
this.setState({
tabs: [...this.state.tabs, {
url: `/channels/${guildId || "@me"}/${channelId}`,
name,
iconUrl,
channelId,
minimized: false,
groupId: -1
}]
}, this.props.plugin.saveSettings);
}
}
openNewTab() {
const newTabIndex = this.state.tabs.length;
this.setState({
tabs: [...this.state.tabs.map(tab=>Object.assign(tab, {selected: false})), {
url: "/channels/@me",
name: "Friends",
selected: true,
channelId: undefined
}],
selectedTabIndex: newTabIndex
}, ()=>{
this.props.plugin.saveSettings();
this.switchToTab(newTabIndex);
});
}
openTabInNewTab(tab)
{
//Used to Duplicate Tabs
this.setState({
tabs: [...this.state.tabs, Object.assign({}, tab, {selected: false})]
}, this.props.plugin.saveSettings);
}
openFavInNewTab(fav, isGroup)
{
if (this.state.alwaysFocusNewTabs && !isGroup)
{
//Opens and Focuses New Tab
const newTabIndex = this.state.tabs.length;
const url = fav.url + (fav.guildId ? `/${fav.guildId}` : "");
this.setState({
tabs: [...this.state.tabs.map(tab=>Object.assign(tab, {selected: false})), {
url,
name: getCurrentName(url),
iconUrl: getCurrentIconUrl(url),
currentStatus: getCurrentUserStatus(url),
channelId: fav.channelId || SelectedChannelStore.getChannelId(fav.guildId)
}],
selectedTabIndex: newTabIndex
}, ()=>{
this.props.plugin.saveSettings();
this.switchToTab(newTabIndex);
});
}
else
{
//Opens New Tab
const url = fav.url + (fav.guildId ? `/${fav.guildId}` : "");
this.setState({
tabs: [...this.state.tabs, {
url,
selected: false,
name: getCurrentName(url),
iconUrl: getCurrentIconUrl(url),
currentStatus: getCurrentUserStatus(url),
channelId: fav.channelId || SelectedChannelStore.getChannelId(fav.guildId)
}]
}, this.props.plugin.saveSettings);
}
}
openFavGroupInNewTab(groupId)
{
this.state.favs.filter(item => item).map(
(fav, favIndex) =>
{
var canCreate = (fav.groupId === groupId);
if (canCreate)
{
this.openFavInNewTab(fav, true);
}
}
)
}
//#endregion
//#region Other Functions
render(){
return React.createElement(
"div",
{
id: "channelTabs-container"
},
!this.state.showQuickSettings ? null : React.createElement('div',
{
id: "channelTabs-settingsMenu",
dangerouslySetInnerHTML: { __html: SettingsMenuIcon },
onClick: e=>{CreateSettingsContextMenu(this,e);}
}),
!this.state.showTabBar ? null : React.createElement(TabBar, {
tabs: this.state.tabs,
showTabUnreadBadges: this.state.showTabUnreadBadges,
showTabMentionBadges: this.state.showTabMentionBadges,
showTabTypingBadge: this.state.showTabTypingBadge,
showEmptyTabBadges: this.state.showEmptyTabBadges,
showActiveTabUnreadBadges: this.state.showActiveTabUnreadBadges,
showActiveTabMentionBadges: this.state.showActiveTabMentionBadges,
showActiveTabTypingBadge: this.state.showActiveTabTypingBadge,
showEmptyActiveTabBadges: this.state.showEmptyActiveTabBadges,
compactStyle: this.state.compactStyle,
privacyMode: this.state.privacyMode,
radialStatusMode: this.state.radialStatusMode,
tabWidthMin: this.state.tabWidthMin,
closeTab: this.closeTab,
switchToTab: this.switchToTab,
openNewTab: this.openNewTab,
openInNewTab: this.openTabInNewTab,
addToFavs: this.addToFavs,
minimizeTab: this.minimizeTab,
move: this.moveTab
}),
!this.state.showFavBar ? null : React.createElement(FavBar, {
favs: this.state.favs,
favGroups: this.state.favGroups,
showFavUnreadBadges: this.state.showFavUnreadBadges,
showFavMentionBadges: this.state.showFavMentionBadges,
showFavTypingBadge: this.state.showFavTypingBadge,
showEmptyFavBadges: this.state.showEmptyFavBadges,
privacyMode: this.state.privacyMode,
radialStatusMode: this.state.radialStatusMode,
showFavGroupUnreadBadges: this.state.showFavGroupUnreadBadges,
showFavGroupMentionBadges: this.state.showFavGroupMentionBadges,
showFavGroupTypingBadge: this.state.showFavGroupTypingBadge,
showEmptyFavGroupBadges: this.state.showEmptyFavGroupBadges,
rename: this.renameFav,
delete: this.deleteFav,
addToFavs: this.addToFavs,
minimizeFav: this.minimizeFav,
openInNewTab: this.openFavInNewTab,
move: this.moveFav,
moveFavGroup: this.moveFavGroup,
addFavGroup: this.addFavGroup,
moveToFavGroup: this.moveToFavGroup,
removeFavGroup: this.removeFavGroup,
renameFavGroup: this.renameFavGroup,
openFavGroupInNewTab: this.openFavGroupInNewTab,
hideFavBar: this.hideFavBar
})
);
}
//#endregion
};
const TopBarRef = React.createRef();
//#endregion
//#region Plugin Decleration
return class ChannelTabs extends Plugin
{
//#region Start/Stop Functions
constructor(){
super();
}
onStart(isRetry = false){
//console.warn("CT Start");
if(isRetry && !BdApi.Plugins.isEnabled(config.info.name)) return;
if(!UserStore.getCurrentUser()) return setTimeout(()=>this.onStart(true), 1000);
//console.warn(UserStore.getCurrentUser());
patches = [];
this.loadSettings();
this.applyStyle();
this.ifNoTabsExist();
this.promises = {state:{cancelled: false}, cancel(){this.state.cancelled = true;}};
this.saveSettings = this.saveSettings.bind(this);
this.keybindHandler = this.keybindHandler.bind(this);
this.onSwitch();
this.patchAppView(this.promises.state);
this.patchContextMenus();
this.ifReopenLastChannelDefault();
document.addEventListener("keydown", this.keybindHandler);
window.onclick = (event) => this.clickHandler(event);
}
onStop(){
this.removeStyle();
document.removeEventListener("keydown", this.keybindHandler);
window.onclick = null;
Patcher.unpatchAll();
this.promises.cancel();
patches.forEach(patch=>patch());
}
//#endregion
//#region Styles
applyStyle()
{
const CompactVariables = `
:root {
--channelTabs-tabHeight: 22px;
--channelTabs-favHeight: 22px;
--channelTabs-tabNameFontSize: 12px;
--channelTabs-openTabSize: 18px;
}
`;
const CozyVariables = `
:root {
--channelTabs-tabHeight: 32px;
--channelTabs-favHeight: 28px;
--channelTabs-tabNameFontSize: 13px;
--channelTabs-openTabSize: 24px;
}
`;
const ConstantVariables = `
:root {
--channelTabs-tabWidth: 220px;
--channelTabs-tabWidthMin: ${this.settings.tabWidthMin}px;
}
`;
const PrivacyStyle = `
#app-mount .channelTabs-favGroupBtn {
color: transparent !important;
}
#app-mount .channelTabs-tabName {
color: transparent;
background-color: var(--interactive-normal);
opacity: 0.5;
}
#app-mount .channelTabs-selected .channelTabs-tabName {
background-color: var(--interactive-active);
}
#app-mount .channelTabs-favName {
color: transparent;
background-color: var(--interactive-normal);
opacity: 0.5;
}
`;
const RadialStatusStyle = `
.channelTabs-tabIconWrapper,
.channelTabs-favIconWrapper {
overflow: visible;
}
.channelTabs-tabIconWrapper img[src*="com/avatars/"],
.channelTabs-favIconWrapper img[src*="com/avatars/"] {
-webkit-clip-path: inset(1px round 50%);
clip-path: inset(2px round 50%);
}
.channelTabs-tabIconWrapper rect,
.channelTabs-favIconWrapper rect {
x: 0;
y: 0;
rx: 50%;
ry: 50%;
-webkit-mask: none;
mask: none;
fill: none;
height: 20px;
width: 20px;
stroke-width: 2px;
}
.channelTabs-onlineIcon {
stroke: hsl(139, calc(var(--saturation-factor, 1) * 47.3%), 43.9%);
}
.channelTabs-idleIcon {
stroke: hsl(38, calc(var(--saturation-factor, 1) * 95.7%), 54.1%);
}
.channelTabs-doNotDisturbIcon {
stroke: hsl(359, calc(var(--saturation-factor, 1) * 82.6%), 59.4%);
}
.channelTabs-offlineIcon {
stroke: hsl(214, calc(var(--saturation-factor, 1) * 9.9%), 50.4%);
}
`;
const tabNavStyle = `
.channelTabs-tabContainer .channelTabs-tabNav {
display:flex;
margin: 0 6px 3px 0;
}
.channelTabs-tabNavClose svg {
transform: scale(0.75);
}
.channelTabs-tabNavLeft svg,
.channelTabs-tabNavRight svg {
transform: scale(0.6);
}
/* if clickable */
.channelTabs-tabContainer .channelTabs-tabNav>div:hover {
color: var(--interactive-hover);
background-color: var(--background-modifier-hover);
}
.channelTabs-tabContainer .channelTabs-tabNav>div:active {
color: var(--interactive-active);
background-color: var(--background-modifier-active);
}
/* if only 1 tab */
.channelTabs-tabContainer[data-tab-count="1"] .channelTabs-tabNav>.channelTabs-tabNavClose {
color: var(--interactive-muted);
background: none;
}
.channelTabs-tabNav>div {
display: flex;
align-items: center;
justify-content: center;
height: var(--channelTabs-tabHeight);
width: 32px;
border-radius: 4px;
margin-right: 3px;
color: var(--interactive-normal);
}
`;
const BaseStyle = `
/*
//#region Tab Base/Container
*/
.channelTabs-tabNav {
display:none;
}
/*
//#macos
*/
.platform-osx .typeMacOS-3V4xXE {
position: relative;
width: 100%;
-webkit-app-region: drag;
}
.platform-osx .typeMacOS-3V4xXE>*,
.platform-osx .menu-1QACrS {
-webkit-app-region: no-drag;
}
.platform-osx .wrapper-1_HaEi {
margin-top: 0;
padding-top: 0;
}
html:not(.platform-win) .sidebar-1tnWFu {
border-radius: 8px 0 0;
overflow: hidden;
}
/*
//#endregion
*/
#channelTabs-container {
z-index: 1000;
padding: 4px 8px 1px 8px;
background: none;
}
.channelTabs-tabContainer {
display: flex;
align-items: center;
flex-wrap:wrap;
}
#channelTabs-container>:not(#channelTabs-settingsMenu)+div {
padding-top: 4px;
border-top: 1px solid var(--background-modifier-accent);
}
.channelTabs-tab {
display: flex;
align-items: center;
height: var(--channelTabs-tabHeight);
background: none;
border-radius: 4px;
max-width: var(--channelTabs-tabWidth);
min-width: var(--channelTabs-tabWidthMin);
flex: 1 1 var(--channelTabs-tabWidthMin);
margin-bottom: 3px;
}
.channelTabs-tab>div:first-child {
display: flex;
width: calc(100% - 16px);
align-items: center;
}
.channelTabs-tab:not(.channelTabs-selected):hover {
background: var(--background-modifier-hover);
}
.channelTabs-tab:not(.channelTabs-selected):active {
background: var(--background-modifier-active);
}
.channelTabs-tab.channelTabs-selected {
background: var(--background-modifier-selected);
}
.channelTabs-tab.channelTabs-unread:not(.channelTabs-selected),
.channelTabs-tab.channelTabs-unread:not(.channelTabs-selected),
.channelTabs-tab.channelTabs-mention:not(.channelTabs-selected) {
color: var(--interactive-hover);
}
.channelTabs-tab.channelTabs-unread:not(.channelTabs-selected):hover,
.channelTabs-tab.channelTabs-mention:not(.channelTabs-selected):hover {
color: var(--interactive-active);
}
/*
//#endregion
*/
/*
//#region Quick Settings
*/
html:not(.platform-win) #channelTabs-settingsMenu {
margin-right: 0;
}
#channelTabs-settingsMenu {
position: absolute;
right:0;
width: 20px;
height: 20px;
z-index: 1000;
}
#channelTabs-settingsMenu:hover {
background: var(--background-modifier-hover);
}
.channelTabs-settingsIcon {
max-width: 40px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
max-height: 40px;
}
/*
//#endregion
*/
/*
//#region Tab Name
*/
.channelTabs-tab .channelTabs-tabName {
margin-right: 6px;
font-size: var(--channelTabs-tabNameFontSize);
line-height: normal;
color: var(--interactive-normal);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.channelTabs-tab:not(.channelTabs-selected):hover .channelTabs-tabName {
color: var(--interactive-hover);
}
.channelTabs-tab:not(.channelTabs-selected):active .channelTabs-tabName,
.channelTabs-tab.channelTabs-selected .channelTabs-tabName {
color: var(--interactive-active);
}
/*
//#endregion
*/
/*
//#region Tab Icon
*/
.channelTabs-tabIcon {
height: 20px;
border-radius: 50%;
-webkit-user-drag: none;
}
.channelTabs-tabIconWrapper {
margin: 0 6px;
flex-shrink: 0;
}
.channelTabs-onlineIcon {
fill: hsl(139, calc(var(--saturation-factor, 1) * 47.3%), 43.9%);
mask: url(#svg-mask-status-online);
}
.channelTabs-idleIcon {
fill: hsl(38, calc(var(--saturation-factor, 1) * 95.7%), 54.1%);
mask: url(#svg-mask-status-idle);
}
.channelTabs-doNotDisturbIcon {
fill: hsl(359, calc(var(--saturation-factor, 1) * 82.6%), 59.4%);
mask: url(#svg-mask-status-dnd);
}
.channelTabs-offlineIcon {
fill: hsl(214, calc(var(--saturation-factor, 1) * 9.9%), 50.4%);
mask: url(#svg-mask-status-offline);
}
/*
//#endregion
*/
/*
//#region Close Tab / New Tab
*/
.channelTabs-closeTab {
position: relative;
height: 16px;
width: 16px;
flex-shrink: 0;
right: 6px;
border-radius: 4px;
color: var(--interactive-normal);
cursor: pointer;
}
.channelTabs-closeTab svg {
height: 100%;
width: 100%;
transform: scale(0.85);
}
.channelTabs-newTab {
display:flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
height: var(--channelTabs-openTabSize);
width: 24px;
margin: 0 6px 3px 6px;
border-radius: 4px;
cursor: pointer;
color: var(--interactive-normal);
}
.channelTabs-newTab:hover {
background: var(--background-modifier-hover);
color: var(--interactive-hover);
}
.channelTabs-newTab:active {
background: var(--background-modifier-active);
color: var(--interactive-active);
}
.channelTabs-closeTab:hover {
background: hsl(359,calc(var(--saturation-factor, 1)*82.6%),59.4%);
color: white;
}
/*
//#endregion
*/
/*
//#region Badges
*/
.channelTabs-gridContainer {
display: flex;
margin-right: 6px;
}
.channelTabs-mentionBadge,
.channelTabs-unreadBadge {
border-radius: 8px;
padding: 0 4px;
min-width: 8px;
width: fit-content;
height: 16px;
font-size: 12px;
line-height: 16px;
font-weight: 600;
text-align: center;
color: #fff;
}
.channelTabs-typingBadge {
border-radius: 8px;
padding-left: 4px;
padding-right: 4px;
min-width: 8px;
width: fit-content;
height: 16px;
font-size: 12px;
line-height: 16px;
font-weight: 600;
text-align: center;
color: #fff;
}
.channelTabs-mentionBadge {
background-color: hsl(359, calc(var(--saturation-factor, 1) * 82.6%), 59.4%);
}
.channelTabs-unreadBadge {
background-color: hsl(235, calc(var(--saturation-factor, 1) * 86%), 65%);
}
.channelTabs-classicBadgeAlignment {
margin-right: 6px;
display: inline-block;
float: right;
}
.channelTabs-badgeAlignLeft {
float: left;
}
.channelTabs-badgeAlignRight {
float: right;
}
.channelTabs-tab .channelTabs-mentionBadge,
.channelTabs-tab .channelTabs-unreadBadge,
.channelTabs-tab .channelTabs-typingBadge {
height: 16px;
}
.channelTabs-tab .channelTabs-noMention,
.channelTabs-tab .channelTabs-noUnread {
background-color: var(--background-primary);
color: var(--text-muted);
}
.channelTabs-fav .channelTabs-mentionBadge,
.channelTabs-fav .channelTabs-unreadBadge {
display: inline-block;
vertical-align: bottom;
float: right;
margin-left: 2px;
}
.channelTabs-fav .channelTabs-typingBadge {
display: inline-flex;
vertical-align: bottom;
float: right;
margin-left: 2px;
margin-right: 6px;
}
.channelTabs-fav .channelTabs-noMention,
.channelTabs-fav .channelTabs-noUnread {
background-color: var(--background-primary);
color: var(--text-muted);
}
.channelTabs-fav .channelTabs-noTyping {
display: none;
}
.channelTabs-fav .channelTabs-favName + div {
margin-left: 6px;
}
.channelTabs-favGroupBtn .channelTabs-noMention,
.channelTabs-favGroupBtn .channelTabs-noUnread {
background-color: var(--background-primary);
color: var(--text-muted);
}
.channelTabs-favGroupBtn .channelTabs-typingBadge {
display: inline-flex;
vertical-align: bottom;
float: right;
margin-left: 2px;
}
.channelTabs-favGroupBtn .channelTabs-mentionBadge,
.channelTabs-favGroupBtn .channelTabs-unreadBadge {
display: inline-block;
vertical-align: bottom;
float: right;
margin-left: 2px;
}
.channelTabs-favGroupBtn .channelTabs-noTyping {
display: none;
}
/*
//#endregion
*/
/*
//#region Favs
*/
.channelTabs-favContainer {
display: flex;
align-items: center;
flex-wrap:wrap;
}
.channelTabs-fav {
display: flex;
align-items: center;
min-width: 0;
border-radius: 4px;
height: var(--channelTabs-favHeight);
background: none;
flex: 0 0 1;
max-width: var(--channelTabs-tabWidth);
margin-bottom: 3px;
padding-left: 6px;
padding-right: 6px;
}
.channelTabs-fav:hover {
background: var(--background-modifier-hover);
}
.channelTabs-fav:active {
background: var(--background-modifier-active);
}
.channelTabs-favIcon {
height: 20px;
border-radius: 50%;
-webkit-user-drag: none;
}
.channelTabs-favName {
margin-left: 6px;
font-size: var(--channelTabs-tabNameFontSize);
line-height: normal;
color: var(--interactive-normal);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.channelTabs-fav:hover .channelTabs-favName {
color: var(--interactive-hover);
}
.channelTabs-fav:active .channelTabs-favName {
color: var(--interactive-active);
}
.channelTabs-noFavNotice {
color: var(--text-muted);
font-size: 14px;
padding: 3px;
}
/*
//#endregion
*/
/*
//#region Fav Folders
*/
.channelTabs-favGroupBtn {
display: flex;
align-items: center;
min-width: 0;
border-radius: 4px;
height: var(--channelTabs-favHeight);
flex: 0 1 1;
max-width: var(--channelTabs-tabWidth);
padding: 0 6px;
font-size: 12px;
color: var(--interactive-normal);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
margin-bottom: 3px;
}
.channelTabs-favGroupBtn>:first-child {
margin-left: 6px;
}
.channelTabs-favGroup:hover .channelTabs-favGroupBtn {
background: var(--background-modifier-hover);
}
.channelTabs-favGroup-content {
z-index: 1001;
display: none;
position: absolute;
min-width: max-content;
background-color: var(--background-floating);
-webkit-box-shadow: var(--elevation-high);
box-shadow: var(--elevation-high);
border-radius: 4px;
padding: 4px;
}
.channelTabs-favGroup-content>:last-child {
margin-bottom: 0;
}
.channelTabs-favGroupShow {
display:block;
}
.channelTabs-sliderContainer {
display: flex;
justify-content: center;
padding: 4px 8px;
margin: 2px 6px 12px 6px;
background: var(--slider-background-normal);
border-radius: var(--slider-background-radius);
}
.channelTabs-slider {
position: relative;
top: -14px;
}
.channelTabs-minimized {
--channelTabs-tabWidth: fit-content;
--channelTabs-tabWidthMin: fit-content;
}
.channelTabs-tab.channelTabs-minimized>div>:first-child~*,
.channelTabs-fav.channelTabs-minimized>svg:first-child~*,
.channelTabs-tab.channelTabs-minimized>.channelTabs-closeTab {
display:none;
}
/*
//#endregion
*/
`;
if (this.settings.compactStyle === true) PluginUtilities.addStyle("channelTabs-style-compact", CompactVariables);
if (this.settings.compactStyle === false) PluginUtilities.addStyle("channelTabs-style-cozy", CozyVariables);
if (this.settings.privacyMode === true) PluginUtilities.addStyle("channelTabs-style-private", PrivacyStyle);
if (this.settings.radialStatusMode === true) PluginUtilities.addStyle("channelTabs-style-radialstatus", RadialStatusStyle);
if (this.settings.showNavButtons === true) PluginUtilities.addStyle("channelTabs-style-tabnav", tabNavStyle);
PluginUtilities.addStyle("channelTabs-style-constants", ConstantVariables);
PluginUtilities.addStyle("channelTabs-style", BaseStyle);
}
removeStyle()
{
PluginUtilities.removeStyle("channelTabs-style-compact");
PluginUtilities.removeStyle("channelTabs-style-cozy");
PluginUtilities.removeStyle("channelTabs-style-private");
PluginUtilities.removeStyle("channelTabs-style-radialstatus");
PluginUtilities.removeStyle("channelTabs-style-tabnav");
PluginUtilities.removeStyle("channelTabs-style-constants");
PluginUtilities.removeStyle("channelTabs-style");
}
//#endregion
//#region Init/Default Functions
ifNoTabsExist()
{
if(this.settings.tabs.length == 0) this.settings.tabs = [{
name: getCurrentName(),
url: location.pathname,
selected: true,
iconUrl: getCurrentIconUrl()
}];
}
ifReopenLastChannelDefault()
{
if(this.settings.reopenLastChannel)
{
switching = true;
NavigationUtils.transitionTo((this.settings.tabs.find(tab=>tab.selected) || this.settings.tabs[0]).url);
switching = false;
}
}
//#endregion
//#region Patches
async patchAppView(promiseState)
{
const AppView = await ReactComponents.getComponent("Shakeable", ".app-2CXKsg");
if(promiseState.cancelled) return;
Patcher.after(AppView.component.prototype, "render", (thisObject, _, returnValue) => {
returnValue.props.children = [
React.createElement(TopBar, {
showTabBar: this.settings.showTabBar,
showFavBar: this.settings.showFavBar,
showFavUnreadBadges: this.settings.showFavUnreadBadges,
showFavMentionBadges: this.settings.showFavMentionBadges,
showFavTypingBadge: this.settings.showFavTypingBadge,
showEmptyFavBadges: this.settings.showEmptyFavBadges,
showTabUnreadBadges: this.settings.showTabUnreadBadges,
showTabMentionBadges: this.settings.showTabMentionBadges,
showTabTypingBadge: this.settings.showTabTypingBadge,
showEmptyTabBadges: this.settings.showEmptyTabBadges,
showActiveTabUnreadBadges: this.settings.showActiveTabUnreadBadges,
showActiveTabMentionBadges: this.settings.showActiveTabMentionBadges,
showActiveTabTypingBadge: this.settings.showActiveTabTypingBadge,
showEmptyActiveTabBadges: this.settings.showEmptyActiveTabBadges,
showFavGroupUnreadBadges: this.settings.showFavGroupUnreadBadges,
showFavGroupMentionBadges: this.settings.showFavGroupMentionBadges,
showFavGroupTypingBadge: this.settings.showFavGroupTypingBadge,
showEmptyFavGroupBadges: this.settings.showEmptyFavGroupBadges,
compactStyle: this.settings.compactStyle,
privacyMode: this.settings.privacyMode,
radialStatusMode: this.settings.radialStatusMode,
tabWidthMin: this.settings.tabWidthMin,
showQuickSettings: this.settings.showQuickSettings,
showNavButtons: this.settings.showNavButtons,
alwaysFocusNewTabs: this.settings.alwaysFocusNewTabs,
useStandardNav: this.settings.useStandardNav,
tabs: this.settings.tabs,
favs: this.settings.favs,
favGroups: this.settings.favGroups,
ref: TopBarRef,
plugin: this
}),
returnValue.props.children
].flat();
});
const forceUpdate = ()=>{
const { app } = WebpackModules.getByProps("app", "layers") || {};
const query = document.querySelector(`.${app}`);
if(query) ReactTools.getOwnerInstance(query)?.forceUpdate?.();
};
forceUpdate();
patches.push(()=>forceUpdate());
}
patchContextMenus()
{
patches.push(
ContextMenu.patch("channel-context", (returnValue, props) => {
if(!this.settings.showTabBar && !this.settings.showFavBar) return;
returnValue.props.children.push(CreateTextChannelContextMenuChildren(this, props));
}),
ContextMenu.patch("user-context", (returnValue, props) => {
if(!this.settings.showTabBar && !this.settings.showFavBar) return;
if(!returnValue) return;
if (!props.channel || props.channel.recipients.length !== 1 || props.channel.recipients[0] !== props.user.id) return;
returnValue.props.children.push(CreateDMContextMenuChildren(this, props));
}),
ContextMenu.patch("gdm-context", (returnValue, props) => {
if(!this.settings.showTabBar && !this.settings.showFavBar) return;
if(!returnValue) return;
returnValue.props.children.push(CreateGroupContextMenuChildren(this, props));
}),
ContextMenu.patch("guild-context", (returnValue, props) => {
if(!this.settings.showTabBar && !this.settings.showFavBar) return;
const channel = ChannelStore.getChannel(SelectedChannelStore.getChannelId(props.guild.id));
returnValue.props.children.push(CreateGuildContextMenuChildren(this, props, channel));
})
);
}
//#endregion
//#region Handlers
clickHandler(e)
{
if (!e.target.matches('.channelTabs-favGroupBtn')) {
closeAllDropdowns();
}
}
keybindHandler(e)
{
const keybinds = [
{altKey: false, ctrlKey: true, shiftKey: false, keyCode: 87 /*w*/, action: this.closeCurrentTab},
{altKey: false, ctrlKey: true, shiftKey: false, keyCode: 33 /*pg_up*/, action: this.previousTab},
{altKey: false, ctrlKey: true, shiftKey: false, keyCode: 34 /*pg_down*/, action: this.nextTab}
];
keybinds.forEach(keybind => {
if(e.altKey === keybind.altKey && e.ctrlKey === keybind.ctrlKey && e.shiftKey === keybind.shiftKey && e.keyCode === keybind.keyCode) keybind.action();
})
}
//#endregion
//#region General Functions
onSwitch(){
if(switching) return;
//console.log(this);
if(TopBarRef.current){
TopBarRef.current.setState({
tabs: TopBarRef.current.state.tabs.map(tab => {
if(tab.selected){
const channelId = SelectedChannelStore.getChannelId();
return {
name: getCurrentName(),
url: location.pathname,
selected: true,
currentStatus: getCurrentUserStatus(location.pathname),
iconUrl: getCurrentIconUrl(location.pathname),
channelId: channelId,
minimized: this.settings.tabs[this.settings.tabs.findIndex(tab=>tab.selected)].minimized
};
}else{
return Object.assign({}, tab);
}
})
}, this.saveSettings);
}else if(!this.settings.reopenLastChannel){
const channelId = SelectedChannelStore.getChannelId();
this.settings.tabs[this.settings.tabs.findIndex(tab=>tab.selected)] = {
name: getCurrentName(),
url: location.pathname,
selected: true,
currentStatus: getCurrentUserStatus(location.pathname),
iconUrl: getCurrentIconUrl(location.pathname),
channelId: channelId,
minimized: this.settings.tabs[this.settings.tabs.findIndex(tab=>tab.selected)].minimized
};
}
}
mergeItems(itemsTab, itemsFav){
const out = [];
if(this.settings.showTabBar) out.push(...itemsTab);
if(this.settings.showFavBar) out.push(...itemsFav);
return out;
}
//#endregion
//#region Hotkey Functions
nextTab(){
if(TopBarRef.current) TopBarRef.current.switchToTab((TopBarRef.current.state.selectedTabIndex + 1) % TopBarRef.current.state.tabs.length);
}
previousTab(){
if(TopBarRef.current) TopBarRef.current.switchToTab((TopBarRef.current.state.selectedTabIndex - 1 + TopBarRef.current.state.tabs.length) % TopBarRef.current.state.tabs.length);
}
closeCurrentTab(){
if(TopBarRef.current) TopBarRef.current.closeTab(TopBarRef.current.state.selectedTabIndex);
}
//#endregion
//#region Settings
get defaultVariables(){
return {
tabs: [],
favs: [],
favGroups: [],
showTabBar: true,
showFavBar: true,
reopenLastChannel: false,
showFavUnreadBadges: true,
showFavMentionBadges: true,
showFavTypingBadge: true,
showEmptyFavBadges: false,
showTabUnreadBadges: true,
showTabMentionBadges: true,
showTabTypingBadge: true,
showEmptyTabBadges: false,
showActiveTabUnreadBadges: false,
showActiveTabMentionBadges: false,
showActiveTabTypingBadge: false,
showEmptyActiveTabBadges: false,
compactStyle: false,
privacyMode: false,
radialStatusMode: false,
tabWidthMin: 100,
showFavGroupUnreadBadges: true,
showFavGroupMentionBadges: true,
showFavGroupTypingBadge: true,
showEmptyFavGroupBadges: false,
showQuickSettings: true,
showNavButtons: true,
alwaysFocusNewTabs: false,
useStandardNav: true
};
}
getSettingsPath(useOldLocation)
{
if (useOldLocation === true)
{
return this.getName();
}
else
{
const user_id = UserStore.getCurrentUser()?.id;
return this.getName() + "_new" + (user_id != null ? "_" + user_id : "");
}
}
loadSettings()
{
if (Object.keys(PluginUtilities.loadSettings(this.getSettingsPath())).length === 0)
{
this.settings = PluginUtilities.loadSettings(this.getSettingsPath(true), this.defaultVariables);
}
else
{
this.settings = PluginUtilities.loadSettings(this.getSettingsPath(), this.defaultVariables);
}
this.settings.favs = this.settings.favs.map(fav => {
if(fav.channelId === undefined){
const match = fav.url.match(/^\/channels\/[^\/]+\/(\d+)$/);
if(match) return Object.assign(fav, {channelId: match[1]});
}
if (fav.groupId === undefined)
{
return Object.assign(fav, {groupId: -1});
}
return fav;
});
this.saveSettings();
}
saveSettings(){
if(TopBarRef.current){
this.settings.tabs = TopBarRef.current.state.tabs;
this.settings.favs = TopBarRef.current.state.favs;
this.settings.favGroups = TopBarRef.current.state.favGroups;
}
PluginUtilities.saveSettings(this.getSettingsPath(), this.settings);
}
getSettingsPanel(){
const panel = document.createElement("div");
panel.className = "form";
panel.style = "width:100%;";
//#region Startup Settings
new Settings.SettingGroup("Startup Settings", {shown: true}).appendTo(panel)
.append(new Settings.Switch("Reopen last channel", "When starting the plugin (or discord) the channel will be selected again instead of the friends page", this.settings.reopenLastChannel, checked=>{
this.settings.reopenLastChannel = checked;
this.saveSettings();
}));
//#endregion
//#region General Appearance
new Settings.SettingGroup("General Appearance").appendTo(panel)
.append(new Settings.Switch("Show Tab Bar", "Allows you to have multiple tabs like in a web browser", this.settings.showTabBar, checked=>{
this.settings.showTabBar = checked;
if(TopBarRef.current) TopBarRef.current.setState({
showTabBar: checked
});
this.saveSettings();
}))
.append(new Settings.Switch("Show Fav Bar", "Allows you to add favorites by right clicking a tab or the fav bar", this.settings.showFavBar, checked=>{
this.settings.showFavBar = checked;
if(TopBarRef.current) TopBarRef.current.setState({
showFavBar: checked
});
this.saveSettings();
}))
.append(new Settings.Switch("Show Quick Settings", "Allows you to quickly change major settings from a context menu", this.settings.showQuickSettings, checked=>{
this.settings.showQuickSettings = checked;
if(TopBarRef.current) TopBarRef.current.setState({
showQuickSettings: checked
});
this.saveSettings();
}))
.append(new Settings.Switch("Show Navigation Buttons", "Click to go the left or right tab, this behavior can be changed in Behavior settings", this.settings.showNavButtons, checked=>{
this.settings.showNavButtons = checked;
if(TopBarRef.current) TopBarRef.current.setState({
showNavButtons: checked
});
this.removeStyle();
this.applyStyle();
this.saveSettings();
}))
.append(new Settings.Switch("Use Compact Look", "", this.settings.compactStyle, checked=>{
this.settings.compactStyle = checked;
if(TopBarRef.current) TopBarRef.current.setState({
compactStyle: checked
});
this.removeStyle();
this.applyStyle();
this.saveSettings();
}))
.append(new Settings.Switch("Enable Privacy Mode", "Obfusicates all the Sensitive Text in ChannelTabs", this.settings.privacyMode, checked=>{
this.settings.privacyMode = checked;
if(TopBarRef.current) TopBarRef.current.setState({
privacyMode: checked
});
this.removeStyle();
this.applyStyle();
this.saveSettings();
}))
.append(new Settings.Switch("Use Radial Status Indicators", "Changes the status indicator into a circular border", this.settings.radialStatusMode, checked=>{
this.settings.radialStatusMode = checked;
if(TopBarRef.current) TopBarRef.current.setState({
radialStatusMode: checked
});
this.removeStyle();
this.applyStyle();
this.saveSettings();
}))
.append(new Settings.Slider("Minimum Tab Width", "Set the limit on how small a tab can be before overflowing to a new row",
58, 220,
this.settings.tabWidthMin,
value => (
this.settings.tabWidthMin = Math.round(value),
this.saveSettings(),
document.documentElement.style.setProperty("--channelTabs-tabWidthMin", this.settings.tabWidthMin + "px")
),
{
defaultValue: 100,
markers: [60, 85, 100, 125, 150, 175, 200, 220],
units: 'px'
}
));
//#endregion
//#region Behavior Settings
new Settings.SettingGroup("Behavior").appendTo(panel)
.append(new Settings.Switch("Always Auto Focus New Tabs", "Forces all newly created tabs to bring themselves to focus", this.settings.alwaysFocusNewTabs, checked=>{
this.settings.alwaysFocusNewTabs = checked;
if(TopBarRef.current) TopBarRef.current.setState({
alwaysFocusNewTabs: checked
});
this.saveSettings();
}))
.append(new Settings.Switch("Primary Forward/Back Navigation", "Instead of scrolling down the row, use the previous and next buttons to navigate between pages", this.settings.useStandardNav, checked=>{
this.settings.useStandardNav = checked;
if(TopBarRef.current) TopBarRef.current.setState({
useStandardNav: checked
});
this.saveSettings();
}));
//#endregion
//#region Badge Visibility - Favs
new Settings.SettingGroup("Badge Visibility - Favorites").appendTo(panel)
.append(new Settings.Switch("Show Unread", "", this.settings.showFavUnreadBadges, checked=>{
this.settings.showFavUnreadBadges = checked;
if(TopBarRef.current) TopBarRef.current.setState({
showFavUnreadBadges: checked
});
this.saveSettings();
}))
.append(new Settings.Switch("Show Mentions", "", this.settings.showFavMentionBadges, checked=>{
this.settings.showFavMentionBadges = checked;
if(TopBarRef.current) TopBarRef.current.setState({
showFavMentionBadges: checked
});
this.saveSettings();
}))
.append(new Settings.Switch("Show Typing", "", this.settings.showFavTypingBadge, checked=>{
this.settings.showFavTypingBadge = checked;
if(TopBarRef.current) TopBarRef.current.setState({
showFavTypingBadge: checked
});
this.saveSettings();
}))
.append(new Settings.Switch("Show Empty", "", this.settings.showEmptyFavBadges, checked=>{
this.settings.showEmptyFavBadges = checked;
if(TopBarRef.current) TopBarRef.current.setState({
showEmptyFavBadges: checked
});
this.saveSettings();
}));
//#endregion
//#region Badge Visibility - Fav Groups
new Settings.SettingGroup("Badge Visibility - Favorite Groups").appendTo(panel)
.append(new Settings.Switch("Show Unread", "", this.settings.showFavGroupUnreadBadges, checked=>{
this.settings.showFavGroupUnreadBadges = checked;
if(TopBarRef.current) TopBarRef.current.setState({
showFavGroupUnreadBadges: checked
});
this.saveSettings();
}))
.append(new Settings.Switch("Show Mentions", "", this.settings.showFavGroupMentionBadges, checked=>{
this.settings.showFavGroupMentionBadges = checked;
if(TopBarRef.current) TopBarRef.current.setState({
showFavGroupMentionBadges: checked
});
this.saveSettings();
}))
.append(new Settings.Switch("Show Typing", "", this.settings.showFavGroupTypingBadge, checked=>{
this.settings.showFavGroupTypingBadge = checked;
if(TopBarRef.current) TopBarRef.current.setState({
showFavGroupTypingBadge: checked
});
this.saveSettings();
}))
.append(new Settings.Switch("Show Empty", "", this.settings.showEmptyGroupFavBadges, checked=>{
this.settings.showEmptyGroupFavBadges = checked;
if(TopBarRef.current) TopBarRef.current.setState({
showEmptyGroupFavBadges: checked
});
this.saveSettings();
}));
//#endregion
//#region Badge Visibility - Tabs
new Settings.SettingGroup("Badge Visibility - Tabs").appendTo(panel)
.append(new Settings.Switch("Show Unread", "", this.settings.showTabUnreadBadges, checked=>{
this.settings.showTabUnreadBadges = checked;
if(TopBarRef.current) TopBarRef.current.setState({
showTabUnreadBadges: checked
});
this.saveSettings();
}))
.append(new Settings.Switch("Show Mentions", "", this.settings.showTabMentionBadges, checked=>{
this.settings.showTabMentionBadges = checked;
if(TopBarRef.current) TopBarRef.current.setState({
showTabMentionBadges: checked
});
this.saveSettings();
}))
.append(new Settings.Switch("Show Typing", "", this.settings.showTabTypingBadge, checked=>{
this.settings.showTabTypingBadge = checked;
if(TopBarRef.current) TopBarRef.current.setState({
showTabTypingBadge: checked
});
this.saveSettings();
}))
.append(new Settings.Switch("Show Empty", "", this.settings.showEmptyTabBadges, checked=>{
this.settings.showEmptyTabBadges = checked;
if(TopBarRef.current) TopBarRef.current.setState({
showEmptyTabBadges: checked
});
this.saveSettings();
}));
//#endregion
//#region Badge Visibility - Active Tabs
new Settings.SettingGroup("Badge Visibility - Active Tabs").appendTo(panel)
.append(new Settings.Switch("Show Unread", "", this.settings.showActiveTabUnreadBadges, checked=>{
this.settings.showActiveTabUnreadBadges = checked;
if(TopBarRef.current) TopBarRef.current.setState({
showActiveTabUnreadBadges: checked
});
this.saveSettings();
}))
.append(new Settings.Switch("Show Mentions", "", this.settings.showActiveTabMentionBadges, checked=>{
this.settings.showActiveTabMentionBadges = checked;
if(TopBarRef.current) TopBarRef.current.setState({
showActiveTabMentionBadges: checked
});
this.saveSettings();
}))
.append(new Settings.Switch("Show Typing", "", this.settings.showActiveTabTypingBadge, checked=>{
this.settings.showActiveTabTypingBadge = checked;
if(TopBarRef.current) TopBarRef.current.setState({
showActiveTabTypingBadge: checked
});
this.saveSettings();
}))
.append(new Settings.Switch("Show Empty", "", this.settings.showEmptyActiveTabBadges, checked=>{
this.settings.showEmptyActiveTabBadges = checked;
if(TopBarRef.current) TopBarRef.current.setState({
showEmptyActiveTabBadges: checked
});
this.saveSettings();
}));
//#endregion
return panel;
}
//#endregion
}
//#endregion
};
return plugin(Plugin, Api);
})(global.ZeresPluginLibrary.buildPlugin(config));
})();
@mrmiller110586
Copy link

Hello I am writing you to see if you can help me. I love the channel tabs I use this for work but when I turn on the channel tabs plugin it disables the screen and an error message pops up. I can still hear but cannot see any part of my discord. If I got to better discord and open via repair all of the plugins are working fine except for my beloved channel tabs. Is there any way you could look into this or could maybe help with a fix. thanks

@MirTeiwaz
Copy link

ShowFabTypingBadge: YES gives me an instant crash. Think this still requires fixing.

@kkanoee
Copy link

kkanoee commented Feb 24, 2023

GM guys. If anyone got a fix, save us. Thank you

@dabenzel
Copy link

dabenzel commented Mar 4, 2023

If you are looking for a non-crash version ==new Repo==> https://github.com/samfundev/BetterDiscordStuff

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment