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));
})();
@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