1/** 2 * CitrusOS compact chatroom â toolbar, side aux windows, auth gate, expand. 3 */ 4(function (global) { 5 'use strict'; 6 7 var toolbarEl = null; 8 var vcBarEl = null; 9 var authGateEl = null; 10 var bound = false; 11 var vcBarOpen = false; 12 var vcControlsSlot = null; 13 var vcDisconnectSlot = null; 14 var resizeObserver = null; 15 var voiceChangeHandler = null; 16 /** @type {Record<string, { placeholder: Comment, el: HTMLElement }>} */ 17 var auxSlots = {}; 18 19 var PANELS = { 20 channels: { 21 label: 'Channels', 22 icon: 'fa-solid fa-hashtag', 23 selector: '.channels-column', 24 w: 268, 25 h: 480 26 }, 27 dms: { 28 label: 'Direct messages', 29 icon: 'fa-solid fa-envelope', 30 selector: '.navigation-column', 31 w: 248, 32 h: 420 33 }, 34 members: { 35 label: 'Members', 36 icon: 'fa-solid fa-users', 37 selector: '.members-column', 38 w: 268, 39 h: 480 40 }, 41 settings: { 42 label: 'Settings', 43 icon: 'fa-solid fa-gear', 44 selector: '#settings-modal', 45 w: 600, 46 h: 450, 47 resizable: false, 48 isSettings: true 49 } 50 }; 51 52 /** Distinct from sitewide Settings (`settings`) so session restore cannot swap them. */ 53 var CHAT_SETTINGS_AUX_ID = 'chat-settings'; 54 55 function getAuxWindowId(type) { 56 if (type === 'settings') return CHAT_SETTINGS_AUX_ID; 57 return type; 58 } 59 60 function isCitrusHost() { 61 return !!(global.document.body && global.document.body.classList.contains('page-citrusos')); 62 } 63 64 function recoverChatSettingsDom() { 65 var modal = global.document.getElementById('settings-modal'); 66 if (modal) { 67 modal.classList.remove('chatroom-citrus-aux-panel'); 68 modal.style.removeProperty('display'); 69 modal.style.removeProperty('width'); 70 modal.style.removeProperty('height'); 71 modal.style.display = 'none'; 72 modal.hidden = true; 73 if (modal.parentNode !== global.document.body) { 74 global.document.body.appendChild(modal); 75 } 76 } 77 auxSlots = {}; 78 if (global.CitrusDesktop && typeof global.CitrusDesktop.closeAuxWindow === 'function') { 79 global.CitrusDesktop.closeAuxWindow(CHAT_SETTINGS_AUX_ID); 80 } 81 } 82 83 function isFullscreenChat() { 84 var root = global.document.querySelector('[data-citrus-app-root]'); 85 return !!(root && root.closest('.citrus-fullscreen__content')); 86 } 87 88 function getLayout() { 89 if (!isCitrusHost()) return 'full'; 90 var root = global.document.querySelector('[data-citrus-app-root]'); 91 if (root && root.closest('.citrus-fullscreen__content')) return 'full'; 92 if (root && root.closest('.citrus-window__content')) return 'compact'; 93 return global.__chatroomCitrusLayout === 'full' ? 'full' : 'compact'; 94 } 95 96 function getWrap() { 97 if (global.ChatroomSpa && typeof global.ChatroomSpa.getPageWrap === 'function') { 98 return global.ChatroomSpa.getPageWrap(); 99 } 100 var root = global.document.querySelector('[data-citrus-app-root]'); 101 if (root && root.id === 'page-wrap') return root; 102 if (root && root.querySelector) { 103 var nested = root.querySelector('#page-wrap'); 104 if (nested) return nested; 105 } 106 return global.document.getElementById('page-wrap'); 107 } 108 109 function getCitrusHosts() { 110 var root = global.document.querySelector('[data-citrus-app-root]'); 111 if (!root) { 112 return { content: null, body: null }; 113 } 114 var content = root.closest('.citrus-window__content, .citrus-fullscreen__content'); 115 var body = content && content.closest('.citrus-window__body'); 116 return { content: content, body: body }; 117 } 118 119 function syncWindowChrome() { 120 if (!isCitrusHost()) return; 121 var hosts = getCitrusHosts(); 122 var compact = getLayout() === 'compact'; 123 if (hosts.content) { 124 hosts.content.classList.toggle('citrus-window__content--chat-compact', compact); 125 hosts.content.classList.toggle('citrus-window__content--chat-full', !compact); 126 hosts.content.classList.toggle('citrus-fullscreen__content--chat-full', 127 !compact && hosts.content.classList.contains('citrus-fullscreen__content')); 128 } 129 if (hosts.body) { 130 hosts.body.classList.toggle('citrus-window__body--chat-compact', compact); 131 } 132 } 133 134 function clearWindowChrome() { 135 global.document.querySelectorAll('.citrus-window__content--chat-compact').forEach(function (el) { 136 el.classList.remove('citrus-window__content--chat-compact'); 137 }); 138 global.document.querySelectorAll('.citrus-window__content--chat-full').forEach(function (el) { 139 el.classList.remove('citrus-window__content--chat-full'); 140 }); 141 global.document.querySelectorAll('.citrus-fullscreen__content--chat-full').forEach(function (el) { 142 el.classList.remove('citrus-fullscreen__content--chat-full'); 143 }); 144 global.document.querySelectorAll('.citrus-window__body--chat-compact').forEach(function (el) { 145 el.classList.remove('citrus-window__body--chat-compact'); 146 }); 147 } 148 149 function applyLayoutMode() { 150 var wrap = getWrap(); 151 if (!wrap || !isCitrusHost()) return; 152 wrap.setAttribute('data-chatroom-layout', getLayout()); 153 global.document.body.classList.toggle('chatroom-citrus-compact', getLayout() === 'compact'); 154 global.document.body.classList.toggle('chatroom-citrus-full', getLayout() === 'full'); 155 if (getLayout() === 'full') { 156 setVcBarOpen(false); 157 } 158 syncWindowChrome(); 159 if (global.ChatroomSpa && typeof global.ChatroomSpa.scheduleChatroomThemeApply === 'function') { 160 global.ChatroomSpa.scheduleChatroomThemeApply(); 161 } else if (global.ChatroomSpa && typeof global.ChatroomSpa.applyChatroomTheme === 'function') { 162 global.ChatroomSpa.applyChatroomTheme(); 163 } 164 } 165 166 function isVoiceConnected() { 167 if (!global.ChatroomVoiceChat || typeof global.ChatroomVoiceChat.getStatus !== 'function') { 168 return false;
169 } 170 var status = global.ChatroomVoiceChat.getStatus(); 171 return !!(status && status.connected); 172 } 173 174 function getVoiceChannelLabel() { 175 var el = global.document.getElementById('voice-status-channel'); 176 if (el && el.textContent) return el.textContent.trim(); 177 return 'Whimsy Zone'; 178 } 179 180 function restoreVcControlSlot(slot) { 181 if (!slot || !slot.el || !slot.placeholder.parentNode) return; 182 slot.el.style.removeProperty('display'); 183 slot.placeholder.parentNode.insertBefore(slot.el, slot.placeholder); 184 slot.placeholder.remove(); 185 } 186 187 function restoreVcControls() { 188 restoreVcControlSlot(vcControlsSlot); 189 restoreVcControlSlot(vcDisconnectSlot); 190 vcControlsSlot = null; 191 vcDisconnectSlot = null; 192 } 193 194 function mountVcControls(host) { 195 if (!host) return; 196 if (vcControlsSlot && host.contains(vcControlsSlot.el)) { 197 return; 198 } 199 200 var controls = global.document.querySelector('#voice-chat-category .voice-chat-controls-sidebar'); 201 if (controls && controls.parentNode && !vcControlsSlot) { 202 var controlsPlaceholder = global.document.createComment('citrus-vc-controls-slot'); 203 controls.parentNode.insertBefore(controlsPlaceholder, controls); 204 vcControlsSlot = { placeholder: controlsPlaceholder, el: controls }; 205 host.appendChild(controls); 206 } 207 208 var disconnectBtn = global.document.getElementById('voice-disconnect-btn'); 209 if (disconnectBtn && disconnectBtn.parentNode && !vcDisconnectSlot) { 210 var disconnectPlaceholder = global.document.createComment('citrus-vc-disconnect-slot'); 211 disconnectBtn.parentNode.insertBefore(disconnectPlaceholder, disconnectBtn); 212 vcDisconnectSlot = { placeholder: disconnectPlaceholder, el: disconnectBtn }; 213 host.appendChild(disconnectBtn); 214 } 215 } 216 217 function syncVcBarContents() { 218 if (!vcBarEl) return; 219 var joinEl = vcBarEl.querySelector('.chatroom-citrus-vc-bar__join'); 220 var controlsHost = vcBarEl.querySelector('.chatroom-citrus-vc-bar__controls-host'); 221 var channelName = vcBarEl.querySelector('.chatroom-citrus-vc-bar__channel-name'); 222 var connected = isVoiceConnected(); 223 224 if (channelName) channelName.textContent = getVoiceChannelLabel(); 225 if (joinEl) joinEl.hidden = connected; 226 if (controlsHost) controlsHost.hidden = !connected; 227 228 if (connected && vcBarOpen) { 229 mountVcControls(controlsHost); 230 } else { 231 restoreVcControls(); 232 } 233 } 234 235 function setVcBarOpen(open) { 236 vcBarOpen = !!open; 237 if (!vcBarEl) return; 238 vcBarEl.classList.toggle('chatroom-citrus-vc-bar--open', vcBarOpen); 239 var voiceBtn = toolbarEl && toolbarEl.querySelector('[data-citrus-chat="voice"]'); 240 if (voiceBtn) voiceBtn.setAttribute('aria-pressed', vcBarOpen ? 'true' : 'false'); 241 if (vcBarOpen) { 242 syncVcBarContents(); 243 } else { 244 restoreVcControls(); 245 } 246 } 247 248 function toggleVcBar() { 249 if (getLayout() !== 'compact') return; 250 setVcBarOpen(!vcBarOpen); 251 } 252 253 function joinVoiceFromBar() { 254 if (!global.ChatroomVoiceChat) return Promise.resolve(); 255 var connectFn = global.ChatroomVoiceChat.connectWithConfirmation || global.ChatroomVoiceChat.connect; 256 if (typeof connectFn !== 'function') return Promise.resolve(); 257 return Promise.resolve(connectFn()).then(function () { 258 syncVcBarContents(); 259 }).catch(function (err) { 260 console.warn('[ChatroomCitrusMenus] voice join failed', err); 261 }); 262 } 263 264 function buildVcBar() { 265 var container = global.document.querySelector('.chatroom-container'); 266 if (!container || vcBarEl) return; 267 vcBarEl = global.document.createElement('div'); 268 vcBarEl.className = 'chatroom-citrus-vc-bar'; 269 vcBarEl.setAttribute('role', 'region'); 270 vcBarEl.setAttribute('aria-label', 'Voice controls'); 271 vcBarEl.innerHTML = 272 '<div class="chatroom-citrus-vc-bar__join">' + 273 '<span class="chatroom-citrus-vc-bar__channel-name">Whimsy Zone</span>' + 274 '<button type="button" class="chatroom-citrus-vc-bar__join-btn chatroom-citrus-chrome-btn citrus-tool-btn">Join voice</button>' + 275 '</div>' + 276 '<div class="chatroom-citrus-vc-bar__controls-host" hidden></div>'; 277 var joinBtn = vcBarEl.querySelector('.chatroom-citrus-vc-bar__join-btn'); 278 if (joinBtn) { 279 joinBtn.addEventListener('click', function () { 280 joinVoiceFromBar(); 281 }); 282 } 283 if (toolbarEl && toolbarEl.nextSibling) { 284 container.insertBefore(vcBarEl, toolbarEl.nextSibling); 285 } else { 286 container.insertBefore(vcBarEl, container.firstChild); 287 } 288 } 289 290 function bindVoiceConnectionListener() { 291 if (voiceChangeHandler) return; 292 voiceChangeHandler = function () { 293 if (!vcBarOpen) return; 294 syncVcBarContents(); 295 }; 296 global.addEventListener('pupwife:voice-connection-change', voiceChangeHandler); 297 } 298 299 function unbindVoiceConnectionListener() { 300 if (!voiceChangeHandler) return; 301 global.removeEventListener('pupwife:voice-connection-change', voiceChangeHandler); 302 voiceChangeHandler = null; 303 } 304 305 function bindCompactResizeObserver() { 306 if (resizeObserver || typeof global.ResizeObserver !== 'function') return; 307 var hosts = getCitrusHosts(); 308 var target = hosts.body || hosts.content; 309 if (!target) return; 310 resizeObserver = new global.ResizeObserver(function () { 311 if (getLayout() !== 'compact') return; 312 if (global.ChatroomSpa && typeof global.ChatroomSpa.refreshChatroomScrollLayout === 'function') { 313 global.ChatroomSpa.refreshChatroomScrollLayout(); 314 } 315 }); 316 resizeObserver.observe(target); 317 } 318 319 function unbindCompactResizeObserver() { 320 if (resizeObserver) { 321 resizeObserver.disconnect(); 322 resizeObserver = null; 323 } 324 } 325 326 function resolvePanelElement(type) { 327 var config = PANELS[type]; 328 if (!config) return null; 329 if (config.isSettings) { 330 if (global.ChatroomSettings && typeof global.ChatroomSettings.showSettings === 'function') { 331 global.ChatroomSettings.showSettings(); 332 } 333 return global.document.getElementById('settings-modal'); 334 } 335 return global.document.querySelector(config.selector); 336 } 337 338 function restoreAuxSlot(type) { 339 var state = auxSlots[type]; 340 if (!state || !state.el || !state.placeholder.parentNode) { 341 delete auxSlots[type]; 342 return; 343 } 344 state.el.classList.remove('chatroom-citrus-aux-panel'); 345 state.el.style.removeProperty('display'); 346 state.el.style.removeProperty('width'); 347 state.el.style.removeProperty('height'); 348 state.placeholder.parentNode.insertBefore(state.el, state.placeholder); 349 state.placeholder.remove(); 350 delete auxSlots[type]; 351 352 if (PANELS[type] && PANELS[type].isSettings) { 353 var settingsModal = global.document.getElementById('settings-modal'); 354 if (settingsModal) { 355 settingsModal.style.display = 'none'; 356 settingsModal.hidden = true; 357 } 358 global.document.body.classList.remove('settings-open'); 359 } 360 } 361 362 function closeAuxPanel(type) { 363 if (global.CitrusDesktop && typeof global.CitrusDesktop.closeAuxWindow === 'function') { 364 global.CitrusDesktop.closeAuxWindow(getAuxWindowId(type)); 365 } else { 366 restoreAuxSlot(type); 367 } 368 } 369 370 function closeAllAuxWindows() {
371 Object.keys(auxSlots).slice().forEach(function (type) { 372 closeAuxPanel(type); 373 }); 374 } 375 376 function syncThemeToAuxHost(hostEl) { 377 if (!hostEl) return; 378 var wrap = getWrap(); 379 if (!wrap) return; 380 if (global.ChatroomSpa && typeof global.ChatroomSpa.applyThemeToElement === 'function') { 381 global.ChatroomSpa.applyThemeToElement(hostEl); 382 return; 383 } 384 var attrs = [ 385 'data-chatroom-theme', 386 'data-chatroom-color-mode', 387 'data-chatroom-font-size', 388 'data-chatroom-emoji-size' 389 ]; 390 var i; 391 for (i = 0; i < attrs.length; i++) { 392 var name = attrs[i]; 393 if (wrap.hasAttribute(name)) { 394 hostEl.setAttribute(name, wrap.getAttribute(name)); 395 } else { 396 hostEl.removeAttribute(name); 397 } 398 } 399 hostEl.classList.toggle('animations', wrap.classList.contains('animations')); 400 } 401 402 function openAuxPanel(type) { 403 if (!global.CitrusDesktop || typeof global.CitrusDesktop.openAuxWindow !== 'function') return; 404 if (getLayout() !== 'compact') return; 405 406 if (auxSlots[type]) { 407 closeAuxPanel(type); 408 return; 409 } 410 411 var config = PANELS[type]; 412 if (!config) return; 413 414 var el = resolvePanelElement(type); 415 if (!el || !el.parentNode) return; 416 417 var placeholder = global.document.createComment('citrus-chat-slot-' + type); 418 el.parentNode.insertBefore(placeholder, el); 419 auxSlots[type] = { placeholder: placeholder, el: el }; 420 421 global.CitrusDesktop.openAuxWindow({ 422 id: getAuxWindowId(type), 423 label: config.label, 424 icon: config.icon, 425 w: config.w, 426 h: config.h, 427 nearChat: true, 428 resizable: config.resizable === false ? false : undefined, 429 auxClass: type === 'settings' ? 'citrus-window--settings' : '', 430 onMount: function (contentEl) { 431 contentEl.classList.add('chatroom-citrus-aux-host'); 432 if (type !== 'settings') { 433 syncThemeToAuxHost(contentEl); 434 } 435 contentEl.appendChild(el); 436 el.classList.add('chatroom-citrus-aux-panel'); 437 el.style.display = 'flex'; 438 }, 439 onClose: function () { 440 restoreAuxSlot(type); 441 } 442 }); 443 } 444 445 function wireToolbarButtons() { 446 if (!toolbarEl) return; 447 var types = ['channels', 'dms', 'members', 'settings']; 448 types.forEach(function (type) { 449 var btn = toolbarEl.querySelector('[data-citrus-chat="' + type + '"]'); 450 if (!btn) return; 451 btn.addEventListener('click', function () { 452 openAuxPanel(type); 453 }); 454 }); 455 456 var voiceBtn = toolbarEl.querySelector('[data-citrus-chat="voice"]'); 457 if (voiceBtn) { 458 voiceBtn.setAttribute('aria-pressed', 'false'); 459 voiceBtn.addEventListener('click', function () { 460 toggleVcBar(); 461 }); 462 } 463 464 var expandBtn = toolbarEl.querySelector('[data-citrus-chat="expand"]'); 465 if (expandBtn) { 466 expandBtn.addEventListener('click', function () { 467 if (!global.CitrusDesktop || !global.CitrusAppRegistry) return; 468 closeAllAuxWindows(); 469 setVcBarOpen(false); 470 var app = global.CitrusAppRegistry.getApp('chat'); 471 if (app && typeof global.CitrusDesktop.openFullscreen === 'function') { 472 global.CitrusDesktop.openFullscreen(app); 473 } 474 }); 475 } 476 } 477 478 function buildToolbar() { 479 var container = global.document.querySelector('.chatroom-container'); 480 if (!container || toolbarEl) return; 481 toolbarEl = global.document.createElement('div'); 482 toolbarEl.className = 'chatroom-citrus-toolbar'; 483 toolbarEl.setAttribute('role', 'toolbar'); 484 toolbarEl.setAttribute('aria-label', 'Chat controls'); 485 toolbarEl.innerHTML = 486 '<button type="button" class="chatroom-citrus-toolbar__btn chatroom-citrus-chrome-btn citrus-tool-btn" data-citrus-chat="channels" title="Channels" aria-label="Channels"><i class="fa-solid fa-hashtag" aria-hidden="true"></i><span>Channels</span></button>' + 487 '<button type="button" class="chatroom-citrus-toolbar__btn chatroom-citrus-chrome-btn citrus-tool-btn" data-citrus-chat="dm
487s" title="Direct messages" aria-label="Direct messages"><i class="fa-solid fa-envelope" aria-hidden="true"></i><span>DMs</span></button>' + 488 '<button type="button" class="chatroom-citrus-toolbar__btn chatroom-citrus-chrome-btn citrus-tool-btn" data-citrus-chat="members" title="Members" aria-label="Members"><i class="fa-solid fa-users" aria-hidden="true"></i><span>Members</span></button>' + 489 '<button type="button" class="chatroom-citrus-toolbar__btn chatroom-citrus-chrome-btn citrus-tool-btn" data-citrus-chat="voice" title="Voice" aria-label="Voice"><i class="fa-solid fa-microphone" aria-hidden="true"></i><span>Voice</span></button>' + 490 '<button type="button" class="chatroom-citrus-toolbar__btn chatroom-citrus-chrome-btn citrus-tool-btn" data-citrus-chat="settings" title="Settings" aria-label="Settings"><i class="fa-solid fa-gear" aria-hidden="true"></i><span>Settings</span></button>' + 491 '<button type="button" class="chatroom-citrus-toolbar__btn chatroom-citrus-chrome-btn citrus-tool-btn chatroom-citrus-toolbar__btn--expand" data-citrus-chat="expand" title="Expand to full chatroom" aria-label="Expand to full chatroom"><i class="fa-solid fa-up-right-and-down-left-from-center" aria-hidden="true"></i><span>Expand</span></button>'; 492 container.insertBefore(toolbarEl, container.firstChild); 493 buildVcBar(); 494 wireToolbarButtons(); 495 } 496 497 function getAuthGateHost() { 498 if (!isCitrusHost()) { 499 return getWrap(); 500 } 501 502 if (global.CitrusRouteScope && typeof global.CitrusRouteScope.getScopeEl === 'function') { 503 var scoped = global.CitrusRouteScope.getScopeEl(); 504 if (scoped && scoped.classList && 505 (scoped.classList.contains('citrus-window__content') || 506 scoped.classList.contains('citrus-fullscreen__content')) && 507 scoped.classList.contains('page-chatroom')) { 508 return scoped; 509 } 510 } 511 512 if (global.CitrusWindowLoader && typeof global.CitrusWindowLoader.getFocusedContainer === 'function') { 513 var focused = global.CitrusWindowLoader.getFocusedContainer(); 514 if (focused && focused.classList && focused.classList.contains('page-chatroom')) { 515 return focused; 516 } 517 } 518 519 var chatWindow = global.document.querySelector( 520 '.citrus-window[data-app-id="chat"] .citrus-window__content.page-chatroom' 521 ); 522 if (chatWindow) return chatWindow; 523 524 return global.document.querySelector( 525 '.citrus-window__content.page-chatroom, .citrus-fullscreen__content.page-chatroom' 526 ); 527 } 528 529 function getChatroomContainer(host) { 530 if (host && host.querySelector) { 531 var inHost = host.querySelector('.chatroom-container'); 532 if (inHost) return inHost; 533 } 534 return null; 535 } 536 537 function mountAuthGate(host) { 538 if (!host) return null; 539 if (!authGateEl) { 540 authGateEl = global.document.createElement('div'); 541 authGateEl.className = 'chatroom-citrus-auth'; 542 authGateEl.innerHTML = 543 '<div class="chatroom-citrus-auth__panel">' + 544 '<i class="fa-solid fa-lock chatroom-citrus-auth__icon" aria-hidden="true"></i>' + 545 '<h2 class="chatroom-citrus-auth__title">Sign in to chat</h2>' + 546 '<p class="chatroom-citrus-auth__text">You need an account to use the chatroom.</p>' + 547 '<button type="button" class="chatroom-citrus-auth__btn citrus-tool-btn" data-citrus-chat-auth="signin">Open account</button>' + 548 '</div>'; 549 authGateEl.querySelector('[data-citrus-chat-auth="signin"]').addEventListener('click', function () { 550 if (global.CitrusAccountWindow && typeof global.CitrusAccountWindow.open === 'function') { 551 global.CitrusAccountWindow.open(); 552 return; 553 } 554 if (global.CitrusDesktop && typeof global.CitrusDesktop.openHref === 'function') { 555 global.CitrusDesktop.openHref('/account.html'); 556 } else { 557 global.location.assign('/account.html'); 558 } 559 }); 560 } 561 if (authGateEl.parentNode !== host) { 562 host.appendChild(authGateEl); 563 } 564 return authGateEl; 565 } 566 567 function hideAuthGate() { 568 var host = getAuthGateHost() || (authGateEl ? authGateEl.parentNode : null); 569 if (authGateEl) { 570 authGateEl.hidden = true; 571 } 572 var container = getChatroomContainer(host); 573 if (container) container.style.display = ''; 574 } 575 576 function showAuthGate() { 577 applyLayoutMode(); 578 var host = getAuthGateHost(); 579 if (!host) { 580 hideAuthGate(); 581 return; 582 } 583 mountAuthGate(host); 584 if (!authGateEl) return; 585 authGateEl.hidden = false;
586 var container = getChatroomContainer(host); 587 if (container) container.style.display = 'none'; 588 } 589 590 function init() { 591 if (!isCitrusHost()) return; 592 recoverChatSettingsDom(); 593 applyLayoutMode(); 594 if (getLayout() === 'compact') { 595 buildToolbar(); 596 bindVoiceConnectionListener(); 597 bindCompactResizeObserver(); 598 } else { 599 setVcBarOpen(false); 600 if (toolbarEl) { 601 toolbarEl.style.display = 'none'; 602 } 603 } 604 if (global.ChatroomCitrusUnread && typeof global.ChatroomCitrusUnread.init === 'function') { 605 global.ChatroomCitrusUnread.init(); 606 } 607 if (global.ChatroomSpa && typeof global.ChatroomSpa.scheduleChatroomThemeApply === 'function') { 608 global.ChatroomSpa.scheduleChatroomThemeApply(); 609 } else if (global.ChatroomSpa && typeof global.ChatroomSpa.applyChatroomTheme === 'function') { 610 global.ChatroomSpa.applyChatroomTheme(); 611 } 612 } 613 614 function destroy() { 615 closeAllAuxWindows(); 616 setVcBarOpen(false); 617 unbindVoiceConnectionListener(); 618 unbindCompactResizeObserver(); 619 if (vcBarEl) { 620 vcBarEl.remove(); 621 vcBarEl = null; 622 } 623 if (toolbarEl) { 624 toolbarEl.remove(); 625 toolbarEl = null; 626 } 627 if (authGateEl) { 628 authGateEl.remove(); 629 authGateEl = null; 630 } 631 if (global.ChatroomCitrusUnread && typeof global.ChatroomCitrusUnread.destroy === 'function') { 632 global.ChatroomCitrusUnread.destroy(); 633 } 634 global.document.body.classList.remove('chatroom-citrus-compact', 'chatroom-citrus-full'); 635 clearWindowChrome(); 636 } 637 638 function onFullscreenClosed() { 639 global.__chatroomCitrusLayout = 'compact'; 640 setVcBarOpen(false); 641 applyLayoutMode(); 642 if (toolbarEl) { 643 toolbarEl.style.display = ''; 644 toolbarEl.style.removeProperty('flex'); 645 toolbarEl.style.removeProperty('width'); 646 toolbarEl.style.removeProperty('height'); 647 } 648 if (global.ChatroomSpa && typeof global.ChatroomSpa.refreshChatroomScrollLayout === 'function') { 649 global.ChatroomSpa.refreshChatroomScrollLayout(); 650 } 651 if (global.ChatroomCitrusUnread && typeof global.ChatroomCitrusUnread.refresh === 'function') { 652 global.ChatroomCitrusUnread.refresh(); 653 } 654 bindVoiceConnectionListener(); 655 bindCompactResizeObserver(); 656 global.requestAnimationFrame(function () { 657 applyLayoutMode(); 658 }); 659 } 660 661 global.ChatroomCitrusMenus = { 662 init: init, 663 destroy: destroy, 664 showAuthGate: showAuthGate, 665 hideAuthGate: hideAuthGate, 666 applyLayoutMode: applyLayoutMode, 667 onFullscreenClosed: onFullscreenClosed, 668 getLayout: getLayout, 669 closeAllAuxWindows: closeAllAuxWindows, 670 openAuxPanel: openAuxPanel, 671 toggleVcBar: toggleVcBar, 672 syncVcBarContents: syncVcBarContents 673 }; 674})(typeof window !== 'undefined' ? window : this);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.