├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── CHANGELOG.md ├── CollapsibleUI.plugin.js ├── LICENSE ├── README.md └── eslint.config.mjs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: programmer2514 2 | patreon: BenjaminPryor 3 | ko_fi: benjaminpryor 4 | custom: [ 5 | "https://paypal.me/BenjaminJPryor" 6 | ] 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. Windows 10, Linux Mint] 28 | - Discord Version: [e.g. Stable, PTB, Canary] 29 | - Enabled Theme(s): [e.g. ClearVision, HorizontalServerList] 30 | - (OPTIONAL) Enabled Plugins: [e.g. BDFDB, CallTimeCounter, GameActivityToggle] 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | 3 | node_modules/ 4 | package-lock.json 5 | package.json 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CollapsibleUI Patch Notes 2 | 3 | ### v12.2.4: 4 | * Fixed user area being cut off 5 | * Fixed a small visual glitch caused by collapsing the channel list 6 | * Improved UIRefreshRefresh compatibility 7 | 8 | ### v12.2.3: 9 | * Fixed user profile compatibility issue with FullscreenToggle plugin 10 | 11 | ### v12.2.2: 12 | * Fixed leftover spacing when collapsing button groups 13 | 14 | ### v12.2.1: 15 | * Fixed a tiny visual inconsistency 16 | 17 | ### v12.2.0: 18 | * Added UIRefreshRefresh compatibility 19 | * Fixed visual glitch when collapsing forum popout 20 | 21 | ### v12.1.1: 22 | * Prevented hiding members list/user profile buttons while they are collapsed 23 | * Fixed crash when trying to collapse all toolbar buttons 24 | * Fixed size collapse not working on some elements 25 | * Fixed several minor styling issues 26 | 27 | ### v12.1.0: 28 | * Fixed for latest Discord version 29 | * Fixed user settings overlapping user profile picture when channel list is collapsed 30 | * Fixed Horizontal Server List compatibility 31 | * Removed static class references 32 | 33 | ### v12.0.0: 34 | * Updated to work with Discord UI refresh (no longer works on old Discord UI) 35 | * Fixed call container buttons being cut off when window is too small 36 | * More visual tweaks for UI consistency 37 | * Removed obsolete advanced settings 38 | 39 | ### v11.0.2: 40 | * Added FullscreenToggle compatibility 41 | 42 | ### v11.0.1: 43 | * Fixed cut-off Message Input on some themes 44 | * Fixed Message Input buttons not being spaced correctly with send message button enabled 45 | * Fixed emoji suggestions not showing in Message Input 46 | * Fixed incorrect Members List styling 47 | * Fixed Channel List offset while in fullscreen calls 48 | * Fixed incompatibility with GameActivityToggle and InvisibleTyping 49 | * Fixed numerical settings being incorrectly stored as strings 50 | 51 | ### v11.0.0: 52 | * Completely re-wrote plugin from the ground up for performance and stability 53 | * Greatly increased plugin\'s overall performance 54 | * Greatly decreased plugin\'s size on disk 55 | * Switched away from direct DOM manipulation wherever possible 56 | * Refactored style routines to reduce reliance on MutationObservers 57 | * Plugin now caches styles, settings, and webpack modules to decrease load times 58 | * Settings update routines have been changed to reduce the number of disk writes 59 | * Keyboard shortcuts can now be whatever you want and are not limited to standard patterns 60 | * Size Collapse has been rewritten using media queries and now does not affect button states 61 | * Expand on Hover is no longer a requirement for Size Collapse (though it is still recommended) 62 | * Resizable panels can now be resized by clicking-and-dragging anywhere on the edge of the panel 63 | * The activities panel, search panel, and forum popout can now be resized and collapsed 64 | * The floating panels setting has been reworked for increased customizability 65 | * Message bar is now capable of floating above other UI elements 66 | * Improved out-of-the-box compatibility with other plugins and themes 67 | * Hovered panels will no longer collapse while a right-click/popup menu is open 68 | * Removed locale labels other than English due to inaccurate translations 69 | * Moved Unread DMs Badge feature to its own plugin 70 | * Updated settings layout and added several new options 71 | * Several visual tweaks for UI consistency 72 | * Fixed showing multiple update notifications if plugin is toggled without reloading Discord 73 | * Fixed inconsistent Size Collapse when snapping window dimensions in Windows 74 | * Fixed panels jumping open during transitions on some low-end devices 75 | * Fixed forum popup resizing inconsistently with other UI elements 76 | 77 | ### v10.0.1: 78 | * Fixed settings failing to apply immediately when set 79 | 80 | ### v10.0.0: 81 | * Added the ability to resize the search panel 82 | * Added the ability to resize the forum popout 83 | * Fixed boolean settings being stored as strings 84 | * Fixed server voice channel being detected as call window 85 | * Fixed forum channels being cut off when channel/members lists are resized too wide 86 | 87 | ### v9.1.4: 88 | * Switched to BdApi for stylesheet handling 89 | * Fixed profile effects and empty members list displaying backwards 90 | * Improved compatibility with DateViewer, MemberCount, and HorizontalServerList 91 | 92 | ### v9.1.3: 93 | * Fixed profile panel failing to collapse on latest Discord update 94 | 95 | ### v9.1.2: 96 | * Fixed plugin failing to load when profile panel is unloaded 97 | 98 | ### v9.1.1: 99 | * Hotfix for newest Discord release 100 | * Fix for ImageUtilities compatibility 101 | 102 | ### v9.1.0: 103 | * Fix for newest Discord release (breaks plugin on Discord versions <360320) 104 | * Message bar no longer collapses when uploading attachments or using the emoji picker 105 | 106 | ### v9.0.2: 107 | * Fixed plugin crashing on MacOS 108 | * Fixed plugin failing to load in forum channels 109 | * Added Quests compatibility 110 | 111 | ### v9.0.0: 112 | * Plugin no longer depends on ZeresPluginLibrary 113 | * Greatly increased robustness against breaking with Discord updates 114 | * Reworked plugin settings and changed defaults 115 | * Decreased plugin size and increased runtime speed 116 | * Updated translations for increased accuracy and consistency 117 | * Added compatibility for MemberCount and CompleteTimestamps 118 | * Fixed channel list failing to collapse/resize 119 | * Fixed VC buttons shrinking when channel list is resized 120 | 121 | ### v8.5.0: 122 | * Fixed profile panel and settings buttons on latest update 123 | * Fixed plugin failing to load in thread channels 124 | 125 | ### v8.4.9: 126 | * Fixed Members List occasionally reversing itself 127 | 128 | ### v8.4.8: 129 | * Hotfix for newest Discord release (breaks plugin on Discord versions <325120) 130 | 131 | ### v8.4.7: 132 | * Fixed grey bar at bottom of screen when zooming out in a fullscreen/server call 133 | 134 | ### v8.4.6: 135 | * Hotfix for newest Discord release (breaks plugin on Discord versions <322979) 136 | 137 | ### v8.4.5: 138 | * Hotfix for newest Discord release (breaks plugin on Discord versions <317617) 139 | 140 | ### v8.4.4: 141 | * Hotfix for newest Discord release (breaks plugin on Discord versions <315866) 142 | 143 | ### v8.4.3: 144 | * Fixed unnecessary console spam 145 | * Updated outdated code 146 | 147 | ### v8.4.2: 148 | * Fixed dynamically uncollapsed elements collapsing when switching servers/channels 149 | * Fixed profile panel briefly jumping open when switching DMs 150 | 151 | ### v8.4.1: 152 | * Hotfix for newest Discord release (breaks plugin on Discord versions <309513) 153 | 154 | ### v8.4.0: 155 | * Hotfix for newest Discord release (breaks plugin on Discord versions <304187) 156 | * Fixed some elements of new profiles being shown reversed 157 | * Fixed call container not filling available space while in fullscreen 158 | * Standardized code formatting using ESLint + Stylistic 159 | * Updated deprecated code 160 | 161 | ### v8.3.2: 162 | * Fixed cut-off message bar buttons in discord PTB and Canary 163 | 164 | ### v8.3.1: 165 | * Fixed broken user profile button in latest Discord update 166 | 167 | ### v8.3.0: 168 | * Hotfix for newest Discord release (breaks plugin on Discord versions <279687) 169 | 170 | ### v8.2.0: 171 | * Updated Russian translation - Thank you @vanja-san 172 | * Fixed plugin not working in forum channels 173 | * Patched a Discord bug with forum channels being cut off by Members List 174 | * Minor code changes 175 | 176 | ### v8.1.2: 177 | * Hopefully fixed (or at least improved) Members List briefly jumping open on channel switch 178 | * More performance improvements 179 | 180 | ### v8.1.1: 181 | * Fixed floating Server List not properly filling vertical space 182 | 183 | ### v8.1.0: 184 | * Fix for even more Discord class/element changes 185 | * The persistent DM badge is now disabled by default 186 | * Fixed Members List offset issue when UI transitions are disabled 187 | * Improved load times and removed unnecessary resource utilization 188 | * Floating Dynamic Uncollapse will now be disabled if Collapsed Element Distance is not equal to 0 189 | 190 | ### v8.0.0: 191 | * Fix for more Discord class/element changes 192 | * Fixed elements occasionally collapsing on scroll 193 | * Made User Profile panel resizable 194 | * Updated plugin icons to match Discord's new theme more closely 195 | * Made uncollapsed elements float instead of shifting other elements over 196 | * Added a persistent badge in the top-left showing a total of unread DMs 197 | 198 | ### v7.4.2: 199 | * Fix for recent Discord sweeping classes/elements changes 200 | 201 | ### v7.4.1: 202 | * Hotfix for newest Discord release (breaks plugin on Discord versions <238110) 203 | * Fixed Call Container button appearing when it shouldn\'t 204 | * Improved compatibility with certain themes 205 | 206 | ### v7.4.0: 207 | * Updated to support newest Discord release (breaks plugin on Discord versions <237546) 208 | * Bumped settings version and cleaned up old JSON 209 | * Fixed Members List not resizing correctly in GCs 210 | * Fixed broken transitions 211 | * Added DateViewer compatibility 212 | 213 | ### v7.3.0: 214 | * Fixed dynamic handling of user profile popout 215 | * Made members list resizable 216 | * Reformatted plugin code 217 | 218 | ### v7.2.4: 219 | * Fixed plugin reloading when user presses shift while hovering the mouse over certain messages 220 | * Updated classes to work with the new themed user profiles 221 | 222 | ### v7.2.3: 223 | * Fixed plugin occasionally failing to reload when switching channels/accounts 224 | * Clarified autocollapse dependencies 225 | 226 | ### v7.2.2: 227 | * Fixed residual styles on message bar buttons on plugin termination/reload 228 | * Added donation links 229 | 230 | ### v7.2.1: 231 | * Reworked code formatting to reduce size and increase readability 232 | 233 | ### v7.2.0: 234 | * Added a fudge factor to collapsible button panels (should decrease jitter) 235 | * Introduced additional advanced settings to control the message bar buttons 236 | * Collapsible button panels are now bound to dynamic uncollapse and its corresponding constraints 237 | 238 | ### v7.1.2: 239 | * Added an outdated plugin warning for future updates 240 | 241 | ### v7.1.1: 242 | * Made message bar buttons collapsible 243 | 244 | ### v7.1.0: 245 | * Fixed plugin crashing Discord when the inspect tool is opened 246 | * Updated to support newest Discord release (breaks plugin on Discord versions <177136) 247 | 248 | ### v7.0.6: 249 | * Removed unnecessary channel resize handle due to buggy rendering 250 | * Fixed channel list with a custom width stuttering when switching channels 251 | * Improved responsiveness of channel list resizing 252 | 253 | ### v7.0.5: 254 | * Fixed unnecessary blank space in server call area 255 | 256 | ### v7.0.4: 257 | * Fixed call area not maintaining custom size when switching between channels 258 | * Fixed channel list expanding unexpectedly on UI refresh if set to a custom width 259 | 260 | ### v7.0.3: 261 | * Fixed message bar autocollapsing when it is not supposed to 262 | 263 | ### v7.0.2: 264 | * Prevented message bar autocollapsing while user is actively typing a message 265 | 266 | ### v7.0.1: 267 | * Fixed full toolbar autocollapse functionality 268 | 269 | ### v7.0.0 270 | * Added full support for User Profile popout 271 | * Completely refactored all plugin code 272 | 273 | ### v6.6.1: 274 | * Tweaked user profile toolbar icon 275 | * Fixed yet another BDFDB incompatibility issue 276 | 277 | ### v6.6.0: 278 | * Separated collapsed states of members list and user profile 279 | * Clarified several settings options 280 | * Changed plugin startup method to work with Canary 281 | * Fixed plugin not loading immediately when first enabled 282 | 283 | ### v6.5.2: 284 | * Fixed several overflow errors with user area and channel list 285 | 286 | ### v6.5.1: 287 | * Fixed minor issues with User Profile panel 288 | * Prevented plugin rarely making server list completely inaccessible 289 | * Removed accidental console spam 290 | * Condensed changelog to save space 291 | 292 | ### v6.5.0: 293 | * Added support for the new User Profile panel 294 | 295 | ### v6.4.2: 296 | * Improved plugin persistence (again) 297 | * Fixed SplitLargeMessages incompatibility 298 | * Cleaned up and refactored some code 299 | 300 | ### v6.4.1: 301 | * Cleaned up method of getting plugin instance 302 | * Fixed scrollbar issue when collapsing user area 303 | * Reworked method for removing Discord's default Members List button 304 | 305 | ### v6.4.0: 306 | * Reduced number of mutationObservers 307 | * Changed plugin persistence algorithm (should be much more consistent) 308 | * Compatibility fix for ServerFolders 309 | * Introduced new conditional autocollapse option 310 | * Removed several "magic numbers" to improve code readability 311 | 312 | ### v6.3.2: 313 | * Fixed members list not collapsing after a message search 314 | 315 | ### v6.3.1: 316 | * Fixed patch notes 317 | 318 | ### v6.3.0: 319 | * Fixed channel list margin remaining in fullscreen mode 320 | * Fixed Members List button functionality in forum channels 321 | * Fixed rare issue where tooltips would get stuck open 322 | * Increased fidelity of toolbar insertion 323 | * Overhauled dynamic uncollapse settings (reset dynamic uncollapse distance) 324 | 325 | ### v6.2.0: 326 | * Fixed resizable channel list on latest update 327 | * Fixed plugin failing to load on initial app startup 328 | * Prevented members list from auto-collapsing when user popout is open 329 | 330 | ### v6.1.3: 331 | * Fixed settings buttons not collapsing when a non-focused user sends a DM 332 | * Removed some pointless console spam 333 | 334 | ### v6.1.2: 335 | * Fixed keyboard shortcuts messing with non-english keyboard layouts 336 | 337 | ### v6.1.1: 338 | * Fixed several issues introduced in v6.1.0 339 | * Rewrote toolbar insertion code to play better with other plugins 340 | * Fixed BDFDB updates causing plugin to break 341 | * Added support for new Discord forum layout 342 | 343 | ### v6.1.0: 344 | * Replaced all intervals with mutation observers 345 | * Removed reliance on ZeresPluginLibrary logger modification 346 | * Changed inefficient startup behavior 347 | * Fixed crash caused by BDFDB's stupidity 348 | * Plugin should now comply with updated code guidelines 349 | 350 | ### v6.0.1: 351 | * Minor tweaks to code and plugin description 352 | 353 | ### v6.0.0: 354 | * Added customizable keybinds to all actions 355 | * Added ability to auto-collapse elements based on size of Discord window 356 | * Added better compatibility with DevilBro's plugins 357 | * Greatly increased plugin persistence 358 | * Fixed plugin not starting after another plugin is updated 359 | * Significantly refactored code for better modularity 360 | * Removed unnecessary plugin reloading when changing settings 361 | * Removed unnecessary console clutter 362 | 363 | ### v5.7.2: 364 | * Fixed HSL integration 365 | 366 | ### v5.7.1: 367 | * Decreased initial plugin loading time 368 | * Fixed incorrect logic causing elements to not remain collapsed 369 | 370 | ### v5.7.0: 371 | * Added settings option to change behavior of collapsed elements when their corresponding button is disabled 372 | * Greatly improved plugin loading times and stability 373 | 374 | ### v5.6.2: 375 | * Fixed plugin not loading during video call/screenshare when chat is hidden 376 | 377 | ### v5.6.1: 378 | * Improved channel list fix (it actually works now) 379 | 380 | ### v5.6.0: 381 | * Fixed settings button alignment glitch 382 | * Fixed incorrect handling of disabled toolbar buttons 383 | * Fixed channel list being able to resize beyond window limits 384 | 385 | ### v5.5.1: 386 | * Tweaked more settings to improve reactability (resets uncollapse distance and delay) 387 | * Clarified and moved a settings option to make it more usable 388 | 389 | ### v5.5.0: 390 | * Fixed toolbar not initializing while in a call 391 | * Added minimal uncollapse delay, which should improve usability 392 | * Tweaked settings and fixed corruption issue (resets all settings to default) 393 | 394 | ### v5.4.4: 395 | * Fixed user area misalignment with transitions disabled 396 | * Added ChannelDMs compatibility 397 | 398 | ### v5.4.3: 399 | * Fixed settings bar alignment glitch 400 | 401 | ### v5.4.2: 402 | * Fixed visual window bar glitch 403 | 404 | ### v5.4.1: 405 | * Fixed minor syntax errors 406 | 407 | ### v5.4.0: 408 | * Added out-of-the-box compatibility and overrides for Dark Matter theme 409 | * Implemented compatibility fix between Dark Matter and Horizontal Server List 410 | 411 | ### v5.3.0: 412 | * Added additional checks for collapsible objects 413 | 414 | ### v5.2.4: 415 | * Suppressed false code security errors 416 | 417 | ### v5.2.3: 418 | * Fixed unintentional console spam 419 | 420 | ### v5.2.2: 421 | * Fixed incorrect settings indices for Selective Dynamic Uncollapse 422 | 423 | ### v5.2.1: 424 | * Fixed plugin failing to load if a collapsed element does not exist 425 | 426 | ### v5.2.0: 427 | * Fixed plugin breaking on GNU/Linux 428 | 429 | ### v5.1.6: 430 | * Cleaned up code 431 | 432 | ### v5.1.5: 433 | * Stopped relying on aria labels for tooltips 434 | 435 | ### v5.1.4: 436 | * Fixed minor security vulnerability with tooltips 437 | 438 | ### v5.1.3: 439 | * Added KeywordTracker compatibility 440 | 441 | ### v5.1.2: 442 | * Fixed elements not collapsing when their respective button is hidden 443 | 444 | ### v5.1.1: 445 | * Reworked toolbar insertion 446 | 447 | ### v5.1.0: 448 | * Added OldTitleBar compatibility 449 | 450 | ### v5.0.2: 451 | * Fixed more call container issues 452 | 453 | ### v5.0.1: 454 | * Fixed call container issues 455 | 456 | ### v5.0.0: 457 | * Decreased number of writes to the config file 458 | * Fixed plugin animations and events while on a call 459 | * Added ability to reset channel list size to default 460 | * Added ability to selectively enable Dynamic Uncollapse 461 | * Added option to make vanilla Discord toolbar collapsible as well as CollapsibleUI's 462 | 463 | ### v4.4.2: 464 | * Fixed channel list not collapsing 465 | 466 | ### v4.4.1: 467 | * Fixed minor channel list styling error 468 | 469 | ### v4.4.0: 470 | * Made channel list resize state persistent 471 | 472 | ### v4.3.1: 473 | * Fixed small tooltip error 474 | 475 | ### v4.3.0: 476 | * Added language localization 477 | 478 | ### v4.2.0: 479 | * Added new advanced option to leave elements partially uncollapsed 480 | 481 | ### v4.1.4: 482 | * Fixed collapsing call container hiding the toolbar 483 | 484 | ### v4.1.3: 485 | * Fixed user area not fully uncollapsing while in a call 486 | 487 | ### v4.1.2: 488 | * Fixed dynamic enabling of Horizontal Server List 489 | * Added startup/shutdown logging 490 | 491 | ### v4.1.1: 492 | * Finished implementing Horizontal Server List support 493 | * Default settings tweaks (reset dynamic uncollapse distance) 494 | 495 | ### v4.1.0: 496 | * Implemented rudimentary Horizontal Server List support 497 | 498 | ### v4.0.7: 499 | * Used more robust message bar hover detection (fixes some themes) 500 | 501 | ### v4.0.6: 502 | * Prevented sidebars from uncollapsing while hovering over message bar 503 | 504 | ### v4.0.5: 505 | * Fixed window bar dynamic uncollapse 506 | 507 | ### v4.0.4: 508 | * Fixed dynamic uncollapse 509 | * Fixed tooltips not showing 510 | 511 | ### v4.0.3: 512 | * Fixed settings collapse malfunction when in a voice call 513 | * Disabled call area buttons collapsing via settings collapse 514 | 515 | ### v4.0.2: 516 | * Fixed UI elements not collapsing on mouse leaving the window 517 | 518 | ### v4.0.1: 519 | * Fixed patch notes 520 | 521 | ### v4.0.0: 522 | * Added settings panel 523 | * Small animation tweaks 524 | * Added dynamic uncollapse feature 525 | * Made call container collapsible 526 | * With settings collapse enabled, now collapses call area buttons correctly 527 | * Fixed a lot of bugs 528 | 529 | ### v3.0.1: 530 | * Fixed BetterDiscord repo integration 531 | 532 | ### v3.0.0: 533 | * Added GNU/Linux support 534 | * Added theme support 535 | * Added thread support 536 | * Made channel list resizable 537 | * Added collapsible button panel feature 538 | * Added settings options in JSON file for advanced tweaking 539 | * Fixed styles on new Discord update 540 | * Fixed many, many bugs 541 | 542 | ### v2.1.1: 543 | * Fixed plugin auto-update 544 | 545 | ### v2.1.0: 546 | * Added ZeresPluginLibrary support 547 | 548 | ### v2.0.1: 549 | * Adjusted some pixel measurements to prevent cutting off the message bar while typing multiline messages 550 | 551 | ### v2.0.0: 552 | * Added a button to collapse the window title bar 553 | * Updated the button icons to be more consistent 554 | * Finished adding transitions to collapsible elements 555 | * Fixed issues with persistent button states 556 | * Actually fixed plugin crashing on reload 557 | * Fixed handling of plugin being disabled 558 | 559 | ### v1.2.1: 560 | * Improved support for non-english locales 561 | * Improved handling of missing config 562 | 563 | ### v1.2.0: 564 | * Added a button to collapse the message bar 565 | * Added transitions to some elements 566 | 567 | ### v1.1.0: 568 | * Added persistent button states 569 | * Fixed plugin crashing on reload 570 | 571 | ### v1.0.0: 572 | * Initial release 573 | -------------------------------------------------------------------------------- /CollapsibleUI.plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @name CollapsibleUI 3 | * @author programmer2514 4 | * @authorId 563652755814875146 5 | * @description A feature-rich BetterDiscord plugin that reworks the Discord UI to be significantly more modular 6 | * @version 12.2.4 7 | * @donate https://ko-fi.com/benjaminpryor 8 | * @patreon https://www.patreon.com/BenjaminPryor 9 | * @website https://github.com/programmer2514/BetterDiscord-CollapsibleUI 10 | * @source https://github.com/programmer2514/BetterDiscord-CollapsibleUI/raw/refs/heads/main/CollapsibleUI.plugin.js 11 | */ 12 | 13 | // Abstract settings calls 14 | const settings = { 15 | get transitionSpeed() { return this._transitionSpeed ?? (this._transitionSpeed = runtime.api.Data.load('transition-speed') ?? 200); }, 16 | set transitionSpeed(v) { runtime.api.Data.save('transition-speed', this._transitionSpeed = v); }, 17 | 18 | get collapseToolbar() { return this._collapseToolbar ?? (this._collapseToolbar = runtime.api.Data.load('collapse-toolbar') ?? 'cui'); }, 19 | set collapseToolbar(v) { runtime.api.Data.save('collapse-toolbar', this._collapseToolbar = v); }, 20 | 21 | get collapseSettings() { return this._collapseSettings ?? (this._collapseSettings = runtime.api.Data.load('collapse-settings') ?? true); }, 22 | set collapseSettings(v) { runtime.api.Data.save('collapse-settings', this._collapseSettings = v); }, 23 | 24 | get messageInputCollapse() { return this._messageInputCollapse ?? (this._messageInputCollapse = runtime.api.Data.load('message-input-collapse') ?? true); }, 25 | set messageInputCollapse(v) { runtime.api.Data.save('message-input-collapse', this._messageInputCollapse = v); }, 26 | 27 | get keyboardShortcuts() { return this._keyboardShortcuts ?? (this._keyboardShortcuts = runtime.api.Data.load('keyboard-shortcuts') ?? true); }, 28 | set keyboardShortcuts(v) { runtime.api.Data.save('keyboard-shortcuts', this._keyboardShortcuts = v); }, 29 | 30 | get shortcutList() { 31 | if (!this._shortcutList) { 32 | let shortcuts = runtime.api.Data.load('shortcut-list') ?? [['Alt', 's'], ['Alt', 'c'], ['Alt', 'm'], ['Alt', 'p'], ['Alt', 'i'], ['Alt', 'w'], ['Alt', 'v'], ['Alt', 'u'], ['Alt', 'q'], ['Alt', 'f'], ['Alt', 'a']]; 33 | this._shortcutList = [ 34 | new Set(shortcuts[0]), 35 | new Set(shortcuts[1]), 36 | new Set(shortcuts[2]), 37 | new Set(shortcuts[3]), 38 | new Set(shortcuts[4]), 39 | new Set(shortcuts[5]), 40 | new Set(shortcuts[6]), 41 | new Set(shortcuts[7]), 42 | new Set(shortcuts[8]), 43 | new Set(shortcuts[9]), 44 | new Set(shortcuts[10]), 45 | ]; 46 | } 47 | return this._shortcutList; 48 | }, 49 | set shortcutList(v) { 50 | runtime.api.Data.save('shortcut-list', v); 51 | this._shortcutList = [ 52 | new Set(v[0]), 53 | new Set(v[1]), 54 | new Set(v[2]), 55 | new Set(v[3]), 56 | new Set(v[4]), 57 | new Set(v[5]), 58 | new Set(v[6]), 59 | new Set(v[7]), 60 | new Set(v[8]), 61 | new Set(v[9]), 62 | new Set(v[10]), 63 | ]; 64 | }, 65 | 66 | get collapseDisabledButtons() { return this._collapseDisabledButtons ?? (this._collapseDisabledButtons = runtime.api.Data.load('collapse-disabled-buttons') ?? false); }, 67 | set collapseDisabledButtons(v) { runtime.api.Data.save('collapse-disabled-buttons', this._collapseDisabledButtons = v); }, 68 | 69 | get buttonIndexes() { return this._buttonIndexes ?? (this._buttonIndexes = runtime.api.Data.load('button-indexes') ?? [1, 2, 4, 5, 3, 0, 9, 0, 6, 7, 8]); }, 70 | set buttonIndexes(v) { runtime.api.Data.save('button-indexes', this._buttonIndexes = v); }, 71 | 72 | get floatingPanels() { return this._floatingPanels ?? (this._floatingPanels = runtime.api.Data.load('floating-panels') ?? true); }, 73 | set floatingPanels(v) { runtime.api.Data.save('floating-panels', this._floatingPanels = v); }, 74 | 75 | get floatingEnabled() { return this._floatingEnabled ?? (this._floatingEnabled = runtime.api.Data.load('floating-enabled') ?? ['hover', 'hover', 'hover', 'hover', 'hover', 'hover', null, null, 'hover', 'hover', 'hover']); }, 76 | set floatingEnabled(v) { runtime.api.Data.save('floating-enabled', this._floatingEnabled = v); }, 77 | 78 | get expandOnHover() { return this._expandOnHover ?? (this._expandOnHover = runtime.api.Data.load('expand-on-hover') ?? true); }, 79 | set expandOnHover(v) { runtime.api.Data.save('expand-on-hover', this._expandOnHover = v); }, 80 | 81 | get expandOnHoverEnabled() { return this._expandOnHoverEnabled ?? (this._expandOnHoverEnabled = runtime.api.Data.load('expand-on-hover-enabled') ?? [true, true, true, true, true, true, true, true, true, true, true]); }, 82 | set expandOnHoverEnabled(v) { runtime.api.Data.save('expand-on-hover-enabled', this._expandOnHoverEnabled = v); }, 83 | 84 | get sizeCollapse() { return this._sizeCollapse ?? (this._sizeCollapse = runtime.api.Data.load('size-collapse') ?? false); }, 85 | set sizeCollapse(v) { runtime.api.Data.save('size-collapse', this._sizeCollapse = v); }, 86 | 87 | get sizeCollapseThreshold() { return this._sizeCollapseThreshold ?? (this._sizeCollapseThreshold = runtime.api.Data.load('size-collapse-threshold') ?? [500, 600, 950, 1200, 400, 200, 550, 400, 950, 950, 950]); }, 88 | set sizeCollapseThreshold(v) { runtime.api.Data.save('size-collapse-threshold', this._sizeCollapseThreshold = v); }, 89 | 90 | get conditionalCollapse() { return this._conditionalCollapse ?? (this._conditionalCollapse = runtime.api.Data.load('conditional-collapse') ?? false); }, 91 | set conditionalCollapse(v) { runtime.api.Data.save('conditional-collapse', this._conditionalCollapse = v); }, 92 | 93 | get collapseConditionals() { return this._collapseConditionals ?? (this._collapseConditionals = runtime.api.Data.load('collapse-conditionals') ?? ['', '', '', '', '', '', '', '', '', '', '']); }, 94 | set collapseConditionals(v) { runtime.api.Data.save('collapse-conditionals', this._collapseConditionals = v); }, 95 | 96 | get collapseSize() { return this._collapseSize ?? (this._collapseSize = runtime.api.Data.load('collapse-size') ?? 0); }, 97 | set collapseSize(v) { runtime.api.Data.save('collapse-size', this._collapseSize = v); }, 98 | 99 | get buttonCollapseFudgeFactor() { return this._buttonCollapseFudgeFactor ?? (this._buttonCollapseFudgeFactor = runtime.api.Data.load('button-collapse-fudge-factor') ?? 10); }, 100 | set buttonCollapseFudgeFactor(v) { runtime.api.Data.save('button-collapse-fudge-factor', this._buttonCollapseFudgeFactor = v); }, 101 | 102 | get expandOnHoverFudgeFactor() { return this._expandOnHoverFudgeFactor ?? (this._expandOnHoverFudgeFactor = runtime.api.Data.load('expand-on-hover-fudge-factor') ?? 15); }, 103 | set expandOnHoverFudgeFactor(v) { runtime.api.Data.save('expand-on-hover-fudge-factor', this._expandOnHoverFudgeFactor = v); }, 104 | 105 | get messageInputButtonWidth() { return this._messageInputButtonWidth ?? (this._messageInputButtonWidth = runtime.api.Data.load('message-input-button-width') ?? 40); }, 106 | set messageInputButtonWidth(v) { runtime.api.Data.save('message-input-button-width', this._messageInputButtonWidth = v); }, 107 | 108 | get toolbarElementMaxWidth() { return this._toolbarElementMaxWidth ?? (this._toolbarElementMaxWidth = runtime.api.Data.load('toolbar-element-max-width') ?? 400); }, 109 | set toolbarElementMaxWidth(v) { runtime.api.Data.save('toolbar-element-max-width', this._toolbarElementMaxWidth = v); }, 110 | 111 | get userAreaMaxHeight() { return this._userAreaMaxHeight ?? (this._userAreaMaxHeight = runtime.api.Data.load('user-area-max-height') ?? 420); }, 112 | set userAreaMaxHeight(v) { runtime.api.Data.save('user-area-max-height', this._userAreaMaxHeight = v); }, 113 | 114 | get buttonsActive() { return this._buttonsActive ?? (this._buttonsActive = runtime.api.Data.load('buttons-active') ?? [true, true, true, true, true, true, true, true, true, true, true]); }, 115 | set buttonsActive(v) { runtime.api.Data.save('buttons-active', this._buttonsActive = v); }, 116 | 117 | get channelListWidth() { return this._channelListWidth ?? (this._channelListWidth = runtime.api.Data.load('channel-list-width') ?? 240); }, 118 | set channelListWidth(v) { runtime.api.Data.save('channel-list-width', this._channelListWidth = v); }, 119 | 120 | get membersListWidth() { return this._membersListWidth ?? (this._membersListWidth = runtime.api.Data.load('members-list-width') ?? 240); }, 121 | set membersListWidth(v) { runtime.api.Data.save('members-list-width', this._membersListWidth = v); }, 122 | 123 | get userProfileWidth() { return this._userProfileWidth ?? (this._userProfileWidth = runtime.api.Data.load('user-profile-width') ?? 340); }, 124 | set userProfileWidth(v) { runtime.api.Data.save('user-profile-width', this._userProfileWidth = v); }, 125 | 126 | get searchPanelWidth() { return this._searchPanelWidth ?? (this._searchPanelWidth = runtime.api.Data.load('search-panel-width') ?? 418); }, 127 | set searchPanelWidth(v) { runtime.api.Data.save('search-panel-width', this._searchPanelWidth = v); }, 128 | 129 | get forumPopoutWidth() { return this._forumPopoutWidth ?? (this._forumPopoutWidth = runtime.api.Data.load('forum-popout-width') ?? 450); }, 130 | set forumPopoutWidth(v) { runtime.api.Data.save('forum-popout-width', this._forumPopoutWidth = v); }, 131 | 132 | get activityPanelWidth() { return this._activityPanelWidth ?? (this._activityPanelWidth = runtime.api.Data.load('activity-panel-width') ?? 360); }, 133 | set activityPanelWidth(v) { runtime.api.Data.save('activity-panel-width', this._activityPanelWidth = v); }, 134 | 135 | get defaultChannelListWidth() { return this._defaultChannelListWidth ?? (this._defaultChannelListWidth = runtime.api.Data.load('default-channel-list-width') ?? 240); }, 136 | set defaultChannelListWidth(v) { runtime.api.Data.save('default-channel-list-width', this._defaultChannelListWidth = v); }, 137 | 138 | get defaultMembersListWidth() { return this._defaultMembersListWidth ?? (this._defaultMembersListWidth = runtime.api.Data.load('default-members-list-width') ?? 240); }, 139 | set defaultMembersListWidth(v) { runtime.api.Data.save('default-members-list-width', this._defaultMembersListWidth = v); }, 140 | 141 | get defaultUserProfileWidth() { return this._defaultUserProfileWidth ?? (this._defaultUserProfileWidth = runtime.api.Data.load('default-user-profile-width') ?? 340); }, 142 | set defaultUserProfileWidth(v) { runtime.api.Data.save('default-user-profile-width', this._defaultUserProfileWidth = v); }, 143 | 144 | get defaultSearchPanelWidth() { return this._defaultSearchPanelWidth ?? (this._defaultSearchPanelWidth = runtime.api.Data.load('default-search-panel-width') ?? 418); }, 145 | set defaultSearchPanelWidth(v) { runtime.api.Data.save('default-search-panel-width', this._defaultSearchPanelWidth = v); }, 146 | 147 | get defaultForumPopoutWidth() { return this._defaultForumPopoutWidth ?? (this._defaultForumPopoutWidth = runtime.api.Data.load('default-forum-popout-width') ?? 450); }, 148 | set defaultForumPopoutWidth(v) { runtime.api.Data.save('default-forum-popout-width', this._defaultForumPopoutWidth = v); }, 149 | 150 | get defaultActivityPanelWidth() { return this._defaultActivityPanelWidth ?? (this._defaultActivityPanelWidth = runtime.api.Data.load('default-activity-panel-width') ?? 360); }, 151 | set defaultActivityPanelWidth(v) { runtime.api.Data.save('default-activity-panel-width', this._defaultActivityPanelWidth = v); }, 152 | }; 153 | 154 | // Define plugin changelog and settings panel layout 155 | const config = { 156 | changelog: [ 157 | { 158 | title: '12.2.4', 159 | type: 'added', 160 | items: [ 161 | 'Fixed user area being cut off', 162 | 'Fixed a small visual glitch caused by collapsing the channel list', 163 | 'Improved UIRefreshRefresh compatibility', 164 | ], 165 | }, 166 | { 167 | title: '1.0.0 - 12.2.3', 168 | type: 'added', 169 | items: [ 170 | 'See the full changelog here: https://programmer2514.github.io/?l=cui-changelog', 171 | ], 172 | }, 173 | ], 174 | settings: [ 175 | { 176 | type: 'category', 177 | id: 'main-group', 178 | name: 'Main', 179 | collapsible: true, 180 | shown: true, 181 | settings: [ 182 | { 183 | type: 'number', 184 | id: 'transitionSpeed', 185 | name: 'UI Transition Speed (ms)', 186 | note: 'Sets the speed of UI animations. Set to 0 to disable transitions', 187 | get value() { return settings.transitionSpeed; }, 188 | }, 189 | { 190 | type: 'dropdown', 191 | id: 'collapseToolbar', 192 | name: 'Collapse Toolbar Buttons', 193 | note: 'Automatically collapse the top-right toolbar buttons', 194 | get value() { return settings.collapseToolbar; }, 195 | options: [ 196 | { 197 | label: 'None', 198 | value: false, 199 | }, 200 | { 201 | label: 'Just CollapsibleUI', 202 | value: 'cui', 203 | }, 204 | { 205 | label: 'All', 206 | value: 'all', 207 | }, 208 | ], 209 | }, 210 | { 211 | type: 'switch', 212 | id: 'collapseSettings', 213 | name: 'Collapse Settings', 214 | note: 'Automatically collapse the bottom-left settings buttons', 215 | get value() { return settings.collapseSettings; }, 216 | }, 217 | { 218 | type: 'switch', 219 | id: 'messageInputCollapse', 220 | name: 'Collapse Message Input Buttons', 221 | note: 'Automatically collapse the GIF/sticker/emoji/gift buttons', 222 | get value() { return settings.messageInputCollapse; }, 223 | }, 224 | ], 225 | }, 226 | { 227 | type: 'category', 228 | id: 'keyboard-shortcuts-group', 229 | name: 'Keyboard Shortcuts', 230 | collapsible: true, 231 | shown: false, 232 | settings: [ 233 | { 234 | type: 'switch', 235 | id: 'keyboardShortcuts', 236 | name: 'Keyboard Shortcuts', 237 | note: 'Collapse UI panels using keyboard shortcuts', 238 | get value() { return settings.keyboardShortcuts; }, 239 | }, 240 | { 241 | type: 'keybind', 242 | id: 'server-list-shortcut', 243 | name: 'Toggle Server List', 244 | get value() { return [...settings.shortcutList[constants.I_SERVER_LIST]]; }, 245 | }, 246 | { 247 | type: 'keybind', 248 | id: 'channel-list-shortcut', 249 | name: 'Toggle Channel List', 250 | get value() { return [...settings.shortcutList[constants.I_CHANNEL_LIST]]; }, 251 | }, 252 | { 253 | type: 'keybind', 254 | id: 'members-list-shortcut', 255 | name: 'Toggle Members List', 256 | get value() { return [...settings.shortcutList[constants.I_MEMBERS_LIST]]; }, 257 | }, 258 | { 259 | type: 'keybind', 260 | id: 'user-profile-shortcut', 261 | name: 'Toggle User Profile', 262 | get value() { return [...settings.shortcutList[constants.I_USER_PROFILE]]; }, 263 | }, 264 | { 265 | type: 'keybind', 266 | id: 'message-input-shortcut', 267 | name: 'Toggle Message Input', 268 | get value() { return [...settings.shortcutList[constants.I_MESSAGE_INPUT]]; }, 269 | }, 270 | { 271 | type: 'keybind', 272 | id: 'window-bar-shortcut', 273 | name: 'Toggle Window Bar', 274 | get value() { return [...settings.shortcutList[constants.I_WINDOW_BAR]]; }, 275 | }, 276 | { 277 | type: 'keybind', 278 | id: 'call-window-shortcut', 279 | name: 'Toggle Call Window', 280 | get value() { return [...settings.shortcutList[constants.I_CALL_WINDOW]]; }, 281 | }, 282 | { 283 | type: 'keybind', 284 | id: 'user-area-shortcut', 285 | name: 'Toggle User Area', 286 | get value() { return [...settings.shortcutList[constants.I_USER_AREA]]; }, 287 | }, 288 | { 289 | type: 'keybind', 290 | id: 'search-panel-shortcut', 291 | name: 'Toggle Search Panel', 292 | get value() { return [...settings.shortcutList[constants.I_SEARCH_PANEL]]; }, 293 | }, 294 | { 295 | type: 'keybind', 296 | id: 'forum-popout-shortcut', 297 | name: 'Toggle Forum Popout', 298 | get value() { return [...settings.shortcutList[constants.I_FORUM_POPOUT]]; }, 299 | }, 300 | { 301 | type: 'keybind', 302 | id: 'activity-panel-shortcut', 303 | name: 'Toggle Activity Panel', 304 | get value() { return [...settings.shortcutList[constants.I_ACTIVITY_PANEL]]; }, 305 | }, 306 | ], 307 | }, 308 | { 309 | type: 'category', 310 | id: 'toolbar-buttons-group', 311 | name: 'Toolbar Buttons', 312 | collapsible: true, 313 | shown: false, 314 | settings: [ 315 | { 316 | type: 'switch', 317 | id: 'collapseDisabledButtons', 318 | name: 'Disabled Buttons Stay Collapsed?', 319 | note: 'Panels with disabled toggle buttons will keep their toggled state. If disabled, they will default to open', 320 | get value() { return settings.collapseDisabledButtons; }, 321 | }, 322 | { 323 | type: 'slider', 324 | id: 'server-list-button-index', 325 | name: 'Server List Button', 326 | note: 'Sets the order of the Server List toolbar button. Set to 0 to disable', 327 | get value() { return settings.buttonIndexes[constants.I_SERVER_LIST]; }, 328 | min: 0, 329 | max: 11, 330 | step: 1, 331 | markers: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 332 | }, 333 | { 334 | type: 'slider', 335 | id: 'channel-list-button-index', 336 | name: 'Channel List Button', 337 | note: 'Sets the order of the Channel List toolbar button. Set to 0 to disable', 338 | get value() { return settings.buttonIndexes[constants.I_CHANNEL_LIST]; }, 339 | min: 0, 340 | max: 11, 341 | step: 1, 342 | markers: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 343 | }, 344 | { 345 | type: 'slider', 346 | id: 'members-list-button-index', 347 | name: 'Members List Button', 348 | note: 'Sets the order of the Members List toolbar button. Set to 0 to disable', 349 | get value() { return settings.buttonIndexes[constants.I_MEMBERS_LIST]; }, 350 | min: 0, 351 | max: 11, 352 | step: 1, 353 | markers: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 354 | }, 355 | { 356 | type: 'slider', 357 | id: 'user-profile-button-index', 358 | name: 'User Profile Button', 359 | note: 'Sets the order of the User Profile toolbar button. Set to 0 to disable', 360 | get value() { return settings.buttonIndexes[constants.I_USER_PROFILE]; }, 361 | min: 0, 362 | max: 11, 363 | step: 1, 364 | markers: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 365 | }, 366 | { 367 | type: 'slider', 368 | id: 'message-input-button-index', 369 | name: 'Message Input Button', 370 | note: 'Sets the order of the Message Input toolbar button. Set to 0 to disable', 371 | get value() { return settings.buttonIndexes[constants.I_MESSAGE_INPUT]; }, 372 | min: 0, 373 | max: 11, 374 | step: 1, 375 | markers: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 376 | }, 377 | { 378 | type: 'slider', 379 | id: 'window-bar-button-index', 380 | name: 'Window Bar Button', 381 | note: 'Sets the order of the Window Bar toolbar button. Set to 0 to disable', 382 | get value() { return settings.buttonIndexes[constants.I_WINDOW_BAR]; }, 383 | min: 0, 384 | max: 11, 385 | step: 1, 386 | markers: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 387 | }, 388 | { 389 | type: 'slider', 390 | id: 'call-window-button-index', 391 | name: 'Call Window Button', 392 | note: 'Sets the order of the Call Window toolbar button. Set to 0 to disable', 393 | get value() { return settings.buttonIndexes[constants.I_CALL_WINDOW]; }, 394 | min: 0, 395 | max: 11, 396 | step: 1, 397 | markers: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 398 | }, 399 | { 400 | type: 'slider', 401 | id: 'user-area-button-index', 402 | name: 'User Area Button', 403 | note: 'Sets the order of the User Area toolbar button. Set to 0 to disable', 404 | get value() { return settings.buttonIndexes[constants.I_USER_AREA]; }, 405 | min: 0, 406 | max: 11, 407 | step: 1, 408 | markers: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 409 | }, 410 | { 411 | type: 'slider', 412 | id: 'search-panel-button-index', 413 | name: 'Search Panel Button', 414 | note: 'Sets the order of the Search Panel toolbar button. Set to 0 to disable', 415 | get value() { return settings.buttonIndexes[constants.I_SEARCH_PANEL]; }, 416 | min: 0, 417 | max: 11, 418 | step: 1, 419 | markers: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 420 | }, 421 | { 422 | type: 'slider', 423 | id: 'forum-popout-button-index', 424 | name: 'Forum Popout Button', 425 | note: 'Sets the order of the Forum Popout toolbar button. Set to 0 to disable', 426 | get value() { return settings.buttonIndexes[constants.I_FORUM_POPOUT]; }, 427 | min: 0, 428 | max: 11, 429 | step: 1, 430 | markers: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 431 | }, 432 | { 433 | type: 'slider', 434 | id: 'activity-panel-button-index', 435 | name: 'Activity Panel Button', 436 | note: 'Sets the order of the Activity Panel toolbar button. Set to 0 to disable', 437 | get value() { return settings.buttonIndexes[constants.I_ACTIVITY_PANEL]; }, 438 | min: 0, 439 | max: 11, 440 | step: 1, 441 | markers: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 442 | }, 443 | ], 444 | }, 445 | { 446 | type: 'category', 447 | id: 'resizable-panels-group', 448 | name: 'Resizable Panels', 449 | collapsible: true, 450 | shown: false, 451 | settings: [ 452 | { 453 | type: 'switch', 454 | id: 'resizable-channel-list', 455 | name: 'Resizable Channel List', 456 | note: 'Resize the channel list by clicking-and-dragging right edge. Right-click to reset width', 457 | get value() { return settings.channelListWidth !== 0; }, 458 | }, 459 | { 460 | type: 'switch', 461 | id: 'resizable-members-list', 462 | name: 'Resizable Members List', 463 | note: 'Resize the members list by clicking-and-dragging the left edge. Right-click to reset width', 464 | get value() { return settings.membersListWidth !== 0; }, 465 | }, 466 | { 467 | type: 'switch', 468 | id: 'resizable-user-profile', 469 | name: 'Resizable User Profile', 470 | note: 'Resize the user profile in DMs by clicking-and-dragging the left edge. Right-click to reset width', 471 | get value() { return settings.userProfileWidth !== 0; }, 472 | }, 473 | { 474 | type: 'switch', 475 | id: 'resizable-search-panel', 476 | name: 'Resizable Search Panel', 477 | note: 'Resize the message search panel by clicking-and-dragging the left edge. Right-click to reset width', 478 | get value() { return settings.searchPanelWidth !== 0; }, 479 | }, 480 | { 481 | type: 'switch', 482 | id: 'resizable-forum-popout', 483 | name: 'Resizable Forum Popout', 484 | note: 'Resize the thread popup in forum channels by clicking-and-dragging the left edge. Right-click to reset width', 485 | get value() { return settings.forumPopoutWidth !== 0; }, 486 | }, 487 | { 488 | type: 'switch', 489 | id: 'resizable-activity-panel', 490 | name: 'Resizable Activity Panel', 491 | note: 'Resize the activity panel in the friends list by clicking-and-dragging the left edge. Right-click to reset width', 492 | get value() { return settings.activityPanelWidth !== 0; }, 493 | }, 494 | ], 495 | }, 496 | { 497 | type: 'category', 498 | id: 'floating-panels-group', 499 | name: 'Floating Panels', 500 | collapsible: true, 501 | shown: false, 502 | settings: [ 503 | { 504 | type: 'switch', 505 | id: 'floatingPanels', 506 | name: 'Floating Panels', 507 | note: 'Expanded UI panels will float over other panels, instead of moving them out of the way', 508 | get value() { return settings.floatingPanels; }, 509 | }, 510 | { 511 | type: 'dropdown', 512 | id: 'server-list-floating', 513 | name: 'Server List', 514 | note: 'Server List floats over other panels', 515 | get value() { return settings.floatingEnabled[constants.I_SERVER_LIST]; }, 516 | options: [ 517 | { 518 | label: 'Never', 519 | value: false, 520 | }, 521 | { 522 | label: 'On Hover', 523 | value: 'hover', 524 | }, 525 | { 526 | label: 'Always', 527 | value: 'always', 528 | }, 529 | ], 530 | }, 531 | { 532 | type: 'dropdown', 533 | id: 'channel-list-floating', 534 | name: 'Channel List', 535 | note: 'Channel List floats over other panels', 536 | get value() { return settings.floatingEnabled[constants.I_CHANNEL_LIST]; }, 537 | options: [ 538 | { 539 | label: 'Never', 540 | value: false, 541 | }, 542 | { 543 | label: 'On Hover', 544 | value: 'hover', 545 | }, 546 | { 547 | label: 'Always', 548 | value: 'always', 549 | }, 550 | ], 551 | }, 552 | { 553 | type: 'dropdown', 554 | id: 'members-list-floating', 555 | name: 'Members List', 556 | note: 'Members List floats over other panels', 557 | get value() { return settings.floatingEnabled[constants.I_MEMBERS_LIST]; }, 558 | options: [ 559 | { 560 | label: 'Never', 561 | value: false, 562 | }, 563 | { 564 | label: 'On Hover', 565 | value: 'hover', 566 | }, 567 | { 568 | label: 'Always', 569 | value: 'always', 570 | }, 571 | ], 572 | }, 573 | { 574 | type: 'dropdown', 575 | id: 'user-profile-floating', 576 | name: 'User Profile', 577 | note: 'User Profile floats over other panels', 578 | get value() { return settings.floatingEnabled[constants.I_USER_PROFILE]; }, 579 | options: [ 580 | { 581 | label: 'Never', 582 | value: false, 583 | }, 584 | { 585 | label: 'On Hover', 586 | value: 'hover', 587 | }, 588 | { 589 | label: 'Always', 590 | value: 'always', 591 | }, 592 | ], 593 | }, 594 | { 595 | type: 'dropdown', 596 | id: 'message-input-floating', 597 | name: 'Message Input', 598 | note: 'Message Input floats over other panels', 599 | get value() { return settings.floatingEnabled[constants.I_MESSAGE_INPUT]; }, 600 | options: [ 601 | { 602 | label: 'Never', 603 | value: false, 604 | }, 605 | { 606 | label: 'On Hover', 607 | value: 'hover', 608 | }, 609 | { 610 | label: 'Always', 611 | value: 'always', 612 | }, 613 | ], 614 | }, 615 | { 616 | type: 'dropdown', 617 | id: 'window-bar-floating', 618 | name: 'Window Bar', 619 | note: 'Window Bar floats over other panels', 620 | get value() { return settings.floatingEnabled[constants.I_WINDOW_BAR]; }, 621 | options: [ 622 | { 623 | label: 'Never', 624 | value: false, 625 | }, 626 | { 627 | label: 'On Hover', 628 | value: 'hover', 629 | }, 630 | { 631 | label: 'Always', 632 | value: 'always', 633 | }, 634 | ], 635 | }, 636 | { 637 | type: 'dropdown', 638 | id: 'search-panel-floating', 639 | name: 'Search Panel', 640 | note: 'Search Panel floats over other panels', 641 | get value() { return settings.floatingEnabled[constants.I_SEARCH_PANEL]; }, 642 | options: [ 643 | { 644 | label: 'Never', 645 | value: false, 646 | }, 647 | { 648 | label: 'On Hover', 649 | value: 'hover', 650 | }, 651 | { 652 | label: 'Always', 653 | value: 'always', 654 | }, 655 | ], 656 | }, 657 | { 658 | type: 'dropdown', 659 | id: 'forum-popout-floating', 660 | name: 'Forum Popout', 661 | note: 'Forum Popout floats over other panels', 662 | get value() { return settings.floatingEnabled[constants.I_FORUM_POPOUT]; }, 663 | options: [ 664 | { 665 | label: 'Never', 666 | value: false, 667 | }, 668 | { 669 | label: 'On Hover', 670 | value: 'hover', 671 | }, 672 | { 673 | label: 'Always', 674 | value: 'always', 675 | }, 676 | ], 677 | }, 678 | { 679 | type: 'dropdown', 680 | id: 'activity-panel-floating', 681 | name: 'Activity Panel', 682 | note: 'Activity Panel floats over other panels', 683 | get value() { return settings.floatingEnabled[constants.I_ACTIVITY_PANEL]; }, 684 | options: [ 685 | { 686 | label: 'Never', 687 | value: false, 688 | }, 689 | { 690 | label: 'On Hover', 691 | value: 'hover', 692 | }, 693 | { 694 | label: 'Always', 695 | value: 'always', 696 | }, 697 | ], 698 | }, 699 | ], 700 | }, 701 | { 702 | type: 'category', 703 | id: 'expand-on-hover-group', 704 | name: 'Expand on Hover', 705 | collapsible: true, 706 | shown: false, 707 | settings: [ 708 | { 709 | type: 'switch', 710 | id: 'expandOnHover', 711 | name: 'Expand on Hover', 712 | note: 'Expands collapsed UI panels when the mouse is near them. Must be enabled for conditional/size collapse to work', 713 | get value() { return settings.expandOnHover; }, 714 | }, 715 | { 716 | type: 'switch', 717 | id: 'server-list-expand-on-hover', 718 | name: 'Server List', 719 | note: 'Server List expands on hover', 720 | get value() { return settings.expandOnHoverEnabled[constants.I_SERVER_LIST]; }, 721 | }, 722 | { 723 | type: 'switch', 724 | id: 'channel-list-expand-on-hover', 725 | name: 'Channel List', 726 | note: 'Channel List expands on hover', 727 | get value() { return settings.expandOnHoverEnabled[constants.I_CHANNEL_LIST]; }, 728 | }, 729 | { 730 | type: 'switch', 731 | id: 'members-list-expand-on-hover', 732 | name: 'Members List', 733 | note: 'Members List expands on hover', 734 | get value() { return settings.expandOnHoverEnabled[constants.I_MEMBERS_LIST]; }, 735 | }, 736 | { 737 | type: 'switch', 738 | id: 'user-profile-expand-on-hover', 739 | name: 'User Profile', 740 | note: 'User Profile expands on hover', 741 | get value() { return settings.expandOnHoverEnabled[constants.I_USER_PROFILE]; }, 742 | }, 743 | { 744 | type: 'switch', 745 | id: 'message-input-expand-on-hover', 746 | name: 'Message Input', 747 | note: 'Message Input expands on hover', 748 | get value() { return settings.expandOnHoverEnabled[constants.I_MESSAGE_INPUT]; }, 749 | }, 750 | { 751 | type: 'switch', 752 | id: 'window-bar-expand-on-hover', 753 | name: 'Window Bar', 754 | note: 'Window Bar expands on hover', 755 | get value() { return settings.expandOnHoverEnabled[constants.I_WINDOW_BAR]; }, 756 | }, 757 | { 758 | type: 'switch', 759 | id: 'call-window-expand-on-hover', 760 | name: 'Call Window', 761 | note: 'Call Window expands on hover', 762 | get value() { return settings.expandOnHoverEnabled[constants.I_CALL_WINDOW]; }, 763 | }, 764 | { 765 | type: 'switch', 766 | id: 'user-area-expand-on-hover', 767 | name: 'User Area', 768 | note: 'User Area expands on hover', 769 | get value() { return settings.expandOnHoverEnabled[constants.I_USER_AREA]; }, 770 | }, 771 | { 772 | type: 'switch', 773 | id: 'search-panel-expand-on-hover', 774 | name: 'Search Panel', 775 | note: 'Search Panel expands on hover', 776 | get value() { return settings.expandOnHoverEnabled[constants.I_SEARCH_PANEL]; }, 777 | }, 778 | { 779 | type: 'switch', 780 | id: 'forum-popout-expand-on-hover', 781 | name: 'Forum Popout', 782 | note: 'Forum Popout expands on hover', 783 | get value() { return settings.expandOnHoverEnabled[constants.I_FORUM_POPOUT]; }, 784 | }, 785 | { 786 | type: 'switch', 787 | id: 'activity-panel-expand-on-hover', 788 | name: 'Activity Panel', 789 | note: 'Activity Panel expands on hover', 790 | get value() { return settings.expandOnHoverEnabled[constants.I_ACTIVITY_PANEL]; }, 791 | }, 792 | ], 793 | }, 794 | { 795 | type: 'category', 796 | id: 'size-collapse-group', 797 | name: 'Size Collapse', 798 | collapsible: true, 799 | shown: false, 800 | settings: [ 801 | { 802 | type: 'switch', 803 | id: 'sizeCollapse', 804 | name: 'Size Collapse', 805 | note: 'Auto-collapse UI panels based on window size', 806 | get value() { return settings.sizeCollapse; }, 807 | }, 808 | { 809 | type: 'number', 810 | id: 'server-list-threshold', 811 | name: 'Server List - Width Threshold (px)', 812 | note: 'Window width at which the Server List will collapse. Specifies height if Horizontal Server List is enabled', 813 | get value() { return settings.sizeCollapseThreshold[constants.I_SERVER_LIST]; }, 814 | }, 815 | { 816 | type: 'number', 817 | id: 'channel-list-threshold', 818 | name: 'Channel List - Width Threshold (px)', 819 | note: 'Window width at which the Channel List will collapse', 820 | get value() { return settings.sizeCollapseThreshold[constants.I_CHANNEL_LIST]; }, 821 | }, 822 | { 823 | type: 'number', 824 | id: 'members-list-threshold', 825 | name: 'Members List - Width Threshold (px)', 826 | note: 'Window width at which the Members List will collapse', 827 | get value() { return settings.sizeCollapseThreshold[constants.I_MEMBERS_LIST]; }, 828 | }, 829 | { 830 | type: 'number', 831 | id: 'user-profile-threshold', 832 | name: 'User Profile - Width Threshold (px)', 833 | note: 'Window width at which the User Profile will collapse', 834 | get value() { return settings.sizeCollapseThreshold[constants.I_USER_PROFILE]; }, 835 | }, 836 | { 837 | type: 'number', 838 | id: 'message-input-threshold', 839 | name: 'Message Input - Height Threshold (px)', 840 | note: 'Window height at which the Message Input will collapse', 841 | get value() { return settings.sizeCollapseThreshold[constants.I_MESSAGE_INPUT]; }, 842 | }, 843 | { 844 | type: 'number', 845 | id: 'window-bar-threshold', 846 | name: 'Window Bar - Height Threshold (px)', 847 | note: 'Window height at which the Window Bar will collapse', 848 | get value() { return settings.sizeCollapseThreshold[constants.I_WINDOW_BAR]; }, 849 | }, 850 | { 851 | type: 'number', 852 | id: 'call-window-threshold', 853 | name: 'Call Window - Height Threshold (px)', 854 | note: 'Window height at which the Call Window will collapse', 855 | get value() { return settings.sizeCollapseThreshold[constants.I_CALL_WINDOW]; }, 856 | }, 857 | { 858 | type: 'number', 859 | id: 'user-area-threshold', 860 | name: 'User Area - Height Threshold (px)', 861 | note: 'Window height at which the User Area will collapse', 862 | get value() { return settings.sizeCollapseThreshold[constants.I_USER_AREA]; }, 863 | }, 864 | { 865 | type: 'number', 866 | id: 'search-panel-threshold', 867 | name: 'Search Panel - Width Threshold (px)', 868 | note: 'Window width at which the Search Panel will collapse', 869 | get value() { return settings.sizeCollapseThreshold[constants.I_SEARCH_PANEL]; }, 870 | }, 871 | { 872 | type: 'number', 873 | id: 'forum-popout-threshold', 874 | name: 'Forum Popout - Width Threshold (px)', 875 | note: 'Window width at which the Forum Popout will collapse', 876 | get value() { return settings.sizeCollapseThreshold[constants.I_FORUM_POPOUT]; }, 877 | }, 878 | { 879 | type: 'number', 880 | id: 'activity-panel-threshold', 881 | name: 'Activity Panel - Width Threshold (px)', 882 | note: 'Window width at which the Activity Panel will collapse', 883 | get value() { return settings.sizeCollapseThreshold[constants.I_ACTIVITY_PANEL]; }, 884 | }, 885 | ], 886 | }, 887 | { 888 | type: 'category', 889 | id: 'conditional-collapse-group', 890 | name: 'Conditional Collapse (Advanced)', 891 | collapsible: true, 892 | shown: false, 893 | settings: [ 894 | { 895 | type: 'switch', 896 | id: 'conditionalCollapse', 897 | name: 'Conditional Collapse', 898 | note: 'Auto-collapse UI panels based on custom conditional expression. Overrides size collapse', 899 | get value() { return settings.conditionalCollapse; }, 900 | }, 901 | { 902 | type: 'text', 903 | id: 'server-list-conditional', 904 | name: 'Server List - Collapse Expression (JS)', 905 | note: 'The Server List will collapse when this expression is true, and expand when it is false', 906 | get value() { return settings.collapseConditionals[constants.I_SERVER_LIST]; }, 907 | }, 908 | { 909 | type: 'text', 910 | id: 'channel-list-conditional', 911 | name: 'Channel List - Collapse Expression (JS)', 912 | note: 'The Channel List will collapse when this expression is true, and expand when it is false', 913 | get value() { return settings.collapseConditionals[constants.I_CHANNEL_LIST]; }, 914 | }, 915 | { 916 | type: 'text', 917 | id: 'members-list-conditional', 918 | name: 'Members List - Collapse Expression (JS)', 919 | note: 'The Members List will collapse when this expression is true, and expand when it is false', 920 | get value() { return settings.collapseConditionals[constants.I_MEMBERS_LIST]; }, 921 | }, 922 | { 923 | type: 'text', 924 | id: 'user-profile-conditional', 925 | name: 'User Profile - Collapse Expression (JS)', 926 | note: 'The User Profile will collapse when this expression is true, and expand when it is false', 927 | get value() { return settings.collapseConditionals[constants.I_USER_PROFILE]; }, 928 | }, 929 | { 930 | type: 'text', 931 | id: 'message-input-conditional', 932 | name: 'Message Input - Collapse Expression (JS)', 933 | note: 'The Message Input will collapse when this expression is true, and expand when it is false', 934 | get value() { return settings.collapseConditionals[constants.I_MESSAGE_INPUT]; }, 935 | }, 936 | { 937 | type: 'text', 938 | id: 'window-bar-conditional', 939 | name: 'Window Bar - Collapse Expression (JS)', 940 | note: 'The Window Bar will collapse when this expression is true, and expand when it is false', 941 | get value() { return settings.collapseConditionals[constants.I_WINDOW_BAR]; }, 942 | }, 943 | { 944 | type: 'text', 945 | id: 'call-window-conditional', 946 | name: 'Call Window - Collapse Expression (JS)', 947 | note: 'The Call Window will collapse when this expression is true, and expand when it is false', 948 | get value() { return settings.collapseConditionals[constants.I_CALL_WINDOW]; }, 949 | }, 950 | { 951 | type: 'text', 952 | id: 'user-area-conditional', 953 | name: 'User Area - Collapse Expression (JS)', 954 | note: 'The User Area will collapse when this expression is true, and expand when it is false', 955 | get value() { return settings.collapseConditionals[constants.I_USER_AREA]; }, 956 | }, 957 | { 958 | type: 'text', 959 | id: 'search-panel-conditional', 960 | name: 'Search Panel - Collapse Expression (JS)', 961 | note: 'The Search Panel will collapse when this expression is true, and expand when it is false', 962 | get value() { return settings.collapseConditionals[constants.I_SEARCH_PANEL]; }, 963 | }, 964 | { 965 | type: 'text', 966 | id: 'forum-popout-conditional', 967 | name: 'Forum Popout - Collapse Expression (JS)', 968 | note: 'The Forum Popout will collapse when this expression is true, and expand when it is false', 969 | get value() { return settings.collapseConditionals[constants.I_FORUM_POPOUT]; }, 970 | }, 971 | { 972 | type: 'text', 973 | id: 'activity-panel-conditional', 974 | name: 'Activity Panel - Collapse Expression (JS)', 975 | note: 'The Activity Panel will collapse when this expression is true, and expand when it is false', 976 | get value() { return settings.collapseConditionals[constants.I_ACTIVITY_PANEL]; }, 977 | }, 978 | ], 979 | }, 980 | { 981 | type: 'category', 982 | id: 'advanced-group', 983 | name: 'Advanced', 984 | collapsible: true, 985 | shown: false, 986 | settings: [ 987 | { 988 | type: 'number', 989 | id: 'collapseSize', 990 | name: 'Collapsed Panel Size (px)', 991 | note: 'The size of UI panels when collapsed', 992 | get value() { return settings.collapseSize; }, 993 | }, 994 | { 995 | type: 'number', 996 | id: 'buttonCollapseFudgeFactor', 997 | name: 'Button Groups - Collapse Fudge Factor (px)', 998 | note: 'The distance the mouse must be from a set of buttons before they collapse', 999 | get value() { return settings.buttonCollapseFudgeFactor; }, 1000 | }, 1001 | { 1002 | type: 'number', 1003 | id: 'expandOnHoverFudgeFactor', 1004 | name: 'Expand on Hover - Fudge Factor (px)', 1005 | note: 'The distance the mouse must be from a UI panel before it expands or collapses', 1006 | get value() { return settings.expandOnHoverFudgeFactor; }, 1007 | }, 1008 | { 1009 | type: 'number', 1010 | id: 'messageInputButtonWidth', 1011 | name: 'Message Input Button - Width (px)', 1012 | note: 'The width of a message input button when expanded', 1013 | get value() { return settings.messageInputButtonWidth; }, 1014 | }, 1015 | { 1016 | type: 'number', 1017 | id: 'toolbarElementMaxWidth', 1018 | name: 'Toolbar Elements - Max Width (px)', 1019 | note: 'The maximum width of the full toolbar\'s elements when expanded', 1020 | get value() { return settings.toolbarElementMaxWidth; }, 1021 | }, 1022 | { 1023 | type: 'number', 1024 | id: 'userAreaMaxHeight', 1025 | name: 'User Area - Max Height (px)', 1026 | note: 'The maximum height of the User Area when expanded', 1027 | get value() { return settings.userAreaMaxHeight; }, 1028 | }, 1029 | { 1030 | type: 'number', 1031 | id: 'defaultChannelListWidth', 1032 | name: 'Channel List - Default Width (px)', 1033 | note: 'The width of the channel list when not actively resized', 1034 | get value() { return settings.defaultChannelListWidth; }, 1035 | }, 1036 | { 1037 | type: 'number', 1038 | id: 'defaultMembersListWidth', 1039 | name: 'Members List - Default Width (px)', 1040 | note: 'The width of the members list when not actively resized', 1041 | get value() { return settings.defaultMembersListWidth; }, 1042 | }, 1043 | { 1044 | type: 'number', 1045 | id: 'defaultUserProfileWidth', 1046 | name: 'User Profile - Default Width (px)', 1047 | note: 'The width of the user profile when not actively resized', 1048 | get value() { return settings.defaultUserProfileWidth; }, 1049 | }, 1050 | { 1051 | type: 'number', 1052 | id: 'defaultSearchPanelWidth', 1053 | name: 'Search Panel - Default Width (px)', 1054 | note: 'The width of the search panel when not actively resized', 1055 | get value() { return settings.defaultSearchPanelWidth; }, 1056 | }, 1057 | { 1058 | type: 'number', 1059 | id: 'defaultForumPopoutWidth', 1060 | name: 'Forum Popout - Default Width (px)', 1061 | note: 'The width of the forum popout when not actively resized', 1062 | get value() { return settings.defaultForumPopoutWidth; }, 1063 | }, 1064 | { 1065 | type: 'number', 1066 | id: 'defaultActivityPanelWidth', 1067 | name: 'Activity Panel - Default Width (px)', 1068 | note: 'The width of the activity panel when not actively resized', 1069 | get value() { return settings.defaultActivityPanelWidth; }, 1070 | }, 1071 | ], 1072 | }, 1073 | ], 1074 | }; 1075 | 1076 | // Define locale labels 1077 | const locale = { 1078 | en: [ 1079 | 'Server List', 1080 | 'Channel List', 1081 | 'Members List', 1082 | 'User Profile', 1083 | 'Message Input', 1084 | 'Window Bar', 1085 | 'Call Window', 1086 | 'User Area', 1087 | 'Search Panel', 1088 | 'Forum Popout', 1089 | 'Activity Panel', 1090 | ], 1091 | get current() { return this[document.documentElement.getAttribute('lang')] ?? this.en; }, 1092 | }; 1093 | 1094 | // Define icon paths 1095 | const icons = [ 1096 | '', 1097 | '', 1098 | '', 1099 | '', 1100 | '', 1101 | '', 1102 | '', 1103 | '', 1104 | '', 1105 | '', 1106 | '', 1107 | ]; 1108 | 1109 | // Define button index constants 1110 | const constants = { 1111 | I_SERVER_LIST: 0, 1112 | I_CHANNEL_LIST: 1, 1113 | I_MEMBERS_LIST: 2, 1114 | I_USER_PROFILE: 3, 1115 | I_MESSAGE_INPUT: 4, 1116 | I_WINDOW_BAR: 5, 1117 | I_CALL_WINDOW: 6, 1118 | I_USER_AREA: 7, 1119 | I_SEARCH_PANEL: 8, 1120 | I_FORUM_POPOUT: 9, 1121 | I_ACTIVITY_PANEL: 10, 1122 | I_SETTINGS_BUTTONS: 0, 1123 | I_MESSAGE_INPUT_BUTTONS: 1, 1124 | I_TOOLBAR_BUTTONS: 2, 1125 | I_TOOLBAR_FULL: 3, 1126 | }; 1127 | 1128 | // Abstract webpack modules 1129 | const modules = { 1130 | get members() { return this._members ?? (this._members = runtime.api.Webpack.getByKeys('membersWrap', 'hiddenMembers', 'roleIcon')); }, 1131 | get icons() { return this._icons ?? (this._icons = runtime.api.Webpack.getByKeys('selected', 'iconWrapper', 'clickable', 'icon')); }, 1132 | get dispatcher() { return this._dispatcher ?? (this._dispatcher = runtime.api.Webpack.getByKeys('dispatch', 'isDispatching')); }, 1133 | get social() { return this._social ?? (this._social = runtime.api.Webpack.getByKeys('inviteToolbar', 'peopleColumn', 'addFriend')); }, 1134 | get toolbar() { return this._toolbar ?? (this._toolbar = runtime.api.Webpack.getByKeys('updateIconForeground', 'search', 'forumOrHome')); }, 1135 | get panel() { return this._panel ?? (this._panel = runtime.api.Webpack.getByKeys('outer', 'inner', 'overlay')); }, 1136 | get guilds() { return this._guilds ?? (this._guilds = runtime.api.Webpack.getByKeys('chatContent', 'noChat', 'parentChannelName', 'linkedLobby')); }, 1137 | get frame() { return this._frame ?? (this._frame = runtime.api.Webpack.getByKeys('bar', 'title', 'winButtons')); }, 1138 | get calls() { return this._calls ?? (this._calls = runtime.api.Webpack.getByKeys('wrapper', 'fullScreen', 'callContainer')); }, 1139 | get threads() { return this._threads ?? (this._threads = runtime.api.Webpack.getByKeys('uploadArea', 'newMemberBanner', 'mainCard', 'newPostsButton')); }, 1140 | get user() { return this._user ?? (this._user = runtime.api.Webpack.getByKeys('avatar', 'nameTag', 'customStatus', 'emoji', 'buttons')); }, 1141 | get input() { return this._input ?? (this._input = runtime.api.Webpack.getByKeys('channelTextArea', 'accessoryBar', 'emojiButton')); }, 1142 | get popout() { return this._popout ?? (this._popout = runtime.api.Webpack.getByKeys('chatLayerWrapper', 'container', 'chatTarget')); }, 1143 | get sidebar() { return this._sidebar ?? (this._sidebar = runtime.api.Webpack.getByKeys('sidebar', 'activityPanel', 'sidebarListRounded')); }, 1144 | get effects() { return this._effects ?? (this._effects = runtime.api.Webpack.getByKeys('profileEffects', 'hovered', 'effect')); }, 1145 | get search() { return this._search ?? (this._search = runtime.api.Webpack.getByKeys('searchResultsWrap', 'stillIndexing', 'noResults')); }, 1146 | get tooltip() { return this._tooltip ?? (this._tooltip = runtime.api.Webpack.getByKeys('menu', 'label', 'caret')); }, 1147 | get preview() { return this._preview ?? (this._preview = runtime.api.Webpack.getByKeys('popout', 'more', 'title', 'timestamp', 'name')); }, 1148 | get channels() { return this._channels ?? (this._channels = runtime.api.Webpack.getByKeys('channel', 'closeIcon', 'dm')); }, 1149 | get activity() { return this._activity ?? (this._activity = runtime.api.Webpack.getByKeys('itemCard', 'emptyCard', 'emptyText')); }, 1150 | get game() { return this._game ?? (this._game = runtime.api.Webpack.getByKeys('openOnHover', 'userSection', 'thumbnail')); }, 1151 | get callIcons() { return this._callIcons ?? (this._callIcons = runtime.api.Webpack.getByKeys('button', 'divider', 'lastButton')); }, 1152 | get callButtons() { return this._callButtons ?? (this._callButtons = runtime.api.Webpack.getByKeys('controlButton', 'wrapper', 'buttonContainer')); }, 1153 | get userAreaButtons() { return this._userAreaButtons ?? (this._userAreaButtons = runtime.api.Webpack.getByKeys('actionButtons', 'micTestButton', 'buttonIcon')); }, 1154 | get scroller() { return this._scroller ?? (this._scroller = runtime.api.Webpack.getByKeys('wrapper', 'scroller', 'discoveryIcon')); }, 1155 | }; 1156 | 1157 | const elements = { 1158 | get inviteToolbar() { return document.querySelector(`.${modules.social?.inviteToolbar}`); }, 1159 | get searchBar() { return document.querySelector(`.${modules.toolbar?.search}`); }, 1160 | get toolbar() { return document.querySelector(`.${modules.icons?.toolbar}`); }, 1161 | get membersList() { return document.querySelector(`.${modules.members?.membersWrap}`); }, 1162 | get userProfile() { return document.querySelector(`.${modules.guilds?.content} .${modules.panel?.inner}`); }, 1163 | get messageInput() { return document.querySelector(`.${modules.guilds?.form}`); }, 1164 | get windowBar() { return document.querySelector(`.${modules.frame?.bar}`); }, 1165 | get callWindow() { return document.querySelector(`.${modules.calls?.wrapper}:not(.${modules.calls?.noChat})`); }, 1166 | get settingsContainer() { return [...document.querySelectorAll(`.${modules.user?.buttons}`)].slice(-1)[0]; }, 1167 | get messageInputContainer() { return [...document.querySelectorAll(`.${modules.input?.buttons}`)].slice(-1)[0]; }, 1168 | get forumPopout() { return document.querySelector(`.${modules.popout?.chatLayerWrapper}`); }, 1169 | get biteSizePanel() { return document.querySelector(`[role="dialog"] .${modules.panel?.outer}`); }, 1170 | get userArea() { return document.querySelector(`.${modules.sidebar?.panels}`); }, 1171 | get serverList() { return document.querySelector(`.${modules.sidebar?.guilds}`); }, 1172 | get channelList() { return document.querySelector(`.${modules.sidebar?.sidebarList}`); }, 1173 | get rightClickMenu() { return document.querySelector(`.${modules.tooltip?.menu}`); }, 1174 | get forumPreviewTooltip() { return document.querySelector(`.${modules.preview?.popout}`); }, 1175 | get bdPluginFolderButton() { return document.querySelector('button:has([d="M10 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"])'); }, 1176 | get searchPanel() { return document.querySelector(`.${modules.search?.searchResultsWrap}`); }, 1177 | get activityPanel() { return document.querySelector(`.${modules.social?.nowPlayingColumn}`); }, 1178 | get chatWrapper() { return document.querySelector(`.${modules.guilds?.content}`); }, 1179 | get noChat() { return document.querySelector(`.${modules.calls?.noChat}`); }, 1180 | get expressionPicker() { return document.querySelector(`.${modules.input?.expressionPickerPositionLayer}`); }, 1181 | get index() { 1182 | return [ 1183 | this.serverList, 1184 | this.channelList, 1185 | this.membersList, 1186 | this.userProfile, 1187 | this.messageInput, 1188 | this.windowBar, 1189 | this.callWindow, 1190 | this.userArea, 1191 | this.searchPanel, 1192 | this.forumPopout, 1193 | this.activityPanel, 1194 | ]; 1195 | }, 1196 | }; 1197 | 1198 | // Declare runtime object structure 1199 | const runtime = { 1200 | meta: null, 1201 | api: null, 1202 | plugin: null, 1203 | notice: null, 1204 | toolbar: null, 1205 | dragging: null, 1206 | interval: null, 1207 | threadsLoaded: false, 1208 | collapsed: [false, false, false, false, false, false, false, false, false, false, false], 1209 | keys: new Set(), 1210 | lastKeypress: Date.now(), 1211 | 1212 | // Controls all event listeners 1213 | get controller() { 1214 | if (this._controller && this._controller.signal.aborted) this._controller = null; 1215 | return this._controller ?? (this._controller = new AbortController()); 1216 | }, 1217 | 1218 | // Scans for changes that require a toolbar/style reload 1219 | get observer() { 1220 | return this._observer ?? (this._observer = new MutationObserver((mutationList) => { 1221 | mutationList.forEach((mutation) => { 1222 | mutation.addedNodes.forEach((node) => { 1223 | if (node.classList?.contains(modules.panel?.outer) 1224 | || node.classList?.contains(modules.search?.searchResultsWrap) 1225 | || node.classList?.contains(modules.popout?.chatLayerWrapper)) { 1226 | this.plugin.partialReload(); 1227 | } 1228 | }); 1229 | mutation.removedNodes.forEach((node) => { 1230 | if (node.classList?.contains(modules.panel?.outer) 1231 | || node.classList?.contains(modules.search?.searchResultsWrap) 1232 | || node.classList?.contains(modules.popout?.chatLayerWrapper)) { 1233 | this.plugin.partialReload(); 1234 | } 1235 | }); 1236 | }); 1237 | })); 1238 | }, 1239 | }; 1240 | 1241 | // Abstract stylesheet application 1242 | const styleFunctions = { 1243 | 1244 | // This element's toggle state 1245 | _toggled: true, 1246 | 1247 | // Add initial element styles 1248 | init: function () { 1249 | if (this._init) runtime.api.DOM.addStyle(...this._init); 1250 | if (this._float 1251 | && settings.floatingPanels 1252 | && settings.floatingEnabled[this._index] === 'always') 1253 | runtime.api.DOM.addStyle(...this._float); 1254 | }, 1255 | 1256 | // Toggle this element's collapsed state 1257 | toggle: function () { 1258 | if (!settings.collapseDisabledButtons && settings.buttonIndexes[this._index] === 0) { 1259 | this._toggled = !this._toggled; 1260 | return; 1261 | } 1262 | 1263 | styles.update(); 1264 | 1265 | if ((!settings.expandOnHover) || (!settings.expandOnHoverEnabled[this._index])) { 1266 | if (this._toggled) runtime.api.DOM.addStyle(...this._toggle); 1267 | else runtime.api.DOM.removeStyle(this._toggle[0]); 1268 | } 1269 | else { 1270 | if (this._toggled) { 1271 | runtime.api.DOM.addStyle(`${this._toggle[0]}_dynamic`, this._toggle[1]); 1272 | if (this._float 1273 | && settings.floatingPanels 1274 | && settings.floatingEnabled[this._index] === 'hover') 1275 | setTimeout(() => runtime.api.DOM.addStyle(...this._float), settings.transitionSpeed); 1276 | } 1277 | else { 1278 | runtime.api.DOM.removeStyle(`${this._toggle[0]}_dynamic`); 1279 | if (this._float 1280 | && settings.floatingPanels 1281 | && settings.floatingEnabled[this._index] === 'hover') 1282 | runtime.api.DOM.removeStyle(this._float[0]); 1283 | } 1284 | } 1285 | 1286 | this._toggled = !this._toggled; 1287 | }, 1288 | 1289 | // Make this element float above other UI elements 1290 | float: function () { 1291 | if (this._float) runtime.api.DOM.addStyle(...this._float); 1292 | }, 1293 | 1294 | // Remove all custom styles from this element 1295 | clear: function () { 1296 | if (this._clear) this._clear(); 1297 | 1298 | if (this._init) runtime.api.DOM.removeStyle(this._init[0]); 1299 | if (this._float) runtime.api.DOM.removeStyle(this._float[0]); 1300 | if (this._queryToggle) runtime.api.DOM.removeStyle(this._queryToggle[0]); 1301 | 1302 | if (this._toggle) { 1303 | runtime.api.DOM.removeStyle(this._toggle[0]); 1304 | runtime.api.DOM.removeStyle(`${this._toggle[0]}_dynamic`); 1305 | } 1306 | 1307 | if (this.__init) delete this.__init; 1308 | if (this.__float) delete this.__float; 1309 | if (this.__queryToggle) delete this.__queryToggle; 1310 | if (this.__toggle) delete this.__toggle; 1311 | 1312 | this._toggled = true; 1313 | }, 1314 | }; 1315 | 1316 | // Define static styles 1317 | const styles = { 1318 | 1319 | // Collapsible panels 1320 | collapse: [ 1321 | // Server list [I_SERVER_LIST] 1322 | { 1323 | _index: constants.I_SERVER_LIST, 1324 | get _init() { 1325 | return this.__init ?? (this.__init = [`${runtime.meta.name}-serverList_init`, ` 1326 | :root { 1327 | --cui-server-list-toggled: 1; 1328 | } 1329 | 1330 | .${modules.sidebar?.guilds} { 1331 | transition: width var(--cui-transition-speed); 1332 | border-right: calc(1px * var(--cui-channel-list-toggled)) solid var(--border-subtle) !important; 1333 | border-top: 1px solid var(--app-border-frame) !important; 1334 | } 1335 | 1336 | .${modules.scroller?.tree} { 1337 | padding-top: var(--space-xs) !important; 1338 | } 1339 | 1340 | .${modules.sidebar?.content} { 1341 | transition: margin-top var(--cui-transition-speed); 1342 | } 1343 | 1344 | ${(settings.sizeCollapse) 1345 | ? ` 1346 | @media ${this.query} { 1347 | ${this._toggle[1]} 1348 | } 1349 | ` 1350 | : ''} 1351 | `.replace(/\s+/g, ' ')]); 1352 | }, 1353 | get _toggle() { 1354 | return this.__toggle ?? (this.__toggle = [`${runtime.meta.name}-serverList_toggle`, ` 1355 | :root { 1356 | --cui-server-list-toggled: 0; 1357 | } 1358 | 1359 | .${modules.sidebar?.guilds} { 1360 | width: var(--cui-collapse-size) !important; 1361 | border: 0 !important; 1362 | } 1363 | 1364 | .${modules.sidebar?.content} { 1365 | margin-top: 0px !important; 1366 | } 1367 | `.replace(/\s+/g, ' ')]); 1368 | }, 1369 | get _float() { 1370 | return this.__float ?? (this.__float = [`${runtime.meta.name}-serverList_float`, ` 1371 | :root { 1372 | --cui-server-list-toggled: 0; 1373 | } 1374 | 1375 | .${modules.sidebar?.guilds} { 1376 | position: absolute !important; 1377 | z-index: 192 !important; 1378 | min-height: 100% !important; 1379 | height: 100% !important; 1380 | max-height: 100% !important; 1381 | overflow-y: scroll !important; 1382 | max-height: ${runtime.api.Themes.isEnabled('Horizontal Server List') ? '100vw' : '100%'} !important; 1383 | } 1384 | `.replace(/\s+/g, ' ')]); 1385 | }, 1386 | get query() { 1387 | return runtime.api.Themes.isEnabled('Horizontal Server List') 1388 | ? `(max-height: ${settings.sizeCollapseThreshold[this._index]}px)` 1389 | : `(max-width: ${settings.sizeCollapseThreshold[this._index]}px)`; 1390 | }, 1391 | get _queryToggle() { 1392 | return this.__queryToggle ?? (this.__queryToggle = [`${runtime.meta.name}-serverList_queryToggle`, ` 1393 | ${(settings.sizeCollapse) 1394 | ? ` 1395 | @media ${this.query} { 1396 | .${modules.sidebar?.guilds} { 1397 | width: var(--custom-guild-list-width) !important; 1398 | } 1399 | 1400 | .${modules.sidebar?.base} { 1401 | --server-container: inherit; 1402 | } 1403 | } 1404 | ` 1405 | : ''} 1406 | `.replace(/\s+/g, ' ')]); 1407 | }, 1408 | ...styleFunctions, 1409 | }, 1410 | 1411 | // Channel list [I_CHANNEL_LIST] 1412 | { 1413 | _index: constants.I_CHANNEL_LIST, 1414 | get _init() { 1415 | return this.__init ?? (this.__init = [`${runtime.meta.name}-channelList_init`, ` 1416 | :root { 1417 | --cui-channel-list-handle-offset: calc(var(--cui-channel-list-width) - 12px); 1418 | --cui-channel-list-handle-transition: left var(--cui-transition-speed); 1419 | --cui-channel-list-toggled: 1; 1420 | } 1421 | 1422 | .${modules.sidebar?.sidebarList} { 1423 | max-width: var(--cui-channel-list-width) !important; 1424 | width: var(--cui-channel-list-width) !important; 1425 | min-width: var(--cui-channel-list-width) !important; 1426 | transition: max-width var(--cui-transition-speed), width var(--cui-transition-speed), min-width var(--cui-transition-speed); 1427 | min-height: 100% !important; 1428 | overflow: visible !important; 1429 | border-right: 1px solid var(--border-subtle) !important; 1430 | border-left: none !important; 1431 | border-radius: 0 !important; 1432 | } 1433 | 1434 | .${modules.sidebar?.sidebarList} > * { 1435 | overflow: hidden !important; 1436 | margin-left: 0 !important; 1437 | } 1438 | 1439 | .${modules.sidebar?.sidebarResizeHandle} { 1440 | display: none !important; 1441 | } 1442 | 1443 | .${modules.sidebar?.sidebar} { 1444 | overflow: visible !important; 1445 | border: none !important; 1446 | } 1447 | 1448 | .${modules.guilds?.subtitleContainer}, 1449 | .${modules.guilds?.content}, 1450 | .${modules.social?.tabBody} { 1451 | border-left: none !important; 1452 | } 1453 | 1454 | .${modules.channels?.channel} { 1455 | max-width: 100% !important; 1456 | } 1457 | 1458 | .${modules.icons?.container} { 1459 | border-left: 0 !important; 1460 | } 1461 | 1462 | ${(settings.channelListWidth) 1463 | ? ` 1464 | .${modules.sidebar?.sidebarList}:before { 1465 | cursor: e-resize; 1466 | z-index: 200; 1467 | position: absolute; 1468 | content: ""; 1469 | width: 16px; 1470 | height: 100%; 1471 | left: var(--cui-channel-list-handle-offset); 1472 | transition: var(--cui-channel-list-handle-transition); 1473 | } 1474 | ` 1475 | : ''} 1476 | 1477 | ${(settings.sizeCollapse) 1478 | ? ` 1479 | @media ${this.query} { 1480 | ${this._toggle[1]} 1481 | } 1482 | ` 1483 | : ''} 1484 | `.replace(/\s+/g, ' ')]); 1485 | }, 1486 | get _toggle() { 1487 | return this.__toggle ?? (this.__toggle = [`${runtime.meta.name}-channelList_toggle`, ` 1488 | :root { 1489 | --cui-channel-list-toggled: 0; 1490 | } 1491 | 1492 | .${modules.sidebar?.sidebarList} { 1493 | max-width: var(--cui-collapse-size) !important; 1494 | width: var(--cui-collapse-size) !important; 1495 | min-width: var(--cui-collapse-size) !important; 1496 | } 1497 | 1498 | ${(settings.channelListWidth) 1499 | ? ` 1500 | .${modules.sidebar?.sidebarList}:before { 1501 | left: -4px; 1502 | } 1503 | ` 1504 | : ''} 1505 | `.replace(/\s+/g, ' ')]); 1506 | }, 1507 | get _float() { 1508 | return this.__float ?? (this.__float = [`${runtime.meta.name}-channelList_float`, ` 1509 | .${modules.sidebar?.sidebarList} { 1510 | position: absolute !important; 1511 | z-index: 190 !important; 1512 | max-height: 100% !important; 1513 | height: 100% !important; 1514 | } 1515 | `.replace(/\s+/g, ' ')]); 1516 | }, 1517 | get query() { return `(max-width: ${settings.sizeCollapseThreshold[this._index]}px)`; }, 1518 | get _queryToggle() { 1519 | return this.__queryToggle ?? (this.__queryToggle = [`${runtime.meta.name}-channelList_queryToggle`, ` 1520 | ${(settings.sizeCollapse) 1521 | ? ` 1522 | @media ${this.query} { 1523 | :root { 1524 | --cui-channel-list-toggled: 1; 1525 | } 1526 | 1527 | .${modules.sidebar?.sidebarList} { 1528 | max-width: var(--cui-channel-list-width) !important; 1529 | width: var(--cui-channel-list-width) !important; 1530 | min-width: var(--cui-channel-list-width) !important; 1531 | } 1532 | 1533 | ${(settings.channelListWidth) 1534 | ? ` 1535 | .${modules.sidebar?.sidebarList}:before { 1536 | left: calc(var(--cui-channel-list-width) - 4px); 1537 | } 1538 | ` 1539 | : ''} 1540 | } 1541 | ` 1542 | : ''} 1543 | `.replace(/\s+/g, ' ')]); 1544 | }, 1545 | ...styleFunctions, 1546 | }, 1547 | 1548 | // Members list [I_MEMBERS_LIST] 1549 | { 1550 | _index: constants.I_MEMBERS_LIST, 1551 | get _init() { 1552 | return this.__init ?? (this.__init = [`${runtime.meta.name}-membersList_init`, ` 1553 | .${modules.members?.membersWrap} { 1554 | max-width: var(--cui-members-list-width) !important; 1555 | width: var(--cui-members-list-width) !important; 1556 | min-width: var(--cui-members-list-width) !important; 1557 | transition: max-width var(--cui-transition-speed), width var(--cui-transition-speed), min-width var(--cui-transition-speed), padding var(--cui-transition-speed); 1558 | min-height: 100% !important; 1559 | } 1560 | 1561 | .${modules.members?.membersWrap} > * { 1562 | width: 100% !important; 1563 | } 1564 | 1565 | .${modules.members?.member}, 1566 | .${modules.game?.container} { 1567 | max-width: 100% !important; 1568 | } 1569 | 1570 | ${(settings.membersListWidth) 1571 | ? ` 1572 | .${modules.members?.membersWrap}:before { 1573 | cursor: e-resize; 1574 | z-index: 200; 1575 | position: absolute; 1576 | content: ""; 1577 | width: 16px; 1578 | height: 100%; 1579 | left: -4px; 1580 | } 1581 | ` 1582 | : ''} 1583 | 1584 | ${(settings.sizeCollapse) 1585 | ? ` 1586 | @media ${this.query} { 1587 | ${this._toggle[1]} 1588 | } 1589 | ` 1590 | : ''} 1591 | `.replace(/\s+/g, ' ')]); 1592 | }, 1593 | get _toggle() { 1594 | return this.__toggle ?? (this.__toggle = [`${runtime.meta.name}-membersList_toggle`, ` 1595 | .${modules.members?.membersWrap} { 1596 | max-width: var(--cui-collapse-size) !important; 1597 | width: var(--cui-collapse-size) !important; 1598 | min-width: var(--cui-collapse-size) !important; 1599 | padding-left: 0 !important; 1600 | padding-right: 0 !important; 1601 | } 1602 | `.replace(/\s+/g, ' ')]); 1603 | }, 1604 | get _float() { 1605 | return this.__float ?? (this.__float = [`${runtime.meta.name}-membersList_float`, ` 1606 | .${modules.members?.membersWrap} { 1607 | position: absolute !important; 1608 | z-index: 190 !important; 1609 | max-height: 100% !important; 1610 | height: 100% !important; 1611 | right: 0 !important; 1612 | border-left: 1px solid var(--border-subtle) !important; 1613 | } 1614 | `.replace(/\s+/g, ' ')]); 1615 | }, 1616 | get query() { return `(max-width: ${settings.sizeCollapseThreshold[this._index]}px)`; }, 1617 | get _queryToggle() { 1618 | return this.__queryToggle ?? (this.__queryToggle = [`${runtime.meta.name}-membersList_queryToggle`, ` 1619 | ${(settings.sizeCollapse) 1620 | ? ` 1621 | @media ${this.query} { 1622 | .${modules.members?.membersWrap} { 1623 | max-width: var(--cui-members-list-width) !important; 1624 | width: var(--cui-members-list-width) !important; 1625 | min-width: var(--cui-members-list-width) !important; 1626 | } 1627 | } 1628 | ` 1629 | : ''} 1630 | `.replace(/\s+/g, ' ')]); 1631 | }, 1632 | ...styleFunctions, 1633 | }, 1634 | 1635 | // User profile [I_USER_PROFILE] 1636 | { 1637 | _index: constants.I_USER_PROFILE, 1638 | get _init() { 1639 | if (document.querySelector(`.${modules.panel?.outer} header > svg`)) document.querySelector(`.${modules.panel?.outer} header > svg`).style.maxHeight = document.querySelector(`.${modules.panel?.outer} header > svg`).style.minHeight; 1640 | document.querySelector(`.${modules.panel?.outer} header > svg > mask > rect`)?.setAttribute('width', '500%'); 1641 | document.querySelector(`.${modules.panel?.outer} header > svg`)?.removeAttribute('viewBox'); 1642 | return this.__init ?? (this.__init = [`${runtime.meta.name}-userProfile_init`, ` 1643 | .${modules.guilds?.content} .${modules.panel?.outer} { 1644 | max-width: var(--cui-user-profile-width) !important; 1645 | width: var(--cui-user-profile-width) !important; 1646 | min-width: var(--cui-user-profile-width) !important; 1647 | transition: max-width var(--cui-transition-speed), width var(--cui-transition-speed), min-width var(--cui-transition-speed); 1648 | min-height: 100% !important; 1649 | } 1650 | 1651 | .${modules.guilds?.content} .${modules.panel?.outer} .${modules.panel?.inner} { 1652 | border-left: 1px solid var(--border-subtle) !important; 1653 | } 1654 | 1655 | .${modules.guilds?.content} .${modules.panel?.outer} > * { 1656 | width: 100% !important; 1657 | } 1658 | 1659 | .${modules.guilds?.content} .${modules.panel?.outer} header > svg { 1660 | min-width: 100% !important; 1661 | } 1662 | 1663 | .${modules.guilds?.content} .${modules.panel?.outer} header > svg > mask > rect { 1664 | width: 500% !important; 1665 | } 1666 | 1667 | .${modules.guilds?.content} .${modules.panel?.outer} .${modules.effects?.effect} { 1668 | min-height: 100% !important; 1669 | } 1670 | 1671 | ${(settings.userProfileWidth) 1672 | ? ` 1673 | .${modules.guilds?.content} .${modules.panel?.outer}:before { 1674 | cursor: e-resize; 1675 | z-index: 200; 1676 | position: absolute; 1677 | content: ""; 1678 | width: 16px; 1679 | height: 100%; 1680 | left: -4px; 1681 | } 1682 | ` 1683 | : ''} 1684 | 1685 | ${(settings.sizeCollapse) 1686 | ? ` 1687 | @media ${this.query} { 1688 | ${this._toggle[1]} 1689 | } 1690 | ` 1691 | : ''} 1692 | `.replace(/\s+/g, ' ')]); 1693 | }, 1694 | get _toggle() { 1695 | return this.__toggle ?? (this.__toggle = [`${runtime.meta.name}-userProfile_toggle`, ` 1696 | .${modules.guilds?.content} .${modules.panel?.outer} { 1697 | max-width: var(--cui-collapse-size) !important; 1698 | width: var(--cui-collapse-size) !important; 1699 | min-width: var(--cui-collapse-size) !important; 1700 | } 1701 | `.replace(/\s+/g, ' ')]); 1702 | }, 1703 | get _float() { 1704 | return this.__float ?? (this.__float = [`${runtime.meta.name}-userProfile_float`, ` 1705 | .${modules.guilds?.content} .${modules.panel?.outer} { 1706 | position: absolute !important; 1707 | z-index: 190 !important; 1708 | max-height: 100% !important; 1709 | height: 100% !important; 1710 | right: 0 !important; 1711 | } 1712 | `.replace(/\s+/g, ' ')]); 1713 | }, 1714 | get query() { return `(max-width: ${settings.sizeCollapseThreshold[this._index]}px)`; }, 1715 | get _queryToggle() { 1716 | return this.__queryToggle ?? (this.__queryToggle = [`${runtime.meta.name}-userProfile_queryToggle`, ` 1717 | ${(settings.sizeCollapse) 1718 | ? ` 1719 | @media ${this.query} { 1720 | .${modules.guilds?.content} .${modules.panel?.outer} { 1721 | max-width: var(--cui-user-profile-width) !important; 1722 | width: var(--cui-user-profile-width) !important; 1723 | min-width: var(--cui-user-profile-width) !important; 1724 | } 1725 | } 1726 | ` 1727 | : ''} 1728 | `.replace(/\s+/g, ' ')]); 1729 | }, 1730 | _clear: function () { 1731 | document.querySelector(`.${modules.guilds?.content} .${modules.panel?.outer} header > svg`)?.style.removeProperty('max-height'); 1732 | document.querySelector(`.${modules.guilds?.content} .${modules.panel?.outer} header > svg > mask > rect`)?.setAttribute('width', '100%'); 1733 | document.querySelector(`.${modules.guilds?.content} .${modules.panel?.outer} header > svg`)?.setAttribute('viewBox', `0 0 ${parseInt(document.querySelector(`.${modules.panel?.outer} header > svg`)?.style.minWidth)} ${parseInt(document.querySelector(`.${modules.panel?.outer} header > svg`)?.style.minHeight)}`); 1734 | }, 1735 | ...styleFunctions, 1736 | }, 1737 | 1738 | // Message input [I_MESSAGE_INPUT] 1739 | { 1740 | _index: constants.I_MESSAGE_INPUT, 1741 | get _init() { 1742 | return this.__init ?? (this.__init = [`${runtime.meta.name}-messageInput_init`, ` 1743 | .${modules.guilds?.form} { 1744 | max-height: calc(var(--custom-channel-textarea-text-area-max-height) + 24px) !important; 1745 | transition: max-height var(--cui-transition-speed) !important; 1746 | } 1747 | 1748 | ${(settings.sizeCollapse) 1749 | ? ` 1750 | @media ${this.query} { 1751 | ${this._toggle[1]} 1752 | } 1753 | ` 1754 | : ''} 1755 | `.replace(/\s+/g, ' ')]); 1756 | }, 1757 | get _toggle() { 1758 | return this.__toggle ?? (this.__toggle = [`${runtime.meta.name}-messageInput_toggle`, ` 1759 | .${modules.guilds?.form}:not(:has([data-slate-string="true"])):not(:has([data-list-id="attachments"])) { 1760 | max-height: var(--cui-collapse-size) !important; 1761 | overflow: hidden !important; 1762 | } 1763 | `.replace(/\s+/g, ' ')]); 1764 | }, 1765 | get _float() { 1766 | return this.__float ?? (this.__float = [`${runtime.meta.name}-messageInput_float`, ` 1767 | .${modules.guilds?.form} { 1768 | position: absolute !important; 1769 | filter: drop-shadow(0px 0px 2px var(--opacity-black-16)); 1770 | left: 0 !important; 1771 | right: 0 !important; 1772 | bottom: 0 !important; 1773 | } 1774 | `.replace(/\s+/g, ' ')]); 1775 | }, 1776 | get query() { return `(max-height: ${settings.sizeCollapseThreshold[this._index]}px)`; }, 1777 | get _queryToggle() { 1778 | return this.__queryToggle ?? (this.__queryToggle = [`${runtime.meta.name}-messageInput_queryToggle`, ` 1779 | ${(settings.sizeCollapse) 1780 | ? ` 1781 | @media ${this.query} { 1782 | .${modules.guilds?.form}.${modules.guilds?.form}.${modules.guilds?.form}.${modules.guilds?.form}.${modules.guilds?.form} { 1783 | max-height: calc(var(--custom-channel-textarea-text-area-max-height) + 24px) !important; 1784 | } 1785 | } 1786 | ` 1787 | : ''} 1788 | `.replace(/\s+/g, ' ')]); 1789 | }, 1790 | ...styleFunctions, 1791 | }, 1792 | 1793 | // Window bar [I_WINDOW_BAR] 1794 | { 1795 | _index: constants.I_WINDOW_BAR, 1796 | get _init() { 1797 | return this.__init ?? (this.__init = [`${runtime.meta.name}-windowBar_init`, ` 1798 | .${modules.frame?.bar} { 1799 | min-height: var(--custom-app-top-bar-height) !important; 1800 | height: var(--custom-app-top-bar-height) !important; 1801 | max-height: var(--custom-app-top-bar-height) !important; 1802 | transition: min-height var(--cui-transition-speed), height var(--cui-transition-speed), max-height var(--cui-transition-speed) !important; 1803 | } 1804 | 1805 | .${modules.sidebar?.base} { 1806 | transition: grid-template-rows var(--cui-transition-speed) !important; 1807 | } 1808 | 1809 | ${(settings.sizeCollapse) 1810 | ? ` 1811 | @media ${this.query} { 1812 | ${this._toggle[1]} 1813 | } 1814 | ` 1815 | : ''} 1816 | `.replace(/\s+/g, ' ')]); 1817 | }, 1818 | get _toggle() { 1819 | return this.__toggle ?? (this.__toggle = [`${runtime.meta.name}-windowBar_toggle`, ` 1820 | .${modules.frame?.bar} { 1821 | overflow: hidden !important; 1822 | min-height: var(--cui-collapse-size) !important; 1823 | height: var(--cui-collapse-size) !important; 1824 | max-height: var(--cui-collapse-size) !important; 1825 | --custom-app-top-bar-height: calc(24px + var(--space-8)); 1826 | } 1827 | 1828 | .${modules.sidebar?.base} { 1829 | --custom-app-top-bar-height: var(--cui-collapse-size); 1830 | } 1831 | `.replace(/\s+/g, ' ')]); 1832 | }, 1833 | get _float() { 1834 | return this.__float ?? (this.__float = [`${runtime.meta.name}-windowBar_float`, ` 1835 | .${modules.frame?.bar} { 1836 | position: absolute !important; 1837 | top: 0 !important; 1838 | left: 0 !important; 1839 | right: 0 !important; 1840 | background: var(--background-tertiary) !important; 1841 | z-index: 200 !important; 1842 | --custom-app-top-bar-height: calc(24px + var(--space-8)); 1843 | border-bottom: 1px solid var(--app-border-frame) !important; 1844 | } 1845 | 1846 | .${modules.sidebar?.base} { 1847 | --custom-app-top-bar-height: var(--cui-collapse-size); 1848 | } 1849 | `.replace(/\s+/g, ' ')]); 1850 | }, 1851 | get query() { return `(max-height: ${settings.sizeCollapseThreshold[this._index]}px)`; }, 1852 | get _queryToggle() { 1853 | return this.__queryToggle ?? (this.__queryToggle = [`${runtime.meta.name}-windowBar_queryToggle`, ` 1854 | ${(settings.sizeCollapse) 1855 | ? ` 1856 | @media ${this.query} { 1857 | .${modules.frame?.bar} { 1858 | min-height: var(--custom-app-top-bar-height) !important; 1859 | height: var(--custom-app-top-bar-height) !important; 1860 | max-height: var(--custom-app-top-bar-height) !important; 1861 | } 1862 | 1863 | .${modules.sidebar?.base} { 1864 | --custom-app-top-bar-height: calc(24px + var(--space-sm)); 1865 | } 1866 | } 1867 | ` 1868 | : ''} 1869 | `.replace(/\s+/g, ' ')]); 1870 | }, 1871 | ...styleFunctions, 1872 | }, 1873 | 1874 | // Call window [I_CALL_WINDOW] 1875 | { 1876 | _index: constants.I_CALL_WINDOW, 1877 | get _init() { 1878 | return this.__init ?? (this.__init = [`${runtime.meta.name}-callWindow_init`, ` 1879 | .${modules.calls?.wrapper}:not(.${modules.calls?.noChat}) { 1880 | transition: min-height var(--cui-transition-speed), max-height var(--cui-transition-speed) !important; 1881 | } 1882 | 1883 | .${modules.calls?.wrapper}:not(.${modules.calls?.noChat}) > .${modules.calls?.callContainer} { 1884 | border-left: none !important; 1885 | border-top: none !important; 1886 | border-bottom: 1px solid var(--border-subtle) !important; 1887 | } 1888 | 1889 | ${(settings.sizeCollapse) 1890 | ? ` 1891 | @media ${this.query} { 1892 | ${this._toggle[1]} 1893 | } 1894 | ` 1895 | : ''} 1896 | `.replace(/\s+/g, ' ')]); 1897 | }, 1898 | get _toggle() { 1899 | return this.__toggle ?? (this.__toggle = [`${runtime.meta.name}-callWindow_toggle`, ` 1900 | .${modules.calls?.wrapper}:not(.${modules.calls?.noChat}) { 1901 | min-height: var(--cui-collapse-size) !important; 1902 | max-height: var(--cui-collapse-size) !important; 1903 | } 1904 | `.replace(/\s+/g, ' ')]); 1905 | }, 1906 | get query() { return `(max-height: ${settings.sizeCollapseThreshold[this._index]}px)`; }, 1907 | get _queryToggle() { 1908 | return [`${runtime.meta.name}-callWindow_queryToggle`, ` 1909 | .${modules.calls?.wrapper}:not(.${modules.calls?.noChat}) { 1910 | min-height: ${elements.callWindow?.style.minHeight} !important; 1911 | max-height: ${elements.callWindow?.style.maxHeight} !important; 1912 | } 1913 | `.replace(/\s+/g, ' ')]; 1914 | }, 1915 | ...styleFunctions, 1916 | }, 1917 | 1918 | // User area [I_USER_AREA] 1919 | { 1920 | _index: constants.I_USER_AREA, 1921 | get _init() { 1922 | return this.__init ?? (this.__init = [`${runtime.meta.name}-userArea_init`, ` 1923 | .${modules.sidebar?.panels} { 1924 | transition: max-height var(--cui-transition-speed), width var(--cui-transition-speed), border var(--cui-transition-speed) !important; 1925 | max-height: ${settings.userAreaMaxHeight}px !important; 1926 | overflow: hidden !important; 1927 | width: calc((var(--cui-channel-list-width) * var(--cui-channel-list-toggled)) + (var(--custom-guild-list-width) * var(--cui-server-list-toggled) * var(--cui-compat-hsl) * (1 - var(--fst-server-list-collapsed))) - (var(--space-xs) * 2)) !important; 1928 | border-left-width: clamp(0px, calc(1px * ((var(--cui-server-list-toggled) * var(--cui-compat-hsl) * (1 - var(--fst-server-list-collapsed))) + var(--cui-channel-list-toggled))), 1px) !important; 1929 | border-right-width: clamp(0px, calc(1px * ((var(--cui-server-list-toggled) * var(--cui-compat-hsl) * (1 - var(--fst-server-list-collapsed))) + var(--cui-channel-list-toggled))), 1px) !important; 1930 | z-index: 191 !important; 1931 | } 1932 | 1933 | .${modules.userAreaButtons?.actionButtons} button { 1934 | padding: 0 !important; 1935 | } 1936 | 1937 | ${(runtime.api.Themes.isEnabled('UI Refresh Refresh')) 1938 | ? ` 1939 | .${modules.user?.avatar} { 1940 | transition: transform var(--cui-transition-speed) !important; 1941 | transform: translateX(calc(-4px * (1 - var(--cui-channel-list-toggled)))); 1942 | } 1943 | ` 1944 | : ''} 1945 | 1946 | ${(settings.sizeCollapse) 1947 | ? ` 1948 | @media ${this.query} { 1949 | ${this._toggle[1]} 1950 | } 1951 | ` 1952 | : ''} 1953 | `.replace(/\s+/g, ' ')]); 1954 | }, 1955 | get _toggle() { 1956 | return this.__toggle ?? (this.__toggle = [`${runtime.meta.name}-userArea_toggle`, ` 1957 | .${modules.sidebar?.panels} { 1958 | max-height: var(--cui-collapse-size) !important; 1959 | border-top-width: 0px !important; 1960 | border-bottom-width: 0px !important; 1961 | } 1962 | `.replace(/\s+/g, ' ')]); 1963 | }, 1964 | get query() { return `(max-height: ${settings.sizeCollapseThreshold[this._index]}px)`; }, 1965 | get _queryToggle() { 1966 | return this.__queryToggle ?? (this.__queryToggle = [`${runtime.meta.name}-userArea_queryToggle`, ` 1967 | .${modules.sidebar?.panels} { 1968 | max-height: ${settings.userAreaMaxHeight}px !important; 1969 | border-top-width: 1px !important; 1970 | border-bottom-width: 1px !important; 1971 | } 1972 | `.replace(/\s+/g, ' ')]); 1973 | }, 1974 | ...styleFunctions, 1975 | }, 1976 | 1977 | // Search panel [I_SEARCH_PANEL] 1978 | { 1979 | _index: constants.I_SEARCH_PANEL, 1980 | get _init() { 1981 | return this.__init ?? (this.__init = [`${runtime.meta.name}-searchPanel_init`, ` 1982 | .${modules.search?.searchResultsWrap} { 1983 | max-width: var(--cui-search-panel-width) !important; 1984 | width: var(--cui-search-panel-width) !important; 1985 | min-width: var(--cui-search-panel-width) !important; 1986 | transition: max-width var(--cui-transition-speed), width var(--cui-transition-speed), min-width var(--cui-transition-speed); 1987 | overflow: visible !important; 1988 | border-left: 1px solid var(--border-subtle) !important; 1989 | } 1990 | 1991 | .${modules.search?.searchResultsWrap} > header > div:last-child { 1992 | justify-content: end !important; 1993 | } 1994 | 1995 | ${(settings.searchPanelWidth) 1996 | ? ` 1997 | .${modules.search?.searchResultsWrap}:before { 1998 | cursor: e-resize; 1999 | z-index: 200; 2000 | position: absolute; 2001 | content: ""; 2002 | width: 16px; 2003 | height: 100%; 2004 | left: -4px; 2005 | } 2006 | ` 2007 | : ''} 2008 | 2009 | ${(settings.sizeCollapse) 2010 | ? ` 2011 | @media ${this.query} { 2012 | ${this._toggle[1]} 2013 | } 2014 | ` 2015 | : ''} 2016 | `.replace(/\s+/g, ' ')]); 2017 | }, 2018 | get _toggle() { 2019 | return this.__toggle ?? (this.__toggle = [`${runtime.meta.name}-searchPanel_toggle`, ` 2020 | .${modules.search?.searchResultsWrap} { 2021 | max-width: var(--cui-collapse-size) !important; 2022 | width: var(--cui-collapse-size) !important; 2023 | min-width: var(--cui-collapse-size) !important; 2024 | } 2025 | `.replace(/\s+/g, ' ')]); 2026 | }, 2027 | get _float() { 2028 | return this.__float ?? (this.__float = [`${runtime.meta.name}-searchPanel_float`, ` 2029 | .${modules.search?.searchResultsWrap} { 2030 | position: absolute !important; 2031 | z-index: 190 !important; 2032 | max-height: 100% !important; 2033 | height: 100% !important; 2034 | right: 0 !important; 2035 | } 2036 | `.replace(/\s+/g, ' ')]); 2037 | }, 2038 | get query() { return `(max-width: ${settings.sizeCollapseThreshold[this._index]}px)`; }, 2039 | get _queryToggle() { 2040 | return this.__queryToggle ?? (this.__queryToggle = [`${runtime.meta.name}-searchPanel_queryToggle`, ` 2041 | ${(settings.sizeCollapse) 2042 | ? ` 2043 | @media ${this.query} { 2044 | .${modules.search?.searchResultsWrap} { 2045 | max-width: var(--cui-search-panel-width) !important; 2046 | width: var(--cui-search-panel-width) !important; 2047 | min-width: var(--cui-search-panel-width) !important; 2048 | } 2049 | } 2050 | ` 2051 | : ''} 2052 | `.replace(/\s+/g, ' ')]); 2053 | }, 2054 | ...styleFunctions, 2055 | }, 2056 | 2057 | // Forum popout [I_FORUM_POPOUT] 2058 | { 2059 | _index: constants.I_FORUM_POPOUT, 2060 | get _init() { 2061 | return this.__init ?? (this.__init = [`${runtime.meta.name}-forumPopout_init`, ` 2062 | .${modules.popout?.chatLayerWrapper} { 2063 | max-width: var(--cui-forum-popout-width) !important; 2064 | width: var(--cui-forum-popout-width) !important; 2065 | min-width: var(--cui-forum-popout-width) !important; 2066 | transition: max-width var(--cui-transition-speed), width var(--cui-transition-speed), min-width var(--cui-transition-speed); 2067 | position: absolute !important; 2068 | z-index: 190 !important; 2069 | top: var(--cui-forum-panel-top) !important; 2070 | height: calc(100% - var(--cui-forum-panel-top)) !important; 2071 | max-height: 100% !important; 2072 | overflow: hidden !important; 2073 | --uirr-forum-panel-width: var(--cui-forum-popout-width); 2074 | } 2075 | 2076 | div:not([class])[style^="min-width"], 2077 | .${modules.popout?.resizeHandle} { 2078 | display: none !important; 2079 | } 2080 | 2081 | .${modules.popout?.container} { 2082 | border-top: 1px solid var(--border-subtle) !important; 2083 | border-left: 1px solid var(--border-subtle) !important; 2084 | } 2085 | 2086 | .${modules.popout?.chatLayerWrapper} > * { 2087 | width: 100% !important; 2088 | border-radius: 0 !important; 2089 | } 2090 | 2091 | .${modules.guilds?.threadSidebarOpen} { 2092 | flex-shrink: 999999999 !important; 2093 | } 2094 | 2095 | .${modules.calls?.noChat} .${modules.calls?.callContainer} { 2096 | border-radius: 0 !important; 2097 | } 2098 | 2099 | .${modules.callIcons?.button}, 2100 | .${modules.callIcons?.lastButton} { 2101 | margin-left: 8px !important; 2102 | margin-right: 8px !important; 2103 | } 2104 | 2105 | div:has(> .${modules.callButtons?.wrapper}) { 2106 | flex-shrink: 0 !important; 2107 | max-width: 100% !important; 2108 | } 2109 | 2110 | .${modules.callButtons?.wrapper}, 2111 | .${modules.callButtons?.wrapper} > * { 2112 | flex-wrap: wrap !important; 2113 | justify-content: center !important; 2114 | } 2115 | 2116 | .${modules.guilds?.content}, 2117 | .${modules.calls?.noChat} { 2118 | --width: var(--cui-forum-popout-width); 2119 | --transition: max-width var(--cui-transition-speed), width var(--cui-transition-speed), min-width var(--cui-transition-speed); 2120 | } 2121 | 2122 | .${modules.guilds?.content}:after, 2123 | .${modules.calls?.noChat}:after { 2124 | content: ""; 2125 | display: ${(elements.forumPopout) ? 'block' : 'none'}; 2126 | height: 100%; 2127 | max-width: var(--width); 2128 | width: var(--width); 2129 | min-width: var(--width); 2130 | transition: var(--transition); 2131 | } 2132 | 2133 | .${modules.popout?.floating} { 2134 | filter: none !important; 2135 | border-left: none !important; 2136 | } 2137 | 2138 | ${(settings.forumPopoutWidth) 2139 | ? ` 2140 | .${modules.popout?.chatLayerWrapper}:before { 2141 | cursor: e-resize; 2142 | z-index: 200; 2143 | position: absolute; 2144 | content: ""; 2145 | width: 16px; 2146 | height: 100%; 2147 | left: -4px; 2148 | } 2149 | ` 2150 | : ''} 2151 | 2152 | ${(settings.sizeCollapse) 2153 | ? ` 2154 | @media ${this.query} { 2155 | ${this._toggle[1]} 2156 | } 2157 | ` 2158 | : ''} 2159 | `.replace(/\s+/g, ' ')]); 2160 | }, 2161 | get _toggle() { 2162 | return this.__toggle ?? (this.__toggle = [`${runtime.meta.name}-forumPopout_toggle`, ` 2163 | .${modules.popout?.chatLayerWrapper} { 2164 | max-width: var(--cui-collapse-size) !important; 2165 | width: var(--cui-collapse-size) !important; 2166 | min-width: var(--cui-collapse-size) !important; 2167 | --uirr-forum-panel-width: var(--cui-collapse-size); 2168 | } 2169 | 2170 | .${modules.guilds?.content}:after, 2171 | .${modules.calls?.noChat}:after { 2172 | max-width: var(--cui-collapse-size); 2173 | width: var(--cui-collapse-size); 2174 | min-width: var(--cui-collapse-size); 2175 | } 2176 | `.replace(/\s+/g, ' ')]); 2177 | }, 2178 | get _float() { 2179 | return this.__float ?? (this.__float = [`${runtime.meta.name}-forumPopout_float`, ` 2180 | .${modules.guilds?.content}:after, 2181 | .${modules.calls?.noChat}:after { 2182 | max-width: 0 !important; 2183 | width: 0 !important; 2184 | min-width: 0 !important; 2185 | } 2186 | `.replace(/\s+/g, ' ')]); 2187 | }, 2188 | get query() { return `(max-width: ${settings.sizeCollapseThreshold[this._index]}px)`; }, 2189 | get _queryToggle() { 2190 | return this.__queryToggle ?? (this.__queryToggle = [`${runtime.meta.name}-forumPopout_queryToggle`, ` 2191 | ${(settings.sizeCollapse) 2192 | ? ` 2193 | @media ${this.query} { 2194 | .${modules.popout?.chatLayerWrapper} { 2195 | max-width: var(--cui-forum-popout-width) !important; 2196 | width: var(--cui-forum-popout-width) !important; 2197 | min-width: var(--cui-forum-popout-width) !important; 2198 | } 2199 | } 2200 | ` 2201 | : ''} 2202 | `.replace(/\s+/g, ' ')]); 2203 | }, 2204 | _clear: function () {}, 2205 | ...styleFunctions, 2206 | }, 2207 | 2208 | // Activity panel [I_ACTIVITY_PANEL] 2209 | { 2210 | _index: constants.I_ACTIVITY_PANEL, 2211 | get _init() { 2212 | return this.__init ?? (this.__init = [`${runtime.meta.name}-activityPanel_init`, ` 2213 | .${modules.social?.nowPlayingColumn} { 2214 | max-width: var(--cui-activity-panel-width) !important; 2215 | width: var(--cui-activity-panel-width) !important; 2216 | min-width: var(--cui-activity-panel-width) !important; 2217 | transition: max-width var(--cui-transition-speed), width var(--cui-transition-speed), min-width var(--cui-transition-speed); 2218 | display: initial !important; 2219 | } 2220 | 2221 | .${modules.activity?.itemCard} { 2222 | overflow: hidden !important; 2223 | } 2224 | 2225 | ${(settings.activityPanelWidth) 2226 | ? ` 2227 | .${modules.social?.nowPlayingColumn}:before { 2228 | cursor: e-resize; 2229 | z-index: 200; 2230 | position: absolute; 2231 | content: ""; 2232 | width: 16px; 2233 | height: 100%; 2234 | transform: translateX(-4px); 2235 | } 2236 | ` 2237 | : ''} 2238 | 2239 | ${(settings.sizeCollapse) 2240 | ? ` 2241 | @media ${this.query} { 2242 | ${this._toggle[1]} 2243 | } 2244 | ` 2245 | : ''} 2246 | `.replace(/\s+/g, ' ')]); 2247 | }, 2248 | get _toggle() { 2249 | return this.__toggle ?? (this.__toggle = [`${runtime.meta.name}-activityPanel_toggle`, ` 2250 | .${modules.social?.nowPlayingColumn} { 2251 | max-width: var(--cui-collapse-size) !important; 2252 | width: var(--cui-collapse-size) !important; 2253 | min-width: var(--cui-collapse-size) !important; 2254 | } 2255 | `.replace(/\s+/g, ' ')]); 2256 | }, 2257 | get _float() { 2258 | return this.__float ?? (this.__float = [`${runtime.meta.name}-activityPanel_float`, ` 2259 | .${modules.social?.nowPlayingColumn} { 2260 | position: absolute !important; 2261 | z-index: 190 !important; 2262 | right: 0 !important; 2263 | height: 100% !important; 2264 | max-height: 100% !important; 2265 | } 2266 | `.replace(/\s+/g, ' ')]); 2267 | }, 2268 | get query() { return `(max-width: ${settings.sizeCollapseThreshold[this._index]}px)`; }, 2269 | get _queryToggle() { 2270 | return this.__queryToggle ?? (this.__queryToggle = [`${runtime.meta.name}-activityPanel_queryToggle`, ` 2271 | ${(settings.sizeCollapse) 2272 | ? ` 2273 | @media ${this.query} { 2274 | .${modules.social?.nowPlayingColumn} { 2275 | max-width: var(--cui-activity-panel-width) !important; 2276 | width: var(--cui-activity-panel-width) !important; 2277 | min-width: var(--cui-activity-panel-width) !important; 2278 | } 2279 | } 2280 | ` 2281 | : ''} 2282 | `.replace(/\s+/g, ' ')]); 2283 | }, 2284 | ...styleFunctions, 2285 | }, 2286 | ], 2287 | 2288 | // Collapsible button groups 2289 | buttons: [ 2290 | // User settings buttons [I_SETTINGS_BUTTONS] 2291 | { 2292 | hidden: false, 2293 | init: function () { 2294 | runtime.api.DOM.addStyle(`${runtime.meta.name}-settingsButtons_init_col`, ` 2295 | .${modules.user?.avatarWrapper} { 2296 | flex-grow: 1 !important; 2297 | } 2298 | 2299 | .${modules.user?.buttons} { 2300 | transition: gap var(--cui-transition-speed) !important; 2301 | transform: translateX(calc((1 - var(--cui-channel-list-toggled)) * 1000000000px)); 2302 | } 2303 | 2304 | .${modules.user?.buttons} > *:not(:nth-last-child(2)):not(.gameActivityToggleButton_fd3fb5) { 2305 | transition: width var(--cui-transition-speed) !important; 2306 | overflow: hidden !important; 2307 | } 2308 | `.replace(/\s+/g, ' ')); 2309 | if (settings.collapseSettings) this.hide(); 2310 | }, 2311 | hide: function () { 2312 | runtime.api.DOM.addStyle(`${runtime.meta.name}-settingsButtons_hide_col`, ` 2313 | .${modules.user?.buttons} { 2314 | gap: 0px !important; 2315 | } 2316 | 2317 | .${modules.user?.buttons} > *:not(:nth-last-child(2)):not(.gameActivityToggleButton_fd3fb5) { 2318 | width: 0px !important; 2319 | } 2320 | `.replace(/\s+/g, ' ')); 2321 | this.hidden = true; 2322 | }, 2323 | show: function () { 2324 | runtime.api.DOM.removeStyle(`${runtime.meta.name}-settingsButtons_hide_col`); 2325 | this.hidden = false; 2326 | }, 2327 | clear: function () { 2328 | this.show(); 2329 | runtime.api.DOM.removeStyle(`${runtime.meta.name}-settingsButtons_init_col`); 2330 | }, 2331 | }, 2332 | 2333 | // Message input buttons [I_MESSAGE_INPUT_BUTTONS] 2334 | { 2335 | hidden: false, 2336 | init: function () { 2337 | runtime.api.DOM.addStyle(`${runtime.meta.name}-messageInputButtons_init_col`, ` 2338 | .${modules.input?.buttons} { 2339 | transition: gap var(--cui-transition-speed) !important; 2340 | } 2341 | 2342 | .${modules.input?.buttons} > *:not(:last-child) { 2343 | transition: max-width var(--cui-transition-speed) !important; 2344 | max-width: ${settings.messageInputButtonWidth}px !important; 2345 | overflow: hidden !important; 2346 | } 2347 | `.replace(/\s+/g, ' ')); 2348 | if (settings.messageInputCollapse) this.hide(); 2349 | }, 2350 | hide: function () { 2351 | runtime.api.DOM.addStyle(`${runtime.meta.name}-messageInputButtons_hide_col`, ` 2352 | .${modules.input?.buttons} { 2353 | gap: 0px !important; 2354 | } 2355 | 2356 | .${modules.input?.buttons} > *:not(:last-child) { 2357 | max-width: 0px !important; 2358 | } 2359 | `.replace(/\s+/g, ' ')); 2360 | this.hidden = true; 2361 | }, 2362 | show: function () { 2363 | runtime.api.DOM.removeStyle(`${runtime.meta.name}-messageInputButtons_hide_col`); 2364 | this.hidden = false; 2365 | }, 2366 | clear: function () { 2367 | this.show(); 2368 | runtime.api.DOM.removeStyle(`${runtime.meta.name}-messageInputButtons_init_col`); 2369 | }, 2370 | }, 2371 | 2372 | // Toolbar buttons [I_TOOLBAR_BUTTONS] 2373 | { 2374 | hidden: false, 2375 | init: function () { 2376 | runtime.api.DOM.addStyle(`${runtime.meta.name}-toolbarButtons_init_col`, ` 2377 | .cui-toolbar > *:not(:last-child) { 2378 | transition: width var(--cui-transition-speed) !important; 2379 | width: var(--space-32) !important; 2380 | overflow: hidden !important; 2381 | } 2382 | `.replace(/\s+/g, ' ')); 2383 | if (settings.collapseToolbar == 'cui') this.hide(); 2384 | }, 2385 | hide: function () { 2386 | runtime.api.DOM.addStyle(`${runtime.meta.name}-toolbarButtons_hide_col`, ` 2387 | .cui-toolbar { 2388 | gap: 0 !important; 2389 | } 2390 | .cui-toolbar > *:not(:last-child) { 2391 | width: 0px !important; 2392 | margin: 0px !important; 2393 | } 2394 | `.replace(/\s+/g, ' ')); 2395 | this.hidden = true; 2396 | }, 2397 | show: function () { 2398 | runtime.api.DOM.removeStyle(`${runtime.meta.name}-toolbarButtons_hide_col`); 2399 | this.hidden = false; 2400 | }, 2401 | clear: function () { 2402 | this.show(); 2403 | runtime.api.DOM.removeStyle(`${runtime.meta.name}-toolbarButtons_init_col`); 2404 | }, 2405 | }, 2406 | 2407 | // Full toolbar [I_TOOLBAR_FULL] 2408 | { 2409 | hidden: false, 2410 | init: function () { 2411 | runtime.api.DOM.addStyle(`${runtime.meta.name}-toolbarFull_init_col`, ` 2412 | .${modules.guilds?.title} .${modules.icons?.toolbar} > *:not(:last-child) { 2413 | transition: max-width var(--cui-transition-speed) !important; 2414 | max-width: ${settings.toolbarElementMaxWidth}px !important; 2415 | overflow: hidden !important; 2416 | } 2417 | `.replace(/\s+/g, ' ')); 2418 | if (settings.collapseToolbar === 'all') this.hide(); 2419 | }, 2420 | hide: function () { 2421 | // Keep expanded while typing in search bar 2422 | // Why is this classname not handled the same by Discord as other elements?? 2423 | if (document.querySelector('.public-DraftEditor-content[aria-expanded="true"]')) return; 2424 | if (document.querySelector('.public-DraftEditor-content')?.querySelector('[data-text="true"]')?.innerHTML) return; 2425 | 2426 | runtime.api.DOM.addStyle(`${runtime.meta.name}-toolbarFull_hide_col`, ` 2427 | .${modules.guilds?.title} .${modules.icons?.toolbar} > *:not(:last-child) { 2428 | max-width: 0px !important; 2429 | } 2430 | `.replace(/\s+/g, ' ')); 2431 | this.hidden = true; 2432 | }, 2433 | show: function () { 2434 | runtime.api.DOM.removeStyle(`${runtime.meta.name}-toolbarFull_hide_col`); 2435 | this.hidden = false; 2436 | }, 2437 | clear: function () { 2438 | this.show(); 2439 | runtime.api.DOM.removeStyle(`${runtime.meta.name}-toolbarFull_init_col`); 2440 | }, 2441 | }, 2442 | ], 2443 | 2444 | // Add initial element styles 2445 | init: function () { 2446 | // Add root styles 2447 | runtime.api.DOM.addStyle(`${runtime.meta.name}-root`, ` 2448 | :root { 2449 | --fst-server-list-collapsed: 0; 2450 | } 2451 | 2452 | ::-webkit-scrollbar { 2453 | width: 0px; 2454 | background: transparent; 2455 | } 2456 | 2457 | .cui-toolbar { 2458 | align-items: right; 2459 | display: flex; 2460 | gap: var(--space-xs); 2461 | transition: gap var(--cui-transition-speed) !important; 2462 | } 2463 | 2464 | .${modules.icons?.iconWrapper}.${modules.icons?.selected}:not([id*="cui"]):has([d="M14.5 8a3 3 0 1 0-2.7-4.3c-.2.4.06.86.44 1.12a5 5 0 0 1 2.14 3.08c.01.06.06.1.12.1ZM18.44 17.27c.15.43.54.73 1 .73h1.06c.83 0 1.5-.67 1.5-1.5a7.5 7.5 0 0 0-6.5-7.43c-.55-.08-.99.38-1.1.92-.06.3-.15.6-.26.87-.23.58-.05 1.3.47 1.63a9.53 9.53 0 0 1 3.83 4.78ZM12.5 9a3 3 0 1 1-6 0 3 3 0 0 1 6 0ZM2 20.5a7.5 7.5 0 0 1 15 0c0 .83-.67 1.5-1.5 1.5a.2.2 0 0 1-.2-.16c-.2-.96-.56-1.87-.88-2.54-.1-.23-.42-.15-.42.1v2.1a.5.5 0 0 1-.5.5h-8a.5.5 0 0 1-.5-.5v-2.1c0-.25-.31-.33-.42-.1-.32.67-.67 1.58-.88 2.54a.2.2 0 0 1-.2.16A1.5 1.5 0 0 1 2 20.5Z"]), 2465 | .${modules.icons?.iconWrapper}.${modules.icons?.selected}:not([id*="cui"]):has([d="M23 12.38c-.02.38-.45.58-.78.4a6.97 6.97 0 0 0-6.27-.08.54.54 0 0 1-.44 0 8.97 8.97 0 0 0-11.16 3.55c-.1.15-.1.35 0 .5.37.58.8 1.13 1.28 1.61.24.24.64.15.8-.15.19-.38.39-.73.58-1.02.14-.21.43-.1.4.15l-.19 1.96c-.02.19.07.37.23.47A8.96 8.96 0 0 0 12 21a.4.4 0 0 1 .38.27c.1.33.25.65.4.95.18.34-.02.76-.4.77L12 23a11 11 0 1 1 11-10.62ZM15.5 7.5a3.5 3.5 0 1 1-7 0 3.5 3.5 0 0 1 7 0Z"]) { 2466 | display: none; 2467 | } 2468 | 2469 | .${modules.threads?.grid}>div:first-child, 2470 | .${modules.threads?.list}>div:first-child, 2471 | .${modules.threads?.headerRow} { 2472 | min-width: 0px !important; 2473 | } 2474 | `.replace(/\s+/g, ' ')); 2475 | 2476 | // Init panel styles 2477 | for (var i = 0; i < this.collapse.length; i++) { 2478 | this.collapse[i].init(); 2479 | if (!settings.buttonsActive[i]) this.collapse[i].toggle(); 2480 | } 2481 | 2482 | // Init button group styles 2483 | this.buttons.forEach(group => group.init()); 2484 | 2485 | // Init dynamic styles 2486 | this.update(); 2487 | }, 2488 | 2489 | // Update dynamic styles 2490 | update: function () { 2491 | runtime.api.DOM.addStyle(`${runtime.meta.name}-vars`, ` 2492 | :root { 2493 | --cui-transition-speed: ${settings.transitionSpeed}ms; 2494 | --cui-collapse-size: ${settings.collapseSize}px; 2495 | 2496 | --cui-channel-list-width: ${settings.channelListWidth || settings.defaultChannelListWidth}px; 2497 | --cui-members-list-width: ${settings.membersListWidth || settings.defaultMembersListWidth}px; 2498 | --cui-user-profile-width: ${settings.userProfileWidth || settings.defaultUserProfileWidth}px; 2499 | --cui-search-panel-width: ${settings.searchPanelWidth || settings.defaultSearchPanelWidth}px; 2500 | --cui-forum-popout-width: ${settings.forumPopoutWidth || settings.defaultForumPopoutWidth}px; 2501 | --cui-activity-panel-width: ${settings.activityPanelWidth || settings.defaultActivityPanelWidth}px; 2502 | 2503 | --cui-forum-panel-top: ${(elements.noChat) ? '0px' : 'var(--custom-channel-header-height)'}; 2504 | 2505 | --cui-compat-hsl: ${(runtime.api.Themes.isEnabled('Horizontal Server List')) ? 0 : 1}; 2506 | } 2507 | `.replace(/\s+/g, ' ')); 2508 | }, 2509 | 2510 | // Remove and re-add some element styles 2511 | reinit: function () { 2512 | this.collapse.forEach((panel) => { 2513 | if (panel._clear) { 2514 | panel.clear(); 2515 | panel.init(); 2516 | if (!settings.buttonsActive[panel._index]) panel.toggle(); 2517 | } 2518 | }); 2519 | }, 2520 | 2521 | // Remove all added element styles 2522 | clear: function () { 2523 | // Clear root styles 2524 | runtime.api.DOM.removeStyle(`${runtime.meta.name}-root`); 2525 | runtime.api.DOM.removeStyle(`${runtime.meta.name}-vars`); 2526 | 2527 | // Clear panel styles 2528 | this.collapse.forEach(panel => panel.clear()); 2529 | 2530 | // Clear button group styles 2531 | this.buttons.forEach(group => group.clear()); 2532 | }, 2533 | }; 2534 | 2535 | // Export plugin class 2536 | module.exports = class CollapsibleUI { 2537 | // Get api and metadata 2538 | constructor(meta) { 2539 | runtime.meta = meta; 2540 | runtime.api = new BdApi(runtime.meta.name); 2541 | runtime.plugin = this; 2542 | } 2543 | 2544 | // Initialize the plugin when it is enabled 2545 | start = () => { 2546 | // Prevent load if plugin settings are old 2547 | if (runtime.api.Data.load('window-bar-height')) { 2548 | runtime.api.UI.showConfirmationModal('Fatal Error', `Your config file is incompatible with \ 2549 | this version of CollapsibleUI.\n\nPlease go to your BetterDiscord \ 2550 | plugins folder, delete the file \`CollapsibleUI.config.json\`, then \ 2551 | restart Discord.`, { 2552 | danger: true, 2553 | confirmText: (elements.bdPluginFolderButton) ? 'Open Plugins Folder' : 'Okay', 2554 | onConfirm: () => elements.bdPluginFolderButton?.click(), 2555 | }); 2556 | 2557 | setTimeout(() => { 2558 | runtime.api.UI.showToast('CollapsibleUI: Invalid Config Detected!', { type: 'error' }); 2559 | runtime.api.Plugins.disable(runtime.meta.name); 2560 | }, 250); 2561 | } 2562 | 2563 | // Notify user if plugin is outdated 2564 | fetch('https://api.github.com/repos/programmer2514/BetterDiscord-CollapsibleUI/releases') 2565 | .then(response => response.json()) 2566 | .then((data) => { 2567 | if (runtime.api.Utils.semverCompare(data[0].tag_name.substring(1), runtime.meta.version) < 0) { 2568 | runtime.api.Logger.warn('A newer version is available'); 2569 | runtime.notice = runtime.api.UI.showNotice(`Your version (v${runtime.meta.version}) \ 2570 | of CollapsibleUI is outdated and may be missing features! You can \ 2571 | either wait for v${data[0].tag_name.substring(1)} to be approved, \ 2572 | or download it manually.`, { timeout: '0' }); 2573 | } 2574 | }); 2575 | 2576 | // Show changelog modal if version has changed 2577 | const savedVersion = runtime.api.Data.load('version'); 2578 | if (savedVersion !== runtime.meta.version) { 2579 | runtime.api.UI.showChangelogModal( 2580 | { 2581 | title: runtime.meta.name, 2582 | subtitle: runtime.meta.version, 2583 | blurb: runtime.meta.description, 2584 | changes: config.changelog, 2585 | }, 2586 | ); 2587 | runtime.api.Data.save('version', runtime.meta.version); 2588 | } 2589 | 2590 | // Subscribe dispatchers and listeners 2591 | modules.dispatcher.subscribe('LAYER_POP', this.reload); 2592 | 2593 | this.addListeners(); 2594 | this.addIntervals(); 2595 | 2596 | runtime.observer.observe(document, { 2597 | childList: true, 2598 | subtree: true, 2599 | attributes: false, 2600 | }); 2601 | 2602 | // Initialize the plugin 2603 | this.initialize(); 2604 | runtime.api.Logger.info('Enabled'); 2605 | }; 2606 | 2607 | // Restore the default UI when the plugin is disabled 2608 | stop = () => { 2609 | // If an update notice was shown, clear it 2610 | if (runtime.notice) runtime.notice(true); 2611 | 2612 | // Unsubscribe dispatchers and listeners 2613 | modules.dispatcher.unsubscribe('LAYER_POP', this.reload); 2614 | 2615 | runtime.controller.abort(); 2616 | 2617 | clearInterval(runtime.interval); 2618 | runtime.interval = null; 2619 | 2620 | runtime.observer.disconnect(); 2621 | 2622 | this.terminate(); 2623 | runtime.api.Logger.info('Disabled'); 2624 | }; 2625 | 2626 | // Re-inject the toolbar container when the page changes 2627 | onSwitch = () => { 2628 | this.createToolbarContainer(); 2629 | styles.reinit(); 2630 | }; 2631 | 2632 | // Build settings panel 2633 | getSettingsPanel = () => { 2634 | return runtime.api.UI.buildSettingsPanel( 2635 | { 2636 | settings: config.settings, 2637 | onChange: (_, id, value) => { 2638 | let split = id.split('-'); 2639 | 2640 | // Update regular settings 2641 | if (split.length === 1) { 2642 | settings[id] = (typeof(value) === 'string' && !isNaN(value)) ? parseInt(value) : value; 2643 | return; 2644 | } 2645 | 2646 | // Update resize settings 2647 | if (split[0] === 'resizable') { 2648 | switch (split[1]) { 2649 | case 'channel': 2650 | settings.channelListWidth = value ? settings.defaultChannelListWidth : 0; 2651 | break; 2652 | case 'members': 2653 | settings.membersListWidth = value ? settings.defaultMembersListWidth : 0; 2654 | break; 2655 | case 'user': 2656 | settings.userProfileWidth = value ? settings.defaultUserProfileWidth : 0; 2657 | break; 2658 | case 'search': 2659 | settings.searchPanelWidth = value ? settings.defaultSearchPanelWidth : 0; 2660 | break; 2661 | case 'forum': 2662 | settings.forumPopoutWidth = value ? settings.defaultForumPopoutWidth : 0; 2663 | break; 2664 | case 'activity': 2665 | settings.activityPanelWidth = value ? settings.defaultActivityPanelWidth : 0; 2666 | break; 2667 | } 2668 | return; 2669 | } 2670 | 2671 | // Determine the index of the setting 2672 | let index; 2673 | switch (split[0]) { 2674 | case 'server': 2675 | index = constants.I_SERVER_LIST; 2676 | break; 2677 | case 'channel': 2678 | index = constants.I_CHANNEL_LIST; 2679 | break; 2680 | case 'members': 2681 | index = constants.I_MEMBERS_LIST; 2682 | break; 2683 | case 'user': 2684 | if (split[1] === 'profile') index = constants.I_USER_PROFILE; 2685 | else index = constants.I_USER_AREA; 2686 | break; 2687 | case 'message': 2688 | index = constants.I_MESSAGE_INPUT; 2689 | break; 2690 | case 'window': 2691 | index = constants.I_WINDOW_BAR; 2692 | break; 2693 | case 'call': 2694 | index = constants.I_CALL_WINDOW; 2695 | break; 2696 | case 'search': 2697 | index = constants.I_SEARCH_PANEL; 2698 | break; 2699 | case 'forum': 2700 | index = constants.I_FORUM_POPOUT; 2701 | break; 2702 | case 'activity': 2703 | index = constants.I_ACTIVITY_PANEL; 2704 | break; 2705 | } 2706 | 2707 | // Save the setting to the appropriate array 2708 | if (id.slice(-9) === '-shortcut') 2709 | this.updateSettingsArray('shortcutList', index, value); 2710 | else if (id.slice(-13) === '-button-index') 2711 | this.updateSettingsArray('buttonIndexes', index, parseInt(value)); 2712 | else if (id.slice(-16) === '-expand-on-hover') 2713 | this.updateSettingsArray('expandOnHoverEnabled', index, value); 2714 | else if (id.slice(-10) === '-threshold') 2715 | this.updateSettingsArray('sizeCollapseThreshold', index, value); 2716 | else if (id.slice(-12) === '-conditional') 2717 | this.updateSettingsArray('collapseConditionals', index, value); 2718 | else if (id.slice(-9) === '-floating') 2719 | this.updateSettingsArray('floatingEnabled', index, value); 2720 | }, 2721 | }, 2722 | ); 2723 | }; 2724 | 2725 | // Initialize the plugin 2726 | initialize = () => { 2727 | styles.init(); 2728 | this.createToolbarContainer(); 2729 | }; 2730 | 2731 | // Terminate the plugin 2732 | terminate = () => { 2733 | styles.clear(); 2734 | runtime.toolbar.remove(); 2735 | }; 2736 | 2737 | // Reload the plugin 2738 | reload = () => { 2739 | this.terminate(); 2740 | this.initialize(); 2741 | }; 2742 | 2743 | // Reload the toolbar after a short delay 2744 | reloadToolbar = () => { 2745 | setTimeout(() => { 2746 | runtime.toolbar.remove(); 2747 | this.createToolbarContainer(); 2748 | }, 250); 2749 | }; 2750 | 2751 | // Reload the toolbar and reinit styles 2752 | partialReload = () => { 2753 | this.reloadToolbar(); 2754 | styles.reinit(); 2755 | }; 2756 | 2757 | // Create the toolbar container and insert buttons 2758 | createToolbarContainer = () => { 2759 | // If the toolbar already exists, remove it 2760 | runtime.toolbar?.remove(); 2761 | 2762 | let toolbar = BdApi.DOM.parseHTML('
', true); 2763 | 2764 | // Insert the toolbar container into the correct spot 2765 | try { 2766 | if (elements.inviteToolbar || elements.searchBar) { 2767 | elements.toolbar.insertBefore(toolbar, (elements.inviteToolbar) 2768 | ? elements.inviteToolbar.nextElementSibling 2769 | : elements.searchBar); 2770 | } 2771 | else 2772 | elements.toolbar.insertBefore(toolbar, elements.toolbar.childNodes[elements.toolbar.childNodes.length - 2]); 2773 | } 2774 | catch (e) { 2775 | elements.toolbar.appendChild(toolbar); 2776 | } 2777 | 2778 | // Get toolbar container reference 2779 | runtime.toolbar = document.querySelector('.cui-toolbar'); 2780 | 2781 | // Insert toolbar buttons 2782 | for (var i = 1; i <= settings.buttonIndexes.length; i++) { 2783 | for (var j = 0; j < settings.buttonIndexes.length; j++) { 2784 | if (i === settings.buttonIndexes[j] && elements.index[j]) 2785 | this.createToolbarButton(j); 2786 | } 2787 | } 2788 | }; 2789 | 2790 | // Create a functional toolbar button 2791 | createToolbarButton = (index) => { 2792 | // Get button text 2793 | let text = locale.current[index] + ' (' + [...settings.shortcutList[index]].map(e => (e.length === 1 ? e.toUpperCase() : e)).join('+') + ')'; 2794 | 2795 | // Create button and add it to the toolbar 2796 | let button = BdApi.DOM.parseHTML(` 2797 |
2798 | 2799 | ${icons[index]} 2800 | 2801 |
2802 | `, true); 2803 | runtime.toolbar.appendChild(button); 2804 | 2805 | button = document.querySelector(`#cui-icon-${index}`); 2806 | 2807 | // Add tooltip and click handler 2808 | runtime.api.UI.createTooltip(button, text, { side: 'bottom' }); 2809 | button.addEventListener('click', () => this.toggleButton(index)); 2810 | }; 2811 | 2812 | // Update a single value in a settings array 2813 | updateSettingsArray(array, index, value) { 2814 | let temp = settings[array]; 2815 | temp[index] = value; 2816 | settings[array] = temp; 2817 | } 2818 | 2819 | // Toggles the button at the specified index 2820 | toggleButton = (index) => { 2821 | styles.collapse[index].toggle(); 2822 | runtime.toolbar.querySelector(`#cui-icon-${index}`)?.classList.toggle(modules.icons?.selected); 2823 | this.updateSettingsArray('buttonsActive', index, !settings.buttonsActive[index]); 2824 | }; 2825 | 2826 | // Add event listeners to handle resize/expand on hover 2827 | addListeners = () => { 2828 | document.body.addEventListener('mousedown', (e) => { 2829 | // Handle left clicks 2830 | if (e.button === 0) { 2831 | // Dynamically handle resizing elements 2832 | if (e.target.classList.contains(modules.sidebar?.sidebarList) 2833 | || e.target.classList.contains(modules.members?.membersWrap) 2834 | || e.target.classList.contains(modules.panel?.outer) 2835 | || e.target.classList.contains(modules.search?.searchResultsWrap) 2836 | || e.target.classList.contains(modules.popout?.chatLayerWrapper) 2837 | || e.target.classList.contains(modules.social?.nowPlayingColumn)) { 2838 | e.target.style.setProperty('transition', 'none', 'important'); 2839 | 2840 | runtime.dragging = e.target; 2841 | } 2842 | 2843 | if (e.target.classList.contains(modules.sidebar?.sidebarList)) { 2844 | document.querySelector(':root').style.setProperty('--cui-channel-list-handle-transition', 'none'); 2845 | elements.userArea?.style.setProperty('transition', 'none', 'important'); 2846 | } 2847 | 2848 | if (e.target.classList.contains(modules.popout?.chatLayerWrapper)) { 2849 | elements.chatWrapper?.style.setProperty('--transition', 'none'); 2850 | elements.noChat?.style.setProperty('--transition', 'none'); 2851 | e.target.childNodes.forEach(e => e.style.setProperty('transition', 'none')); 2852 | } 2853 | } 2854 | }, { passive: true, signal: runtime.controller.signal }); 2855 | 2856 | document.body.addEventListener('mouseup', (e) => { 2857 | // Handle right clicks 2858 | if (e.button === 2) { 2859 | let target = null; 2860 | 2861 | // Reset channels list width 2862 | if (e.target.classList.contains(modules.sidebar?.sidebarList)) { 2863 | settings.channelListWidth = settings.defaultChannelListWidth; 2864 | target = e.target; 2865 | } 2866 | 2867 | // Reset members list width 2868 | if (e.target.classList.contains(modules.members?.membersWrap)) { 2869 | settings.membersListWidth = settings.defaultMembersListWidth; 2870 | target = e.target; 2871 | } 2872 | 2873 | // Reset user profile width 2874 | if (e.target.classList.contains(modules.panel?.outer)) { 2875 | settings.userProfileWidth = settings.defaultUserProfileWidth; 2876 | target = e.target; 2877 | } 2878 | 2879 | // Reset search panel width 2880 | if (e.target.classList.contains(modules.search?.searchResultsWrap)) { 2881 | e.target.style.setProperty('transition', `max-width var(--cui-transition-speed), width var(--cui-transition-speed), min-width var(--cui-transition-speed)`, 'important'); 2882 | settings.searchPanelWidth = settings.defaultSearchPanelWidth; 2883 | target = e.target; 2884 | } 2885 | 2886 | // Reset activity panel width 2887 | if (e.target.classList.contains(modules.social?.nowPlayingColumn)) { 2888 | e.target.style.setProperty('transition', `max-width var(--cui-transition-speed), width var(--cui-transition-speed), min-width var(--cui-transition-speed)`, 'important'); 2889 | settings.activityPanelWidth = settings.defaultActivityPanelWidth; 2890 | target = e.target; 2891 | } 2892 | 2893 | // Reset forum popout width 2894 | if (e.target.classList.contains(modules.popout?.chatLayerWrapper)) { 2895 | e.target.style.setProperty('transition', `max-width var(--cui-transition-speed), width var(--cui-transition-speed), min-width var(--cui-transition-speed)`, 'important'); 2896 | settings.forumPopoutWidth = settings.defaultForumPopoutWidth; 2897 | target = e.target; 2898 | } 2899 | 2900 | if (target) { 2901 | styles.update(); 2902 | // Timeout to provide smooth transition 2903 | setTimeout(() => target.style.removeProperty('transition'), settings.transitionSpeed); 2904 | } 2905 | } 2906 | 2907 | // Handle left clicks 2908 | else { 2909 | let dragging = null; 2910 | let target = null; 2911 | if (runtime.dragging) { 2912 | dragging = runtime.dragging; 2913 | runtime.dragging = null; 2914 | } 2915 | else return; 2916 | 2917 | // Finish resizing the channels list 2918 | if (dragging.classList.contains(modules.sidebar?.sidebarList)) { 2919 | settings.channelListWidth = parseInt(dragging.style.width); 2920 | document.querySelector(':root').style.removeProperty('--cui-channel-list-handle-offset'); 2921 | document.querySelector(':root').style.removeProperty('--cui-channel-list-handle-transition'); 2922 | target = dragging; 2923 | elements.userArea?.style.removeProperty('width'); 2924 | // Timeout to avoid transition flash 2925 | setTimeout(() => elements.userArea?.style.removeProperty('transition'), settings.transitionSpeed); 2926 | } 2927 | 2928 | // Finish resizing the members list 2929 | if (dragging.classList.contains(modules.members?.membersWrap)) { 2930 | settings.membersListWidth = parseInt(dragging.style.width); 2931 | target = dragging; 2932 | } 2933 | 2934 | // Finish resizing the user profile 2935 | if (dragging.classList.contains(modules.panel?.outer)) { 2936 | settings.userProfileWidth = parseInt(dragging.style.width); 2937 | target = dragging; 2938 | } 2939 | 2940 | // Finish resizing the search panel 2941 | if (dragging.classList.contains(modules.search?.searchResultsWrap)) { 2942 | settings.searchPanelWidth = parseInt(dragging.style.width); 2943 | target = dragging; 2944 | } 2945 | 2946 | // Finish resizing the activity panel 2947 | if (dragging.classList.contains(modules.social?.nowPlayingColumn)) { 2948 | settings.activityPanelWidth = parseInt(dragging.style.width); 2949 | target = dragging; 2950 | } 2951 | 2952 | // Finish resizing the forum popout 2953 | if (dragging.classList.contains(modules.popout?.chatLayerWrapper)) { 2954 | settings.forumPopoutWidth = parseInt(dragging.style.width); 2955 | target = dragging; 2956 | 2957 | elements.chatWrapper?.style.removeProperty('--width'); 2958 | elements.noChat?.style.removeProperty('--width'); 2959 | target.style.removeProperty('--uirr-forum-panel-width'); 2960 | target.childNodes.forEach(e => e.style.removeProperty('transition')); 2961 | // Timeout to avoid transition flash 2962 | setTimeout(() => { 2963 | elements.chatWrapper?.style.removeProperty('--transition'); 2964 | elements.noChat?.style.removeProperty('--transition'); 2965 | }, settings.transitionSpeed); 2966 | } 2967 | 2968 | if (target) { 2969 | target.style.removeProperty('width'); 2970 | target.style.removeProperty('max-width'); 2971 | target.style.removeProperty('min-width'); 2972 | 2973 | styles.update(); 2974 | // Timeout to avoid transition flash 2975 | setTimeout(() => target.style.removeProperty('transition'), settings.transitionSpeed); 2976 | } 2977 | } 2978 | 2979 | // Update expand on hover when the user clicks 2980 | // Timeout to provide smooth transition 2981 | setTimeout(() => runtime.plugin.tickExpandOnHover(e.clientX, e.clientY), settings.transitionSpeed); 2982 | }, { passive: true, signal: runtime.controller.signal }); 2983 | 2984 | document.body.addEventListener('mousemove', (e) => { 2985 | runtime.plugin.tickExpandOnHover(e.clientX, e.clientY); 2986 | runtime.plugin.tickCollapseSettings(e.clientX, e.clientY); 2987 | runtime.plugin.tickMessageInputCollapse(e.clientX, e.clientY); 2988 | runtime.plugin.tickCollapseToolbar(e.clientX, e.clientY, settings.collapseToolbar === 'all'); 2989 | 2990 | if (!runtime.dragging) return; 2991 | 2992 | // Clamp width to between 101px and 50vw 2993 | let width = runtime.dragging.classList.contains(modules.sidebar?.sidebarList) 2994 | ? e.clientX - runtime.dragging.getBoundingClientRect().left 2995 | : runtime.dragging.getBoundingClientRect().right - e.clientX; 2996 | if (width > window.innerWidth * 0.5) 2997 | width = window.innerWidth * 0.5; 2998 | else if (width < 101) 2999 | width = 101; 3000 | 3001 | // Handle resizing members list/user profile/search panel/activity panel/forum popout 3002 | if (runtime.dragging.classList.contains(modules.members?.membersWrap) 3003 | || runtime.dragging.classList.contains(modules.panel?.outer) 3004 | || runtime.dragging.classList.contains(modules.search?.searchResultsWrap) 3005 | || runtime.dragging.classList.contains(modules.sidebar?.sidebarList) 3006 | || runtime.dragging.classList.contains(modules.social?.nowPlayingColumn) 3007 | || runtime.dragging.classList.contains(modules.popout?.chatLayerWrapper)) { 3008 | runtime.dragging.style.setProperty('width', `${width}px`, 'important'); 3009 | runtime.dragging.style.setProperty('max-width', `${width}px`, 'important'); 3010 | runtime.dragging.style.setProperty('min-width', `${width}px`, 'important'); 3011 | } 3012 | 3013 | if (runtime.dragging.classList.contains(modules.sidebar?.sidebarList)) { 3014 | document.querySelector(':root').style.setProperty('--cui-channel-list-handle-offset', `${width - 12}px`); 3015 | elements.userArea?.style.setProperty('width', `calc((${width}px * var(--cui-channel-list-toggled)) + (var(--custom-guild-list-width) * var(--cui-server-list-toggled)) - (var(--space-xs) * 2))`, 'important'); 3016 | } 3017 | 3018 | if (runtime.dragging.classList.contains(modules.popout?.chatLayerWrapper)) { 3019 | elements.chatWrapper?.style.setProperty('--width', `${width}px`); 3020 | elements.noChat?.style.setProperty('--width', `${width}px`); 3021 | runtime.dragging.style.setProperty('--uirr-forum-panel-width', `${width}px`); 3022 | } 3023 | }, { passive: true, signal: runtime.controller.signal }); 3024 | 3025 | document.body.addEventListener('mouseleave', (e) => { 3026 | if (!runtime.plugin.isNear(elements.windowBar, settings.expandOnHoverFudgeFactor, e.clientX, e.clientY)) 3027 | runtime.plugin.tickExpandOnHover(NaN, NaN); 3028 | }, { passive: true, signal: runtime.controller.signal }); 3029 | 3030 | document.body.addEventListener('keydown', (e) => { 3031 | // Handle keyboard shortcuts 3032 | if (settings.keyboardShortcuts) { 3033 | // Clear old logged keypresses if necessary 3034 | if (Date.now() - runtime.lastKeypress > 1000) 3035 | runtime.keys.clear(); 3036 | runtime.lastKeypress = Date.now(); 3037 | 3038 | // Log keypress 3039 | runtime.keys.add(e.key); 3040 | runtime.plugin.tickKeyboardShortcuts(); 3041 | } 3042 | }, { passive: true, signal: runtime.controller.signal }); 3043 | 3044 | document.body.addEventListener('keyup', (e) => { 3045 | // Delete logged keypress 3046 | if (settings.keyboardShortcuts) 3047 | runtime.keys.delete(e.key); 3048 | }, { passive: true, signal: runtime.controller.signal }); 3049 | }; 3050 | 3051 | // Add intervals to periodically check for changes 3052 | addIntervals = () => { 3053 | runtime.interval = setInterval(() => { 3054 | // Wait for lazy-loaded threads module 3055 | if ((!runtime.threadsLoaded) && modules.threads) { 3056 | runtime.threadsLoaded = true; 3057 | runtime.api.Logger.info('Loaded threads module'); 3058 | this.reload(); 3059 | } 3060 | 3061 | // Handle conditional collapse 3062 | if (settings.conditionalCollapse) { 3063 | this.tickConditionalCollapse(); 3064 | } 3065 | }, 250); 3066 | }; 3067 | 3068 | // Update conditional collapse states 3069 | tickConditionalCollapse = () => { 3070 | for (let i = 0; i < styles.collapse.length; i++) { 3071 | if (settings.collapseConditionals[i]) { 3072 | if (eval(settings.collapseConditionals[i])) { 3073 | if (!runtime.collapsed[i]) this.collapseElementDynamic(i, true); 3074 | } 3075 | else { 3076 | if (runtime.collapsed[i]) this.collapseElementDynamic(i, false); 3077 | } 3078 | } 3079 | } 3080 | }; 3081 | 3082 | // Check keyboard shortcut states 3083 | tickKeyboardShortcuts = () => { 3084 | for (let i = 0; i < styles.collapse.length; i++) { 3085 | if (runtime.keys.symmetricDifference(settings.shortcutList[i]).size === 0) 3086 | this.toggleButton(i); 3087 | } 3088 | }; 3089 | 3090 | // Update dynamic collapsed state of elements 3091 | tickExpandOnHover = (x, y) => { 3092 | if (settings.expandOnHover) { 3093 | for (let i = 0; i < styles.collapse.length; i++) { 3094 | if (!settings.collapseDisabledButtons && settings.buttonIndexes[i] === 0) 3095 | continue; 3096 | 3097 | if (settings.expandOnHoverEnabled[i] && (!settings.buttonsActive[i] || (settings.sizeCollapse && window.matchMedia(styles.collapse[i].query)).matches)) { 3098 | if (this.isNear(elements.index[i], settings.expandOnHoverFudgeFactor, x, y)) { 3099 | if (runtime.collapsed[i]) { 3100 | if (settings.floatingPanels && settings.floatingEnabled[i] === 'hover') 3101 | styles.collapse[i].float(); 3102 | this.collapseElementDynamic(i, false); 3103 | } 3104 | } 3105 | else { 3106 | if (!runtime.collapsed[i]) { 3107 | if (elements.biteSizePanel 3108 | || elements.rightClickMenu 3109 | || elements.forumPreviewTooltip 3110 | || elements.expressionPicker) { 3111 | this.collapseElementDynamic(i, false); 3112 | } 3113 | else this.collapseElementDynamic(i, true); 3114 | } 3115 | } 3116 | } 3117 | } 3118 | } 3119 | }; 3120 | 3121 | // Update dynamic collapsed state of user settings buttons 3122 | tickCollapseSettings = (x, y) => { 3123 | if (settings.collapseSettings) { 3124 | if (this.isNear(elements.settingsContainer, settings.buttonCollapseFudgeFactor, x, y) && !(document.querySelector(`#${runtime.meta.name}-channelList_toggle_dynamic`))) { 3125 | if (styles.buttons[constants.I_SETTINGS_BUTTONS].hidden) styles.buttons[constants.I_SETTINGS_BUTTONS].show(); 3126 | } 3127 | else { 3128 | if (!styles.buttons[constants.I_SETTINGS_BUTTONS].hidden) styles.buttons[constants.I_SETTINGS_BUTTONS].hide(); 3129 | } 3130 | } 3131 | }; 3132 | 3133 | // Update dynamic collapsed state of message input buttons 3134 | tickMessageInputCollapse = (x, y) => { 3135 | if (settings.messageInputCollapse) { 3136 | if (this.isNear(elements.messageInputContainer, settings.buttonCollapseFudgeFactor, x, y)) { 3137 | if (styles.buttons[constants.I_MESSAGE_INPUT_BUTTONS].hidden) styles.buttons[constants.I_MESSAGE_INPUT_BUTTONS].show(); 3138 | } 3139 | else { 3140 | if (!styles.buttons[constants.I_MESSAGE_INPUT_BUTTONS].hidden) styles.buttons[constants.I_MESSAGE_INPUT_BUTTONS].hide(); 3141 | } 3142 | } 3143 | }; 3144 | 3145 | // Update dynamic collapsed state of toolbar buttons 3146 | tickCollapseToolbar = (x, y, full) => { 3147 | if (settings.collapseToolbar) { 3148 | if (full) { 3149 | if (this.isNear(elements.toolbar, settings.buttonCollapseFudgeFactor, x, y) 3150 | && !this.isNear(elements.forumPopout, 0, x, y)) { 3151 | if (styles.buttons[constants.I_TOOLBAR_FULL].hidden) styles.buttons[constants.I_TOOLBAR_FULL].show(); 3152 | } 3153 | else { 3154 | if (!styles.buttons[constants.I_TOOLBAR_FULL].hidden) styles.buttons[constants.I_TOOLBAR_FULL].hide(); 3155 | } 3156 | } 3157 | else { 3158 | if (this.isNear(runtime.toolbar, settings.buttonCollapseFudgeFactor, x, y) 3159 | && !this.isNear(elements.forumPopout, 0, x, y)) { 3160 | if (styles.buttons[constants.I_TOOLBAR_BUTTONS].hidden) styles.buttons[constants.I_TOOLBAR_BUTTONS].show(); 3161 | } 3162 | else { 3163 | if (!styles.buttons[constants.I_TOOLBAR_BUTTONS].hidden) styles.buttons[constants.I_TOOLBAR_BUTTONS].hide(); 3164 | } 3165 | } 3166 | } 3167 | }; 3168 | 3169 | // Check if the cursor is within the given distance of an element 3170 | isNear = (element, distance, x, y) => { 3171 | let box = element?.getBoundingClientRect(); 3172 | if (!box) return false; 3173 | 3174 | let top = box.top - distance; 3175 | let left = box.left - distance; 3176 | let right = box.right + distance; 3177 | let bottom = box.bottom + distance; 3178 | 3179 | if (element.classList.contains(`${modules.frame?.bar}`)) 3180 | right = window.innerWidth + distance; 3181 | 3182 | return (x > left && x < right && y > top && y < bottom); 3183 | }; 3184 | 3185 | // Update the dynamic collapsed state of an element 3186 | collapseElementDynamic(index, collapsed) { 3187 | if (collapsed) { 3188 | if (settings.sizeCollapse && window.matchMedia(styles.collapse[index].query).matches) { 3189 | runtime.api.DOM.removeStyle(styles.collapse[index]._queryToggle[0]); 3190 | if (settings.floatingPanels && settings.floatingEnabled[index] === 'hover') 3191 | if (styles.collapse[index]._float) 3192 | setTimeout(() => runtime.api.DOM.removeStyle(styles.collapse[index]._float[0]), settings.transitionSpeed); 3193 | } 3194 | else 3195 | runtime.api.DOM.addStyle( 3196 | `${styles.collapse[index]._toggle[0]}_dynamic`, 3197 | styles.collapse[index]._toggle[1], 3198 | ); 3199 | } 3200 | else { 3201 | if (settings.sizeCollapse && window.matchMedia(styles.collapse[index].query).matches) 3202 | runtime.api.DOM.addStyle(...styles.collapse[index]._queryToggle); 3203 | else 3204 | runtime.api.DOM.removeStyle(`${styles.collapse[index]._toggle[0]}_dynamic`); 3205 | } 3206 | runtime.collapsed[index] = collapsed; 3207 | } 3208 | }; 3209 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 programmer2514 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CollapsibleUI for BetterDiscord 2 | A feature-rich BetterDiscord plugin that reworks the Discord UI to be significantly more modular. 3 | 4 | #### [> Check out my other add-ons <](https://github.com/programmer2514/BetterDiscord-Stuff) 5 | 6 | ![Plugin Preview](https://github.com/programmer2514/BetterDiscord-CollapsibleUI/assets/43104632/d7a9849e-13d4-41fe-9e34-0b8655bac670) 7 | 8 | 9 | ### Features 10 | * Members list style collapsing of most UI panels 11 | * Dynamic expansion of hidden panels on hover 12 | * Auto-collapse/expand panels based on window size 13 | * Out-of-the-box compatibility with most themes and plugins 14 | * Keyboard shortcuts for most actions 15 | * Resizable members list, channel/DM list, user profile panel, search panel, forum/chat panel, and friend activity panel 16 | * Smooth transition effects 17 | * And most importantly, ___everything___ is customizable 18 | 19 | 20 | ### How to install 21 | 1) Make sure you have [BetterDiscord](https://betterdiscord.app/) installed 22 | 2) Download the file `CollapsibleUI.plugin.js` from [here](https://github.com/programmer2514/BetterDiscord-CollapsibleUI/releases/latest) 23 | 3) Copy it to `%AppData%\betterdiscord\plugins` 24 | 4) Refresh or restart Discord 25 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import globals from 'globals'; 2 | import pluginJs from '@eslint/js'; 3 | import stylistic from '@stylistic/eslint-plugin'; 4 | 5 | export default [ 6 | pluginJs.configs.recommended, 7 | { 8 | languageOptions: { 9 | sourceType: 'module', 10 | globals: globals.browser, 11 | }, 12 | rules: { 13 | 'no-empty': 'off', 14 | 'no-redeclare': 'off', 15 | 'no-unused-vars': 'off', 16 | }, 17 | }, 18 | { 19 | languageOptions: { 20 | globals: { 21 | BdApi: 'readonly', 22 | require: 'readonly', 23 | module: 'readonly', 24 | }, 25 | }, 26 | }, 27 | stylistic.configs['recommended-flat'], 28 | { 29 | plugins: { 30 | '@stylistic': stylistic, 31 | }, 32 | rules: { 33 | '@stylistic/semi': ['error', 'always'], 34 | '@stylistic/operator-linebreak': ['error', 'before', { 35 | overrides: { 36 | '=': 'after', 37 | }, 38 | }], 39 | '@stylistic/space-unary-ops': [2, { 40 | words: false, 41 | nonwords: false, 42 | overrides: { 43 | new: true, 44 | }, 45 | }], 46 | '@stylistic/indent-binary-ops': 'off', 47 | }, 48 | }, 49 | ]; 50 | --------------------------------------------------------------------------------