mirror of
https://github.com/ancsemi/Haven.git
synced 2026-10-26 08:38:20 +00:00
Turning the layout off used to restore the class and style the Join and Create sections had when it was turned on. Haven shows or hides Create as permissions arrive, so an admin could lose Create (or a member gain it) until a reload. Only the position is restored now, the panes lose the pane-hidden class, and the sections go home before the modal is removed.
764 lines
22 KiB
JavaScript
764 lines
22 KiB
JavaScript
/**
|
||
* @name Channel Tabs Layout
|
||
* @description Reversible Channels/DMs sidebar tabs with a Join/Create modal.
|
||
* @author birdcrazy
|
||
* @version 1.0.0
|
||
*/
|
||
|
||
class ChannelTabsLayout {
|
||
static ID = 'ChannelTabsLayout';
|
||
|
||
constructor() {
|
||
this._started = false;
|
||
this._engaged = false;
|
||
this._suspended = false;
|
||
|
||
this._style = null;
|
||
this._tabs = null;
|
||
this._modal = null;
|
||
this._modalContent = null;
|
||
|
||
this._moved = [];
|
||
this._createButtonText = null;
|
||
this._listeners = [];
|
||
this._generatedSplit = false;
|
||
this._split = null;
|
||
|
||
this._boundOwnerChange = this._onOwnerChange.bind(this);
|
||
this._boundEditingChange = this._onEditingChange.bind(this);
|
||
this._boundDelegatedClick = this._onDelegatedClick.bind(this);
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────
|
||
// Lifecycle
|
||
// ─────────────────────────────────────────────────────
|
||
|
||
start() {
|
||
if (this._started) return;
|
||
this._started = true;
|
||
this._suspended = document.documentElement.hasAttribute('data-haven-layout-editing');
|
||
|
||
document.addEventListener('haven:layout-owner-change', this._boundOwnerChange);
|
||
document.addEventListener('haven:layout-editing', this._boundEditingChange);
|
||
|
||
if (HavenApi.Data.load(ChannelTabsLayout.ID, 'layoutOn', '1') !== '0') {
|
||
this._engage(false);
|
||
}
|
||
}
|
||
|
||
stop() {
|
||
if (!this._started) return;
|
||
this._disengage(false);
|
||
document.removeEventListener('haven:layout-owner-change', this._boundOwnerChange);
|
||
document.removeEventListener('haven:layout-editing', this._boundEditingChange);
|
||
this._started = false;
|
||
}
|
||
|
||
_onOwnerChange() {
|
||
if (!this._engaged) return;
|
||
|
||
const owner = document.documentElement.getAttribute('data-haven-layout-owner');
|
||
|
||
if (owner && owner !== ChannelTabsLayout.ID) {
|
||
// Another layout has acquired ownership. Restore our DOM,
|
||
// but do not release ownership from the new layout.
|
||
this._disengage(false, false);
|
||
}
|
||
}
|
||
|
||
_onEditingChange() {
|
||
const editing = document.documentElement.hasAttribute('data-haven-layout-editing');
|
||
|
||
if (editing && this._engaged) {
|
||
this._suspended = true;
|
||
this._disengage(false);
|
||
} else if (!editing && this._suspended) {
|
||
this._suspended = false;
|
||
|
||
if (HavenApi.Data.load(ChannelTabsLayout.ID, 'layoutOn', '1' ) !== '0') {
|
||
this._engage(false);
|
||
}
|
||
}
|
||
}
|
||
|
||
_engage(persist = true) {
|
||
if (this._engaged || this._suspended) return false;
|
||
|
||
const channelsPane = document.getElementById('channels-pane');
|
||
const dmPane = document.getElementById('dm-pane');
|
||
|
||
if (!channelsPane || !dmPane) {
|
||
console.warn('[Channel Tabs] Could not find #channels-pane and #dm-pane.');
|
||
return false;
|
||
}
|
||
|
||
if (!HavenApi.Layout.acquire(ChannelTabsLayout.ID)) {
|
||
return false;
|
||
}
|
||
|
||
try {
|
||
this._engaged = true;
|
||
this._applyLayout();
|
||
|
||
if (persist) {
|
||
HavenApi.Data.save(ChannelTabsLayout.ID, 'layoutOn', '1');
|
||
}
|
||
|
||
return true;
|
||
} catch (error) {
|
||
this._restoreLayout();
|
||
this._engaged = false;
|
||
|
||
try {
|
||
HavenApi.Layout.release(ChannelTabsLayout.ID);
|
||
} catch (_) {}
|
||
|
||
console.error('[Channel Tabs] Failed to engage layout:', error);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
_disengage(persist = true, releaseOwnership = true) {
|
||
if (!this._engaged) return;
|
||
|
||
this._engaged = false;
|
||
this._restoreLayout();
|
||
|
||
if (releaseOwnership) {
|
||
try {
|
||
HavenApi.Layout.release(ChannelTabsLayout.ID);
|
||
} catch (error) {
|
||
console.warn('[Channel Tabs] Layout release failed:', error);
|
||
}
|
||
}
|
||
|
||
if (persist) {
|
||
HavenApi.Data.save(ChannelTabsLayout.ID, 'layoutOn', '0');
|
||
}
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────
|
||
// Reversible DOM manipulation
|
||
// ─────────────────────────────────────────────────────
|
||
|
||
// Only the position is remembered. Haven keeps changing these sections
|
||
// while the layout is on (the Create section is shown or hidden as
|
||
// permissions arrive), so putting back an old class or style would undo
|
||
// Haven's own update.
|
||
_moveElement(element, destination, before = null) {
|
||
if (!element || !element.parentNode) return;
|
||
|
||
this._moved.push({
|
||
element,
|
||
parent: element.parentNode,
|
||
nextSibling: element.nextSibling
|
||
});
|
||
|
||
destination.insertBefore(element, before);
|
||
}
|
||
|
||
_restoreMovedElements() {
|
||
// Restore in reverse order so sibling anchors that were moved
|
||
// elsewhere are restored before their dependants.
|
||
for (let i = this._moved.length - 1; i >= 0; i--) {
|
||
const { element, parent, nextSibling } = this._moved[i];
|
||
|
||
if (!parent) continue;
|
||
const anchor = nextSibling && nextSibling.parentNode === parent ? nextSibling : null;
|
||
|
||
parent.insertBefore(element, anchor);
|
||
}
|
||
|
||
this._moved = [];
|
||
|
||
// Put back the create button's own label, unless Haven changed it since.
|
||
const text = this._createButtonText;
|
||
if (text && text.element.textContent === text.label) {
|
||
text.element.textContent = text.original;
|
||
}
|
||
this._createButtonText = null;
|
||
}
|
||
|
||
_applyLayout() {
|
||
const channelsPane = document.getElementById('channels-pane');
|
||
const dmPane = document.getElementById('dm-pane');
|
||
|
||
if (!channelsPane || !dmPane) {
|
||
throw new Error('Required sidebar panes were not found.');
|
||
}
|
||
|
||
document.documentElement.setAttribute('data-sidebar-tabs-layout', '1');
|
||
|
||
this._installStyles();
|
||
this._ensureSplit();
|
||
this._createTabs();
|
||
this._createModal();
|
||
this._installNotificationHook();
|
||
this._moveJoinCreateSections();
|
||
this._bindEvents();
|
||
|
||
const savedTab = localStorage.getItem('activeChannelTab');
|
||
this._setChannelTab(savedTab === 'DMs' ? 'DMs' : 'channels');
|
||
}
|
||
|
||
_ensureSplit() {
|
||
const channelsPane = document.getElementById('channels-pane');
|
||
const dmPane = document.getElementById('dm-pane');
|
||
|
||
let split = document.getElementById('sidebar-split');
|
||
|
||
if (split) {
|
||
this._split = split;
|
||
return;
|
||
}
|
||
|
||
// Older Haven layouts may not have the shared split wrapper.
|
||
// Create one while remembering the original positions of its children.
|
||
split = document.createElement('div');
|
||
split.id = 'sidebar-split';
|
||
split.className = 'sidebar-split';
|
||
split.dataset.modId = 'channels';
|
||
|
||
channelsPane.parentNode.insertBefore(split, channelsPane);
|
||
|
||
this._moveElement(channelsPane, split);
|
||
|
||
const handle = document.getElementById('sidebar-split-handle');
|
||
if (handle) this._moveElement(handle, split);
|
||
|
||
this._moveElement(dmPane, split);
|
||
|
||
this._split = split;
|
||
this._generatedSplit = true;
|
||
}
|
||
|
||
_createTabs() {
|
||
if (this._tabs) return;
|
||
|
||
const tabs = document.createElement('div');
|
||
tabs.id = 'channel-dm-select';
|
||
tabs.className = 'sidebar-section channel-tabs';
|
||
tabs.dataset.modId = 'channel-dm-select';
|
||
|
||
tabs.innerHTML = `
|
||
<button type="button" class="channel-tab settings-tab" data-channel-tab="channels">
|
||
# <span>Channels</span>
|
||
<span class="channel-tab-notify-dot settings-tab" id="channels-channel-tab-notify-dot" style="display:none">
|
||
<span class="channel-tab-notify-dot-indicator"></span>
|
||
</span>
|
||
</button>
|
||
|
||
<button type="button" class="channel-tab settings-tab" data-channel-tab="DMs">
|
||
👥 <span>DMs</span>
|
||
<span class="channel-tab-notify-dot settings-tab" id="DMs-channel-tab-notify-dot" style="display:none">
|
||
<span class="channel-tab-notify-dot-indicator"></span>
|
||
</span>
|
||
</button>
|
||
|
||
<button type="button" class="join-create-btn" id="join-create-btn" title="Join or create channels" aria-label="Join or create channels">➕</button>
|
||
`;
|
||
|
||
this._split.parentNode.insertBefore(tabs, this._split);
|
||
this._tabs = tabs;
|
||
}
|
||
|
||
_createModal() {
|
||
if (this._modal) return;
|
||
|
||
const modal = document.createElement('div');
|
||
modal.id = 'join-create-modal';
|
||
modal.className = 'modal-overlay';
|
||
modal.style.display = 'none';
|
||
|
||
modal.innerHTML = `
|
||
<div class="modal modal-wide channel-tabs-modal"
|
||
role="dialog"
|
||
aria-modal="true"
|
||
aria-labelledby="channel-tabs-modal-title">
|
||
|
||
<h3 id="channel-tabs-modal-title">
|
||
➕ Join or Create Channel
|
||
</h3>
|
||
|
||
<div class="channel-tabs-modal-content"></div>
|
||
|
||
<div class="modal-actions">
|
||
<button type="button"
|
||
class="btn-sm"
|
||
id="close-join-create-btn">Close</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
document.body.appendChild(modal);
|
||
|
||
this._modal = modal;
|
||
this._modalContent = modal.querySelector('.channel-tabs-modal-content');
|
||
}
|
||
|
||
_updateChannelTabNotificationDots(channels = [], unreadCounts = {}) {
|
||
const channelDot = document.getElementById('channels-channel-tab-notify-dot');
|
||
const dmDot = document.getElementById('DMs-channel-tab-notify-dot');
|
||
|
||
if (!channelDot && !dmDot) return;
|
||
|
||
let hasChannelUnread = false;
|
||
let hasDmUnread = false;
|
||
|
||
for (const ch of channels) {
|
||
const count = Object.prototype.hasOwnProperty.call(unreadCounts, ch.code) ? unreadCounts[ch.code] : (ch.unreadCount || 0);
|
||
if (count <= 0) continue;
|
||
|
||
if (ch.is_dm) {
|
||
hasDmUnread = true;
|
||
} else {
|
||
hasChannelUnread = true;
|
||
}
|
||
|
||
if (hasChannelUnread && hasDmUnread) break;
|
||
}
|
||
|
||
if (channelDot) {
|
||
channelDot.style.display = hasChannelUnread ? '' : 'none';
|
||
}
|
||
|
||
if (dmDot) {
|
||
dmDot.style.display = hasDmUnread ? '' : 'none';
|
||
}
|
||
}
|
||
|
||
_installNotificationHook() {
|
||
const app = window.app;
|
||
|
||
if (!app || typeof app._renderChannels !== 'function') {
|
||
return false;
|
||
}
|
||
|
||
if (this._notificationHook?.app === app) {
|
||
return true;
|
||
}
|
||
|
||
this._removeNotificationHook();
|
||
|
||
const plugin = this;
|
||
const hooks = [];
|
||
|
||
// Refresh the dots after channel rendering and unread-badge updates.
|
||
for (const method of ['_renderChannels', '_updateBadge']) {
|
||
if (typeof app[method] !== 'function') continue;
|
||
|
||
const original = app[method];
|
||
|
||
const wrapped = function (...args) {
|
||
const result = original.apply(this, args);
|
||
plugin._updateChannelTabNotificationDots(this.channels, this.unreadCounts);
|
||
return result;
|
||
};
|
||
|
||
app[method] = wrapped;
|
||
hooks.push({ method, original, wrapped });
|
||
}
|
||
|
||
this._notificationHook = { app, hooks };
|
||
|
||
// Initialize the dots using the current state.
|
||
this._updateChannelTabNotificationDots(app.channels, app.unreadCounts);
|
||
|
||
return true;
|
||
}
|
||
|
||
_removeNotificationHook() {
|
||
const hook = this._notificationHook;
|
||
if (!hook) return;
|
||
|
||
for (const { method, original, wrapped } of hook.hooks) {
|
||
// Don't overwrite another component's wrapper.
|
||
if (hook.app[method] === wrapped) {
|
||
hook.app[method] = original;
|
||
}
|
||
}
|
||
|
||
this._notificationHook = null;
|
||
}
|
||
|
||
_moveJoinCreateSections() {
|
||
const joinSection = document.querySelector(
|
||
'.sidebar-section[data-mod-id="join"][data-haven-region="join-channel"]'
|
||
);
|
||
|
||
const createSection = document.getElementById('admin-controls');
|
||
|
||
if (joinSection) {
|
||
this._moveElement(joinSection, this._modalContent);
|
||
}
|
||
|
||
if (createSection) {
|
||
this._moveElement(createSection, this._modalContent);
|
||
}
|
||
|
||
const createButton = document.getElementById('create-channel-btn');
|
||
if (createButton) {
|
||
const label = 'Create';
|
||
this._createButtonText = { element: createButton, original: createButton.textContent, label };
|
||
createButton.textContent = label;
|
||
}
|
||
}
|
||
|
||
|
||
// ─────────────────────────────────────────────────────
|
||
// Tab behavior
|
||
// ─────────────────────────────────────────────────────
|
||
|
||
_setChannelTab(tab) {
|
||
if (tab !== 'channels' && tab !== 'DMs') return;
|
||
|
||
if (!this._tabs || !this._split) return;
|
||
|
||
localStorage.setItem('activeChannelTab', tab);
|
||
|
||
this._tabs.querySelectorAll('[data-channel-tab]').forEach(button => {
|
||
const active = button.dataset.channelTab === tab;
|
||
|
||
button.classList.toggle('active', active);
|
||
button.setAttribute('aria-selected', String(active));
|
||
});
|
||
|
||
const channelsPane = document.getElementById('channels-pane');
|
||
const dmPane = document.getElementById('dm-pane');
|
||
|
||
if (channelsPane) {
|
||
channelsPane.classList.toggle('pane-hidden', tab !== 'channels');
|
||
}
|
||
|
||
if (dmPane) {
|
||
dmPane.classList.toggle('pane-hidden', tab !== 'DMs');
|
||
}
|
||
}
|
||
|
||
_onDelegatedClick(event) {
|
||
const tabButton = event.target.closest('[data-channel-tab]');
|
||
|
||
if (tabButton && this._tabs?.contains(tabButton)) {
|
||
this._setChannelTab(tabButton.dataset.channelTab);
|
||
return;
|
||
}
|
||
|
||
if (event.target.closest('#join-create-btn')) {
|
||
this._openModal();
|
||
return;
|
||
}
|
||
|
||
if (event.target.closest('#close-join-create-btn')) {
|
||
this._closeModal();
|
||
return;
|
||
}
|
||
|
||
if (event.target === this._modal) {
|
||
this._closeModal();
|
||
return;
|
||
}
|
||
|
||
// Selecting an item in either list selects the matching tab.
|
||
if (event.target.closest('#channel-list .channel-item')) {
|
||
this._setChannelTab('channels');
|
||
return;
|
||
}
|
||
|
||
if (event.target.closest('#dm-list .channel-item')) {
|
||
this._setChannelTab('DMs');
|
||
return;
|
||
}
|
||
|
||
// Let Haven's existing join/create handlers process the form first.
|
||
if (event.target.closest('#join-channel-btn')) {
|
||
const input = document.getElementById('channel-code-input');
|
||
|
||
if (input?.value.trim()) {
|
||
setTimeout(() => {
|
||
if (this._modal) this._closeModal();
|
||
this._setChannelTab('channels');
|
||
}, 0);
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
if (event.target.closest('#create-channel-btn')) {
|
||
const input = document.getElementById('new-channel-name');
|
||
|
||
if (input?.value.trim()) {
|
||
setTimeout(() => {
|
||
if (this._modal) this._closeModal();
|
||
this._setChannelTab('channels');
|
||
}, 0);
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
if (event.target.closest('#create-temp-channel-btn')) {
|
||
setTimeout(() => {
|
||
if (this._modal) this._closeModal();
|
||
this._setChannelTab('channels');
|
||
}, 0);
|
||
}
|
||
}
|
||
|
||
_bindEvents() {
|
||
document.addEventListener('click', this._boundDelegatedClick, true);
|
||
|
||
this._listeners.push(() => {
|
||
document.removeEventListener('click', this._boundDelegatedClick, true);
|
||
});
|
||
|
||
const keyHandler = event => {
|
||
if (event.key === 'Escape' && this._modal?.style.display !== 'none') {
|
||
this._closeModal();
|
||
}
|
||
};
|
||
|
||
document.addEventListener('keydown', keyHandler);
|
||
|
||
this._listeners.push(() => {
|
||
document.removeEventListener('keydown', keyHandler);
|
||
});
|
||
}
|
||
|
||
_openModal() {
|
||
if (!this._modal) return;
|
||
|
||
// Haven shows or hides the Create section by permission; leave it be.
|
||
this._modal.style.display = 'flex';
|
||
document.getElementById('channel-code-input')?.focus();
|
||
}
|
||
|
||
_closeModal() {
|
||
if (!this._modal) return;
|
||
|
||
this._modal.style.display = 'none';
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────
|
||
// Styling
|
||
// ─────────────────────────────────────────────────────
|
||
|
||
_installStyles() {
|
||
if (this._style) return;
|
||
|
||
const style = document.createElement('style');
|
||
style.id = 'sidebar-tabs-layout-styles';
|
||
|
||
style.textContent = `
|
||
html[data-sidebar-tabs-layout="1"] #sidebar-split .channel-section,
|
||
html[data-sidebar-tabs-layout="1"] #sidebar-split .dm-section-pane {
|
||
display: flex;
|
||
flex-direction: column;
|
||
flex: 1 1 0px !important;
|
||
min-height: 0;
|
||
overflow: hidden;
|
||
}
|
||
|
||
html[data-sidebar-tabs-layout="1"] .temp-channel-create-btn {
|
||
display: none !important;
|
||
}
|
||
|
||
html[data-sidebar-tabs-layout="1"] .sidebar-mod-container {
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-height: 0;
|
||
}
|
||
|
||
html[data-sidebar-tabs-layout="1"] #sidebar-split {
|
||
display: flex !important;
|
||
flex: 1 1 0;
|
||
flex-direction: column;
|
||
min-height: 0;
|
||
overflow: hidden;
|
||
}
|
||
|
||
html[data-sidebar-tabs-layout="1"] #sidebar-split-handle {
|
||
display: none !important;
|
||
}
|
||
|
||
html[data-sidebar-tabs-layout="1"] #sidebar-split .channel-section,
|
||
html[data-sidebar-tabs-layout="1"] #sidebar-split .dm-section-pane {
|
||
display: flex;
|
||
flex: 1 1 0;
|
||
flex-direction: column;
|
||
min-height: 0;
|
||
overflow: hidden;
|
||
}
|
||
|
||
html[data-sidebar-tabs-layout="1"] #sidebar-split .channel-list,
|
||
html[data-sidebar-tabs-layout="1"] #sidebar-split .dm-list-scroll {
|
||
flex: 1 1 0;
|
||
min-height: 0;
|
||
overflow-x: hidden;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
html[data-sidebar-tabs-layout="1"] #sidebar-split .pane-hidden {
|
||
display: none !important;
|
||
}
|
||
|
||
html[data-sidebar-tabs-layout="1"] .channel-tabs {
|
||
display: flex;
|
||
flex: 0 0 auto;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
html[data-sidebar-tabs-layout="1"] .channel-tab {
|
||
position: relative;
|
||
padding: 0.3125rem 0.875rem;
|
||
font-size: 0.75rem;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition:
|
||
background 0.15s,
|
||
color 0.15s,
|
||
border-color 0.15s;
|
||
}
|
||
|
||
html[data-sidebar-tabs-layout="1"] .channel-tab:hover {
|
||
background: var(--bg-tertiary);
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
html[data-sidebar-tabs-layout="1"] .channel-tab.active {
|
||
background: var(--accent);
|
||
color: var(--accent-text);
|
||
border-color: var(--accent);
|
||
}
|
||
|
||
html[data-sidebar-tabs-layout="1"] .channel-tab-notify-dot {
|
||
position: absolute;
|
||
top: -1px;
|
||
right: -1px;
|
||
width: 13px;
|
||
height: 13px;
|
||
background-color: var(--bg-secondary);
|
||
padding: 0 0;
|
||
border-top: none;
|
||
border-right: none;
|
||
border-radius: 0px 0px 0px 75%;
|
||
}
|
||
|
||
html[data-sidebar-tabs-layout="1"] .channel-tab-notify-dot-indicator {
|
||
position: absolute;
|
||
top: 0px;
|
||
right: 0px;
|
||
width: 8px;
|
||
height: 8px;
|
||
background-color: var(--danger);
|
||
border-radius: 50%;
|
||
}
|
||
|
||
html[data-sidebar-tabs-layout="1"] .join-create-btn {
|
||
margin-left: auto;
|
||
border-radius: var(--radius-sm);
|
||
background: transparent;
|
||
font-size: 1.2em;
|
||
}
|
||
|
||
html[data-sidebar-tabs-layout="1"] .join-create-btn:hover {
|
||
background: var(--bg-hover);
|
||
}
|
||
|
||
html[data-sidebar-tabs-layout="1"] #join-create-modal {
|
||
z-index: 10000;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
html[data-sidebar-tabs-layout="1"] #join-create-modal .channel-tabs-modal {
|
||
display: flex;
|
||
flex-direction: column;
|
||
width: min(450px, calc(100vw - 24px));
|
||
max-height: 85vh;
|
||
min-width: min(320px, calc(100vw - 24px));
|
||
overflow: hidden;
|
||
}
|
||
|
||
html[data-sidebar-tabs-layout="1"] #join-create-modal
|
||
.channel-tabs-modal-content {
|
||
flex: 1 1 auto;
|
||
min-height: 0;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
html[data-sidebar-tabs-layout="1"] #join-create-modal
|
||
.channel-tabs-modal-content > .sidebar-section {
|
||
margin-bottom: 8px;
|
||
padding: 0.75rem 1rem;
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
|
||
html[data-sidebar-tabs-layout="1"] #join-create-modal .modal-actions {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
flex: 0 0 auto;
|
||
margin-top: auto;
|
||
padding-top: 12px;
|
||
}
|
||
`;
|
||
|
||
document.head.appendChild(style);
|
||
this._style = style;
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────
|
||
// Restoration
|
||
// ─────────────────────────────────────────────────────
|
||
|
||
_restoreLayout() {
|
||
for (const cleanup of this._listeners.splice(0)) {
|
||
try {
|
||
cleanup();
|
||
} catch (_) {}
|
||
}
|
||
|
||
// Sections go home before the modal holding them is removed.
|
||
this._restoreMovedElements();
|
||
|
||
if (this._modal) {
|
||
this._modal.remove();
|
||
this._modal = null;
|
||
this._modalContent = null;
|
||
}
|
||
|
||
if (this._tabs) {
|
||
this._tabs.remove();
|
||
this._tabs = null;
|
||
}
|
||
|
||
for (const id of ['channels-pane', 'dm-pane']) {
|
||
document.getElementById(id)?.classList.remove('pane-hidden');
|
||
}
|
||
|
||
this._removeNotificationHook();
|
||
|
||
if (this._generatedSplit && this._split) {
|
||
this._split.remove();
|
||
}
|
||
|
||
this._split = null;
|
||
this._generatedSplit = false;
|
||
|
||
if (this._style) {
|
||
this._style.remove();
|
||
this._style = null;
|
||
}
|
||
|
||
document.documentElement.removeAttribute('data-sidebar-tabs-layout');
|
||
}
|
||
}
|
||
|
||
if (typeof module !== 'undefined') {
|
||
module.exports = ChannelTabsLayout;
|
||
}
|
||
|
||
if (typeof _win !== 'undefined') {
|
||
_win.ChannelTabsLayout = ChannelTabsLayout;
|
||
}
|