diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2d29364..626be1f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,33 @@
# Changelog
+## Changes 11/8/2025 (v1.8.12)
+
+release(v1.8.12): auth UI & DnD polish — show OIDC, auto-SSO, right-aligned header icons
+
+- auth (public/js/main.js)
+ - Robust login options: tolerate key variants (disableFormLogin/disable_form_login, etc.).
+ - Correctly show/hide wrapper + individual methods (form/OIDC/basic).
+ - Auto-SSO when OIDC is the only enabled method; add opt-out with `?noauto=1`.
+ - Minor cleanup (SW register catch spacing).
+
+- drag & drop (public/js/dragAndDrop.js)
+ - Reworked zones model: Sidebar / Top (left/right) / Header (icon+modal).
+ - Persist user layout with `userZonesSnapshot.v2` and responsive stash for small screens.
+ - Live UI sync: toggle icon (`material-icons`) updates immediately after moves.
+ - Smarter small-screen behavior: lift sidebar cards ephemerally; restore only what belonged to sidebar.
+ - Cleaner header icon modal plumbing; remove legacy/dead code.
+
+- styles (public/css/styles.css)
+ - Header drop zone fills remaining space and right-aligns its icons.
+
+UX:
+
+- OIDC button reliably appears when form/basic are disabled.
+- If OIDC is the sole method, users are taken straight to the provider (unless `?noauto=1`).
+- Header icons sit with the other header actions (right-aligned), and the toggle icon reflects layout changes instantly.
+
+---
+
## Changes 11/8/2025 (v1.8.11)
release(v1.8.11): fix(oidc): always send PKCE (S256) and treat empty secret as public client
diff --git a/public/css/styles.css b/public/css/styles.css
index c558c89..396a8b5 100644
--- a/public/css/styles.css
+++ b/public/css/styles.css
@@ -141,7 +141,15 @@ body {
}#userDropdownToggle {
border-radius: 4px !important;
padding: 6px 10px !important;
- }.header-buttons button:hover {
+ }
+ /* make the drop zone fill leftover space and right-align its own icons */
+#headerDropArea.header-drop-zone{
+ display: flex;
+ justify-content: flex-end;
+ min-width: 100px;
+
+}
+ .header-buttons button:hover {
background-color: rgba(255, 255, 255, 0.2);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
color: #fff;
diff --git a/public/js/dragAndDrop.js b/public/js/dragAndDrop.js
index 4f74dd3..26d8e94 100644
--- a/public/js/dragAndDrop.js
+++ b/public/js/dragAndDrop.js
@@ -1,528 +1,385 @@
// dragAndDrop.js
-// Enhances the dashboard with drag-and-drop functionality for cards:
-// - injects a tiny floating toggle btn
-// - remembers collapsed state in localStorage
-// - keeps the original card DnD + order logic intact
-
-// ---- responsive defaults ----
-const MEDIUM_MIN = 1205; // matches your small-screen cutoff
-const MEDIUM_MAX = 1600;
+// Cards can live in 3 places and will persist across refresh:
+// - Sidebar: #sidebarDropArea
+// - Top zone: #leftCol or #rightCol
+// - Header zone: #headerDropArea (as icons with modal)
+// Responsive rule remains:
+// - Wide screens default to sidebar.
+// - Small screens auto-lift sidebar cards into top zone (ephemeral, does NOT overwrite saved layout).
+// -------------------- constants --------------------
+const MEDIUM_MIN = 1205; // small-screen cutoff
const TOGGLE_TOP_PX = 8;
-const TOGGLE_LEFT_PX = 50;
-
+const TOGGLE_LEFT_PX = 65;
const TOGGLE_ICON_OPEN = 'view_sidebar';
const TOGGLE_ICON_CLOSED = 'menu';
-// Cards we manage
-const KNOWN_CARD_IDS = ['uploadCard', 'folderManagementCard'];
-
const CARD_IDS = ['uploadCard', 'folderManagementCard'];
+const ZONES = {
+ SIDEBAR: 'sidebarDropArea',
+ TOP_LEFT: 'leftCol',
+ TOP_RIGHT: 'rightCol',
+ HEADER: 'headerDropArea',
+};
+const LAYOUT_KEY = 'userZonesSnapshot.v2'; // {cardId: zoneId}
+const RESPONSIVE_STASH_KEY = 'responsiveSidebarSnapshot.v2'; // [cardId]
-// --- NEW: separate user snapshot so refresh restores *manual* placements only ---
-const USER_SNAPSHOT_KEY = 'userZonesSnapshot'; // { cardId: 'sidebarDropArea'|'leftCol'|'rightCol' }
+// -------------------- small helpers --------------------
+function $(id) { return document.getElementById(id); }
+function getSidebar() { return $(ZONES.SIDEBAR); }
+function getTopZone() { return $('uploadFolderRow'); }
+function getLeftCol() { return $(ZONES.TOP_LEFT); }
+function getRightCol() { return $(ZONES.TOP_RIGHT); }
+function getHeaderDropArea() { return $(ZONES.HEADER); }
+function isSmallScreen() { return window.innerWidth < MEDIUM_MIN; }
+function getCards() { return CARD_IDS.map(id => $(id)).filter(Boolean); }
-function hasUserSnapshot() {
- try { const s = JSON.parse(localStorage.getItem(USER_SNAPSHOT_KEY) || '{}'); return !!s && Object.keys(s).length > 0; } catch { return false; }
+function readLayout() {
+ try { return JSON.parse(localStorage.getItem(LAYOUT_KEY) || '{}'); }
+ catch { return {}; }
+}
+function writeLayout(layout) {
+ localStorage.setItem(LAYOUT_KEY, JSON.stringify(layout || {}));
}
-function isDarkMode() {
- return document.body.classList.contains('dark-mode');
+function setLayoutFor(cardId, zoneId) {
+ const layout = readLayout();
+ layout[cardId] = zoneId;
+ writeLayout(layout);
}
function themeToggleButton(btn) {
if (!btn) return;
- if (isDarkMode()) {
- btn.style.background = '#2c2c2c';
- btn.style.border = '1px solid #555';
- btn.style.boxShadow = '0 2px 6px rgba(0,0,0,.35)';
- btn.style.color = '#e0e0e0'; // <- material icon inherits this
- } else {
- btn.style.background = '#fff';
- btn.style.border = '1px solid #ccc';
- btn.style.boxShadow = '0 2px 6px rgba(0,0,0,.15)';
- btn.style.color = '#222'; // <- material icon inherits this
- }
+ const dark = document.body.classList.contains('dark-mode');
+ btn.style.background = dark ? '#2c2c2c' : '#fff';
+ btn.style.border = dark ? '1px solid #555' : '1px solid #ccc';
+ btn.style.boxShadow = dark ? '0 2px 6px rgba(0,0,0,.35)' : '0 2px 6px rgba(0,0,0,.15)';
+ btn.style.color = dark ? '#e0e0e0' : '#222';
}
-function getKnownCards() {
- return CARD_IDS
- .map(id => document.getElementById(id))
- .filter(Boolean);
-}
-
-// Save current container for each card so we can restore after refresh.
-function snapshotZoneLocations() {
- const snap = {};
- getKnownCards().forEach(card => {
- const p = card.parentNode;
- snap[card.id] = p && p.id ? p.id : '';
+function animateVerticalSlide(card) {
+ card.style.transform = 'translateY(30px)';
+ card.style.opacity = '0';
+ card.offsetWidth; // reflow
+ requestAnimationFrame(() => {
+ card.style.transition = 'transform 0.25s ease, opacity 0.25s ease';
+ card.style.transform = 'translateY(0)';
+ card.style.opacity = '1';
});
- localStorage.setItem('zonesSnapshot', JSON.stringify(snap));
+ setTimeout(() => {
+ card.style.transition = '';
+ card.style.transform = '';
+ card.style.opacity = '';
+ }, 260);
}
-// NEW: Save where the user *manually* placed cards (used on normal refresh).
-function snapshotUserZones() {
- const snap = {};
- getKnownCards().forEach(card => {
- const p = card.parentNode;
- snap[card.id] = p && p.id ? p.id : '';
- });
- localStorage.setItem(USER_SNAPSHOT_KEY, JSON.stringify(snap));
+// -------------------- header (icon+modal) --------------------
+function saveHeaderOrder() {
+ const host = getHeaderDropArea();
+ if (!host) return;
+ const order = Array.from(host.children).map(btn => btn.cardElement?.id).filter(Boolean);
+ localStorage.setItem('headerOrder', JSON.stringify(order));
}
-// Move a card to default expanded spot (your request: sidebar is default).
-function moveCardToSidebarDefault(card) {
- const sidebar = getSidebar();
- if (sidebar) {
- sidebar.appendChild(card);
- card.style.width = '100%';
- animateVerticalSlide(card);
- }
-}
-
-// Remove any header icon/modal for a card (so it truly leaves header mode).
-function stripHeaderArtifacts(card) {
- if (card.headerIconButton) {
- if (card.headerIconButton.modalInstance) {
- try { card.headerIconButton.modalInstance.remove(); } catch { }
- }
- try { card.headerIconButton.remove(); } catch { }
- card.headerIconButton = null;
- }
-}
-
-// Kill the 50px ghost gutter when the sidebar isn't participating in layout.
-function clampSidebarWhenEmpty() {
- const sidebar = getSidebar();
- if (!sidebar) return;
-
- const sidebarHasCards = hasSidebarCards();
- const collapsed = isZonesCollapsed();
-
- // Sidebar should not take space if it's collapsed OR has no cards.
- const shouldHideSidebarSpace = collapsed || !sidebarHasCards;
-
- if (shouldHideSidebarSpace) {
- // Make sure it takes absolutely no horizontal space.
- sidebar.style.display = 'none'; // don't render at all
- sidebar.style.width = '0px';
- sidebar.style.minWidth = '0px';
- sidebar.style.margin = '0';
- sidebar.style.padding = '0';
- sidebar.style.flex = '0 0 0px';
-
- // if you add/remove highlight elsewhere, also ensure it's not forcing size
- sidebar.classList.remove('active');
- } else {
- // Let your CSS control it when it's actually visible/has cards.
- sidebar.style.width = '';
- sidebar.style.minWidth = '';
- sidebar.style.margin = '';
- sidebar.style.padding = '';
- sidebar.style.flex = '';
- // display is already decided by updateSidebarVisibility/applyZonesCollapsed
- }
-}
-
-// Let the sidebar become a real drop target during drag, even if empty.
-function unclampSidebarForDrag() {
- const sidebar = getSidebar();
- if (!sidebar) return;
- // only un-clamp if panels are not collapsed
- if (!isZonesCollapsed()) {
- sidebar.style.display = 'block';
- // give it a sensible min width so the highlight looks right
- sidebar.style.minWidth = '280px';
- // never force a fixed height here; let CSS layout handle it
- sidebar.style.width = '';
- sidebar.style.flex = ''; // don't lock flex while dragging
- sidebar.style.margin = '';
- sidebar.style.padding = '';
- }
-}
-
-// Restore cards after “expand” (toggle off) or after refresh.
-// - If we have a snapshot, use it.
-// - If not, put all cards in the sidebar (your default).
-function restoreCardsFromSnapshot() {
- const sidebar = getSidebar();
- const leftCol = document.getElementById('leftCol');
- const rightCol = document.getElementById('rightCol');
-
- let snap = {};
- try { snap = JSON.parse(localStorage.getItem('zonesSnapshot') || '{}'); } catch { }
-
- getKnownCards().forEach(card => {
- stripHeaderArtifacts(card);
- const destId = snap[card.id] || 'sidebarDropArea'; // fallback to sidebar
- const dest =
- destId === 'leftCol' ? leftCol :
- destId === 'rightCol' ? rightCol :
- destId === 'sidebarDropArea' ? sidebar :
- sidebar; // final fallback
- card.style.width = '';
- card.style.minWidth = '';
- if (dest) dest.appendChild(card);
- });
-
- // Clear header icons storage because we’re expanded.
- localStorage.removeItem('headerOrder');
- const headerDropArea = document.getElementById('headerDropArea');
- if (headerDropArea) headerDropArea.innerHTML = '';
-
- updateTopZoneLayout();
- updateSidebarVisibility();
- ensureZonesToggle();
- updateZonesToggleUI();
-}
-
-// Read the saved snapshot (or {} if none)
-function readZonesSnapshot() {
- try {
- return JSON.parse(localStorage.getItem('zonesSnapshot') || '{}');
- } catch {
- return {};
- }
-}
-
-// Move a card into the header zone as an icon (uses your existing helper)
-function moveCardToHeader(card) {
- // If it's already in header icon form, skip
- if (card.headerIconButton && card.headerIconButton.parentNode) return;
- insertCardInHeader(card, null);
-}
-
-// Collapse behavior: snapshot locations, then move all known cards to header as icons
-function collapseCardsToHeader() {
- const headerDropArea = document.getElementById('headerDropArea');
- if (headerDropArea) headerDropArea.style.display = 'inline-flex'; // NEW
-
- getKnownCards().forEach(card => {
- if (!card.headerIconButton) insertCardInHeader(card, null);
- });
-
- updateTopZoneLayout();
- updateSidebarVisibility();
- ensureZonesToggle();
- updateZonesToggleUI();
-}
-
-// Clean up any header icon (button + modal) attached to a card
function removeHeaderIconForCard(card) {
- if (card.headerIconButton) {
- const btn = card.headerIconButton;
- const modal = btn.modalInstance;
- if (btn.parentNode) btn.parentNode.removeChild(btn);
- if (modal && modal.parentNode) modal.parentNode.removeChild(modal);
- card.headerIconButton = null;
- }
+ if (!card || !card.headerIconButton) return;
+ const btn = card.headerIconButton;
+ const modal = btn.modalInstance;
+ if (btn.parentNode) btn.parentNode.removeChild(btn);
+ if (modal && modal.parentNode) modal.parentNode.removeChild(modal);
+ card.headerIconButton = null;
}
-// Apply *user* snapshot on normal load (not expand) to preserve manual placement.
-function applySnapshotIfPresent() {
- let snap = {};
- try { snap = JSON.parse(localStorage.getItem(USER_SNAPSHOT_KEY) || '{}'); } catch { snap = {}; }
- const keys = Object.keys(snap || {});
- if (!keys.length) return false;
+function insertCardInHeader(card) {
+ const host = getHeaderDropArea();
+ if (!host) return;
+ // Ensure hidden container exists to park real cards while icon-visible.
+ let hidden = $('hiddenCardsContainer');
+ if (!hidden) {
+ hidden = document.createElement('div');
+ hidden.id = 'hiddenCardsContainer';
+ hidden.style.display = 'none';
+ document.body.appendChild(hidden);
+ }
+ if (card.parentNode?.id !== 'hiddenCardsContainer') hidden.appendChild(card);
- const sidebar = getSidebar();
- const leftCol = document.getElementById('leftCol');
- const rightCol = document.getElementById('rightCol');
+ if (card.headerIconButton && card.headerIconButton.parentNode) return;
- getKnownCards().forEach(card => {
- const destId = snap[card.id];
- const dest =
- destId === 'leftCol' ? leftCol :
- destId === 'rightCol' ? rightCol :
- destId === 'sidebarDropArea' ? sidebar : null;
- if (dest) {
- // clear sticky widths if coming from sidebar/header
+ const iconButton = document.createElement('button');
+ iconButton.className = 'header-card-icon';
+ iconButton.style.border = 'none';
+ iconButton.style.background = 'none';
+ iconButton.style.cursor = 'pointer';
+ iconButton.innerHTML = `${card.id === 'uploadCard' ? 'cloud_upload'
+ : card.id === 'folderManagementCard' ? 'folder'
+ : 'insert_drive_file'
+ }`;
+
+ iconButton.cardElement = card;
+ card.headerIconButton = iconButton;
+
+ let modal = null;
+ let isLocked = false;
+ let hoverActive = false;
+
+ function ensureModal() {
+ if (modal) return;
+ modal = document.createElement('div');
+ modal.className = 'header-card-modal';
+ Object.assign(modal.style, {
+ position: 'fixed',
+ top: '55px',
+ right: '80px',
+ zIndex: '11000',
+ display: 'block',
+ visibility: 'hidden',
+ opacity: '0',
+ background: 'none',
+ border: 'none',
+ padding: '0',
+ boxShadow: 'none',
+ maxWidth: '440px',
+ width: 'max-content'
+ });
+ document.body.appendChild(modal);
+ iconButton.modalInstance = modal;
+ modal.addEventListener('mouseover', () => { hoverActive = true; showModal(); });
+ modal.addEventListener('mouseout', () => { hoverActive = false; maybeHide(); });
+ }
+
+ function showModal() {
+ ensureModal();
+ if (!modal.contains(card)) {
+ let hidden = $('hiddenCardsContainer');
+ if (hidden && hidden.contains(card)) hidden.removeChild(card);
card.style.width = '';
card.style.minWidth = '';
- dest.appendChild(card);
+ modal.appendChild(card);
}
+ modal.style.visibility = 'visible';
+ modal.style.opacity = '1';
+ }
+ function hideModal() {
+ if (!modal) return;
+ modal.style.visibility = 'hidden';
+ modal.style.opacity = '0';
+ const hidden = $('hiddenCardsContainer');
+ if (hidden && modal.contains(card)) hidden.appendChild(card);
+ }
+ function maybeHide() {
+ setTimeout(() => {
+ if (!hoverActive && !isLocked) hideModal();
+ }, 200);
+ }
+
+ iconButton.addEventListener('mouseover', () => { hoverActive = true; showModal(); });
+ iconButton.addEventListener('mouseout', () => { hoverActive = false; maybeHide(); });
+ iconButton.addEventListener('click', (e) => {
+ e.stopPropagation();
+ isLocked = !isLocked;
+ if (isLocked) showModal(); else hideModal();
});
- // prevent first-run default from stomping this on reload
- localStorage.setItem('layoutDefaultApplied_v1', '1');
- return true;
+ host.appendChild(iconButton);
+ saveHeaderOrder();
}
-// New: small-screen detector
-function isSmallScreen() { return window.innerWidth < MEDIUM_MIN; }
+// -------------------- placement --------------------
+function placeCardInZone(card, zoneId, { animate = true } = {}) {
+ if (!card) return;
-// New: remember which cards were in the sidebar right before we go small
-const RESPONSIVE_SNAPSHOT_KEY = 'responsiveSidebarSnapshot';
+ // If moving out of header, remove header artifacts
+ if (zoneId !== ZONES.HEADER) removeHeaderIconForCard(card);
-function snapshotSidebarCardsForResponsive() {
+ switch (zoneId) {
+ case ZONES.SIDEBAR: {
+ const sb = getSidebar();
+ if (!sb) return;
+ card.style.width = '100%';
+ card.style.minWidth = '';
+ sb.appendChild(card);
+ if (animate) animateVerticalSlide(card);
+ card.dataset.originalContainerId = ZONES.SIDEBAR;
+ break;
+ }
+ case ZONES.TOP_LEFT: {
+ const col = getLeftCol();
+ if (!col) return;
+ card.style.width = '';
+ card.style.minWidth = '';
+ col.appendChild(card);
+ if (animate) animateVerticalSlide(card);
+ card.dataset.originalContainerId = ZONES.TOP_LEFT;
+ break;
+ }
+ case ZONES.TOP_RIGHT: {
+ const col = getRightCol();
+ if (!col) return;
+ card.style.width = '';
+ card.style.minWidth = '';
+ col.appendChild(card);
+ if (animate) animateVerticalSlide(card);
+ card.dataset.originalContainerId = ZONES.TOP_RIGHT;
+ break;
+ }
+ case ZONES.HEADER: {
+ insertCardInHeader(card);
+ break;
+ }
+ }
+}
+
+function currentZoneForCard(card) {
+ if (!card || !card.parentNode) return null;
+ const pid = card.parentNode.id || '';
+ if (pid === 'hiddenCardsContainer' && card.headerIconButton) return ZONES.HEADER;
+ if ([ZONES.SIDEBAR, ZONES.TOP_LEFT, ZONES.TOP_RIGHT, ZONES.HEADER].includes(pid)) return pid;
+ // If card is temporarily in modal (header), treat as header
+ if (card.headerIconButton && card.headerIconButton.modalInstance?.contains(card)) return ZONES.HEADER;
+ return pid || null;
+}
+
+function saveCurrentLayout() {
+ const layout = {};
+ getCards().forEach(card => {
+ const zone = currentZoneForCard(card);
+ if (zone) layout[card.id] = zone;
+ });
+ writeLayout(layout);
+}
+
+function applyUserLayoutOrDefault() {
+ const layout = readLayout();
+ const hasAny = Object.keys(layout).length > 0;
+
+ // If we have saved user layout, honor it
+ if (hasAny) {
+ getCards().forEach(card => {
+ const targetZone = layout[card.id];
+ if (!targetZone) return;
+ // On small screens: if saved zone is the sidebar, temporarily place in top cols
+ if (isSmallScreen() && targetZone === ZONES.SIDEBAR) {
+ const target = (card.id === 'uploadCard') ? ZONES.TOP_LEFT : ZONES.TOP_RIGHT;
+ placeCardInZone(card, target, { animate: false });
+ } else {
+ placeCardInZone(card, targetZone, { animate: false });
+ }
+ });
+ updateTopZoneLayout();
+ updateSidebarVisibility();
+ return;
+ }
+
+ // No saved layout yet: apply defaults
+ if (!isSmallScreen()) {
+ // Wide: default both to sidebar (if not already)
+ getCards().forEach(c => placeCardInZone(c, ZONES.SIDEBAR, { animate: false }));
+ } else {
+ // Small: deterministic mapping
+ getCards().forEach(c => {
+ const zone = (c.id === 'uploadCard') ? ZONES.TOP_LEFT : ZONES.TOP_RIGHT;
+ placeCardInZone(c, zone, { animate: false });
+ });
+ }
+ updateTopZoneLayout();
+ updateSidebarVisibility();
+ saveCurrentLayout(); // initialize baseline so future moves persist
+}
+
+// -------------------- responsive stash --------------------
+function stashSidebarCardsBeforeSmall() {
const sb = getSidebar();
if (!sb) return;
- const ids = Array.from(sb.querySelectorAll('#uploadCard, #folderManagementCard'))
- .map(el => el.id);
- localStorage.setItem(RESPONSIVE_SNAPSHOT_KEY, JSON.stringify(ids));
+ const ids = Array.from(sb.querySelectorAll('#uploadCard, #folderManagementCard')).map(el => el.id);
+ localStorage.setItem(RESPONSIVE_STASH_KEY, JSON.stringify(ids));
}
-
-function readResponsiveSnapshot() {
- try { return JSON.parse(localStorage.getItem(RESPONSIVE_SNAPSHOT_KEY) || '[]'); }
+function readSidebarStash() {
+ try { return JSON.parse(localStorage.getItem(RESPONSIVE_STASH_KEY) || '[]'); }
catch { return []; }
}
+function clearSidebarStash() { localStorage.removeItem(RESPONSIVE_STASH_KEY); }
-function clearResponsiveSnapshot() {
- localStorage.removeItem(RESPONSIVE_SNAPSHOT_KEY);
-}
-
-// New: deterministic mapping from card -> top column
-function moveCardToTopByMapping(card) {
- const leftCol = document.getElementById('leftCol');
- const rightCol = document.getElementById('rightCol');
- if (!leftCol || !rightCol) return;
-
- const target = (card.id === 'uploadCard') ? leftCol :
- (card.id === 'folderManagementCard') ? rightCol : leftCol;
-
- // clear any sticky widths from sidebar/header
- card.style.width = '';
- card.style.minWidth = '';
- target.appendChild(card);
- card.dataset.originalContainerId = target.id;
- animateVerticalSlide(card);
-}
-
-// New: move all sidebar cards to top (used when we cross into small)
-function moveAllSidebarCardsToTop() {
+function moveAllSidebarCardsToTopEphemeral() {
const sb = getSidebar();
if (!sb) return;
- const cards = Array.from(sb.querySelectorAll('#uploadCard, #folderManagementCard'));
- cards.forEach(moveCardToTopByMapping);
+ Array.from(sb.querySelectorAll('#uploadCard, #folderManagementCard')).forEach(card => {
+ const target = (card.id === 'uploadCard') ? ZONES.TOP_LEFT : ZONES.TOP_RIGHT;
+ placeCardInZone(card, target, { animate: true });
+ });
+ // do NOT save layout here (ephemeral)
updateTopZoneLayout();
updateSidebarVisibility();
}
-// New: enforce responsive behavior (sidebar disabled on small screens)
-// Add hysteresis to avoid flapping near threshold
-let __lastIsSmall = null;
-let __lastWidth = null;
-const SMALL_ENTER = MEDIUM_MIN - 16; // enter small below this
-const SMALL_EXIT = MEDIUM_MIN + 16; // leave small above this
-
+let __wasSmall = null;
function enforceResponsiveZones() {
- const w = window.innerWidth;
- const prevSmall = __lastIsSmall;
- let nowSmall;
+ const nowSmall = isSmallScreen();
+ if (__wasSmall === null) { __wasSmall = nowSmall; }
- if (__lastWidth == null) {
- nowSmall = w < MEDIUM_MIN;
- } else if (prevSmall === true) {
- nowSmall = !(w >= SMALL_EXIT);
- } else if (prevSmall === false) {
- nowSmall = (w < SMALL_ENTER);
- } else {
- nowSmall = w < MEDIUM_MIN;
- }
- __lastWidth = w;
-
- const sidebar = getSidebar();
- const topZone = getTopZone();
-
- if (nowSmall && prevSmall !== true) {
- // entering small: remember what was in sidebar, move them up, hide sidebar
- snapshotSidebarCardsForResponsive();
- moveAllSidebarCardsToTop();
- if (sidebar) sidebar.style.display = 'none';
- if (topZone) topZone.style.display = ''; // ensure visible
- __lastIsSmall = true;
- } else if (!nowSmall && prevSmall !== false) {
- // leaving small: restore only what used to be in the sidebar
- const ids = readResponsiveSnapshot();
+ if (nowSmall && __wasSmall === false) {
+ // entering small: remember what was in sidebar, then lift them
+ stashSidebarCardsBeforeSmall();
+ moveAllSidebarCardsToTopEphemeral();
+ const sb = getSidebar();
+ if (sb) sb.style.display = 'none';
+ } else if (!nowSmall && __wasSmall === true) {
+ // leaving small: restore only those that used to be in sidebar *if* saved layout says sidebar
+ const ids = readSidebarStash();
+ const layout = readLayout();
const sb = getSidebar();
ids.forEach(id => {
- const card = document.getElementById(id);
- if (card && sb && !sb.contains(card)) {
- sb.appendChild(card);
- card.style.width = '100%';
+ const card = $(id);
+ if (!card) return;
+ if (layout[id] === ZONES.SIDEBAR && sb && !sb.contains(card)) {
+ placeCardInZone(card, ZONES.SIDEBAR, { animate: true });
}
});
- clearResponsiveSnapshot();
- // show sidebar again if panels aren’t collapsed
- if (sidebar) sidebar.style.display = isZonesCollapsed() ? 'none' : 'block';
- updateTopZoneLayout();
- updateSidebarVisibility();
- __lastIsSmall = false;
+ clearSidebarStash();
}
+ __wasSmall = nowSmall;
+ updateTopZoneLayout();
+ updateSidebarVisibility();
}
-
-function updateSidebarToggleUI() {
- const btn = document.getElementById('sidebarToggleFloating');
- const sidebar = getSidebar();
- if (!btn || !sidebar) return;
-
- if (!hasSidebarCards()) { btn.remove(); return; }
-
- const collapsed = isSidebarCollapsed();
- btn.innerHTML = `${collapsed ? TOGGLE_ICON_CLOSED : TOGGLE_ICON_OPEN
- }`;
- btn.title = collapsed ? 'Show sidebar' : 'Hide sidebar';
- btn.style.display = 'block';
- btn.classList.toggle('toggle-ping', collapsed);
-}
-
-
-function hasSidebarCards() {
- const sb = getSidebar();
- return !!sb && sb.querySelectorAll('#uploadCard, #folderManagementCard').length > 0;
-}
-
-function hasTopZoneCards() {
- const tz = getTopZone();
- return !!tz && tz.querySelectorAll('#uploadCard, #folderManagementCard').length > 0;
-}
-
-// Both cards are in the top zone (upload + folder)
-function allCardsInTopZone() {
- const tz = getTopZone();
- if (!tz) return false;
- const hasUpload = !!tz.querySelector('#uploadCard');
- const hasFolder = !!tz.querySelector('#folderManagementCard');
- return hasUpload && hasFolder;
-}
-
-function isZonesCollapsed() {
- return localStorage.getItem('zonesCollapsed') === '1';
-}
+// -------------------- zones toggle (collapse to header) --------------------
+function isZonesCollapsed() { return localStorage.getItem('zonesCollapsed') === '1'; }
function setZonesCollapsed(collapsed) {
localStorage.setItem('zonesCollapsed', collapsed ? '1' : '0');
-
if (collapsed) {
- // Remember where cards were, then show them as header icons
- snapshotZoneLocations(); // original snapshot used only for collapse/expand
- collapseCardsToHeader(); // your existing helper that calls insertCardInHeader(...)
+ // Move ALL cards to header icons (transient). Do not overwrite saved layout.
+ getCards().forEach(insertCardInHeader);
} else {
- // Expand: bring cards back (from the collapse snapshot)
- restoreCardsFromSnapshot();
-
- // Ensure zones are visible right away after expand
- const sidebar = getSidebar();
- const topZone = getTopZone();
- if (sidebar) sidebar.style.display = 'block';
- if (topZone) topZone.style.display = '';
+ // Restore the saved user layout.
+ applyUserLayoutOrDefault();
}
-
ensureZonesToggle();
updateZonesToggleUI();
- clampSidebarWhenEmpty();
-}
-
-function applyZonesCollapsed() {
- const collapsed = isZonesCollapsed();
- const sidebar = getSidebar();
- const topZone = getTopZone();
-
- if (sidebar) sidebar.style.display = collapsed ? 'none' : (hasSidebarCards() ? 'block' : 'none');
- if (topZone) topZone.style.display = collapsed ? 'none' : (hasTopZoneCards() ? '' : '');
-}
-
-function isMediumScreen() {
- const w = window.innerWidth;
- return w >= MEDIUM_MIN && w < MEDIUM_MAX;
-}
-
-// ----- Sidebar collapse state helpers -----
-function getSidebar() {
- return document.getElementById('sidebarDropArea');
-}
-function getTopZone() {
- return document.getElementById('uploadFolderRow');
-}
-
-function isSidebarCollapsed() {
- return localStorage.getItem('sidebarCollapsed') === '1';
-}
-
-function setSidebarCollapsed(collapsed) {
- localStorage.setItem('sidebarCollapsed', collapsed ? '1' : '0');
- applySidebarCollapsed();
- updateSidebarToggleUI();
-}
-
-function applySidebarCollapsed() {
- const sidebar = getSidebar();
- if (!sidebar) return;
-
- // We avoid hard-coding layout assumptions: simply hide/show the sidebar area.
- // If you want a sliding effect, add CSS later; JS will just toggle display.
- const collapsed = isSidebarCollapsed();
- sidebar.style.display = collapsed ? 'none' : 'block';
}
function getHeaderHost() {
- // 1) exact structure you shared
let host = document.querySelector('.header-container .header-left');
- // 2) fallback to header root
if (!host) host = document.querySelector('.header-container');
- // 3) last resort
if (!host) host = document.querySelector('header');
return host || document.body;
}
-function mountHeaderToggle(btn) {
- const host = document.querySelector('.header-left');
- const logoA = host?.querySelector('a');
- if (!host) return;
-
- // ensure positioning context
- if (getComputedStyle(host).position === 'static') host.style.position = 'relative';
-
- if (logoA) {
- logoA.insertAdjacentElement('afterend', btn); // sibling of , not inside it
- } else {
- host.appendChild(btn);
- }
-
- Object.assign(btn.style, {
- position: 'absolute',
- left: TOGGLE_LEFT_PX, // adjust position beside the logo
- top: TOGGLE_TOP_PX,
- zIndex: '10010',
- pointerEvents: 'auto'
- });
-}
-
-
function ensureZonesToggle() {
- let btn = document.getElementById('sidebarToggleFloating');
const host = getHeaderHost();
if (!host) return;
- // ensure the host is a positioning context
- const hostStyle = getComputedStyle(host);
- if (hostStyle.position === 'static') {
- host.style.position = 'relative';
- }
+ if (getComputedStyle(host).position === 'static') host.style.position = 'relative';
+ let btn = $('sidebarToggleFloating');
if (!btn) {
btn = document.createElement('button');
btn.id = 'sidebarToggleFloating';
btn.type = 'button';
btn.setAttribute('aria-label', 'Toggle panels');
-
- // Prevent accidental navigations / bubbling
- btn.addEventListener('click', (e) => {
- e.preventDefault();
- e.stopPropagation();
- setSidebarCollapsed(!isSidebarCollapsed());
- updateSidebarToggleUI();
- });
- ['mousedown','mouseup','pointerdown','pointerup'].forEach(evt =>
- btn.addEventListener(evt, (e) => e.stopPropagation())
- );
-
Object.assign(btn.style, {
position: 'absolute',
- top: '8px',
- left: '65px',
- zIndex: '1000',
+ top: `${TOGGLE_TOP_PX}px`,
+ left: `${TOGGLE_LEFT_PX}px`,
+ zIndex: '10010',
width: '38px',
height: '38px',
borderRadius: '19px',
@@ -537,63 +394,50 @@ function ensureZonesToggle() {
lineHeight: '0'
});
btn.classList.add('zones-toggle');
-
- // Dark mode polish
- if (document.body.classList.contains('dark-mode')) {
- btn.style.background = '#2c2c2c';
- btn.style.border = '1px solid #555';
- btn.style.boxShadow = '0 2px 6px rgba(0,0,0,.35)';
- btn.style.color = '#e0e0e0';
- }
-
- btn.addEventListener('click', () => {
+ btn.addEventListener('click', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
setZonesCollapsed(!isZonesCollapsed());
});
- // Insert right after the logo if present, else append to host
const afterLogo = host.querySelector('.header-logo');
if (afterLogo && afterLogo.parentNode) {
afterLogo.parentNode.insertBefore(btn, afterLogo.nextSibling);
} else {
host.appendChild(btn);
}
-
- themeToggleButton(btn);
}
-
+ themeToggleButton(btn);
updateZonesToggleUI();
}
function updateZonesToggleUI() {
- const btn = document.getElementById('sidebarToggleFloating');
+ const btn = $('sidebarToggleFloating');
if (!btn) return;
-
- // Never remove the button just because cards are in header.
const collapsed = isZonesCollapsed();
const iconName = collapsed ? TOGGLE_ICON_CLOSED : TOGGLE_ICON_OPEN;
btn.innerHTML = `${iconName}`;
btn.title = collapsed ? 'Show panels' : 'Hide panels';
- btn.style.display = 'block';
const iconEl = btn.querySelector('.toggle-icon');
if (iconEl) {
iconEl.style.transition = 'transform 0.2s ease';
iconEl.style.display = 'inline-flex';
iconEl.style.alignItems = 'center';
- if (!collapsed && allCardsInTopZone()) {
- iconEl.style.transform = 'rotate(90deg)';
- } else {
- iconEl.style.transform = 'rotate(0deg)';
- }
+ // fun rotate if both cards are in top zone
+ const tz = getTopZone();
+ const allTop = !!tz?.querySelector('#uploadCard') && !!tz?.querySelector('#folderManagementCard');
+ iconEl.style.transform = (!collapsed && allTop) ? 'rotate(90deg)' : 'rotate(0deg)';
}
themeToggleButton(btn);
}
+// Keep the button styled when theme flips
(function watchThemeChanges() {
const obs = new MutationObserver((muts) => {
for (const m of muts) {
if (m.type === 'attributes' && m.attributeName === 'class') {
- const btn = document.getElementById('sidebarToggleFloating');
+ const btn = $('sidebarToggleFloating');
if (btn) themeToggleButton(btn);
}
}
@@ -601,371 +445,68 @@ function updateZonesToggleUI() {
obs.observe(document.body, { attributes: true });
})();
-// create a small floating toggle button (no HTML edits needed)
-function ensureSidebarToggle() {
- const sidebar = getSidebar();
- if (!sidebar) return;
-
- // Only show if there are cards
- if (!hasSidebarCards()) {
- const existing = document.getElementById('sidebarToggleFloating');
- if (existing) existing.remove();
- return;
- }
-
- let btn = document.getElementById('sidebarToggleFloating');
- if (!btn) {
- btn = document.createElement('button');
- btn.id = 'sidebarToggleFloating';
- btn.type = 'button';
- btn.setAttribute('aria-label', 'Toggle sidebar');
-
- Object.assign(btn.style, {
- position: 'fixed',
- left: `${TOGGLE_LEFT_PX}px`,
- top: `${TOGGLE_TOP_PX}px`,
- zIndex: '10010',
- width: '38px',
- height: '38px',
- borderRadius: '19px',
- border: '1px solid #ccc',
- background: '#fff',
- cursor: 'pointer',
- boxShadow: '0 2px 6px rgba(0,0,0,.15)',
- display: 'block',
- });
-
- btn.addEventListener('click', () => {
- setSidebarCollapsed(!isSidebarCollapsed());
- // icon/title/animation update after state change
- updateSidebarToggleUI();
- });
-
- document.body.appendChild(btn);
- }
-
- // set correct icon/title right away
- //updateSidebarToggleUI();
- //applySidebarCollapsed();
- updateZonesToggleUI();
- applyZonesCollapsed();
+// -------------------- layout polish --------------------
+function hasSidebarCards() {
+ const sb = getSidebar();
+ return !!sb && sb.querySelectorAll('#uploadCard, #folderManagementCard').length > 0;
}
-
-// Moves cards into the sidebar based on the saved order in localStorage.
-export function loadSidebarOrder() {
- const sidebar = getSidebar();
- if (!sidebar) return;
-
- const defaultAppliedKey = 'layoutDefaultApplied_v1';
- const defaultAlready = localStorage.getItem(defaultAppliedKey) === '1';
-
- const orderStr = localStorage.getItem('sidebarOrder');
- const headerOrderStr = localStorage.getItem('headerOrder');
-
- // Restore user's last manual placement on normal load
- if (applySnapshotIfPresent()) {
- updateTopZoneLayout();
- updateSidebarVisibility();
- applyZonesCollapsed();
- ensureZonesToggle();
- return;
- }
- // If no user snapshot exists and we're not on small screens,
- // but the cards are currently in the top zone, default them to the sidebar once.
- if (!hasUserSnapshot() && !isSmallScreen() && hasTopZoneCards() && !hasSidebarCards()) {
- const sb = getSidebar();
- if (sb) {
- ['uploadCard','folderManagementCard'].forEach(id => {
- const c = document.getElementById(id);
- if (c && !sb.contains(c)) {
- sb.appendChild(c);
- c.style.width = '100%';
- }
- });
- snapshotUserZones(); // persist this as the user's baseline
- updateSidebarVisibility();
- updateTopZoneLayout();
- }
- }
-
- // Only apply the one-time default if *not* initialized yet
- if (!defaultAlready &&
- ((!orderStr || !JSON.parse(orderStr || '[]').length) &&
- (!headerOrderStr || !JSON.parse(headerOrderStr || '[]').length))) {
-
- const isLargeEnough = window.innerWidth >= MEDIUM_MIN;
- if (isLargeEnough) {
- const mainWrapper = document.querySelector('.main-wrapper');
- if (mainWrapper) mainWrapper.style.display = 'flex';
-
- const moved = [];
- ['uploadCard', 'folderManagementCard'].forEach(id => {
- const card = document.getElementById(id);
- if (card && card.parentNode?.id !== 'sidebarDropArea') {
- card.style.width = '';
- card.style.minWidth = '';
- getSidebar().appendChild(card);
- animateVerticalSlide(card);
- moved.push(id);
- }
- });
-
- if (moved.length) {
- localStorage.setItem('sidebarOrder', JSON.stringify(moved));
- }
- }
-
- // Mark initialized so this default never fires again
- localStorage.setItem(defaultAppliedKey, '1');
- }
-
- // If user has header icons saved, honor that and bail
- const headerOrder = JSON.parse(headerOrderStr || '[]');
- if (Array.isArray(headerOrder) && headerOrder.length > 0) {
- updateSidebarVisibility();
- applyZonesCollapsed();
- ensureZonesToggle();
- return;
- }
-
- if (!defaultAlready && isMediumScreen()) {
- const mainWrapper = document.querySelector('.main-wrapper');
- if (mainWrapper) mainWrapper.style.display = 'flex';
-
- const candidates = ['uploadCard', 'folderManagementCard'];
- const moved = [];
- candidates.forEach(id => {
- const card = document.getElementById(id);
- if (card && card.parentNode?.id !== 'sidebarDropArea') {
- sidebar.appendChild(card);
- animateVerticalSlide(card);
- moved.push(id);
- }
- });
-
- if (moved.length) {
- localStorage.setItem('sidebarOrder', JSON.stringify(moved));
- localStorage.setItem(defaultAppliedKey, '1'); // mark initialized
- }
- }
-
- updateSidebarVisibility();
- applyZonesCollapsed();
- ensureZonesToggle();
+function hasTopZoneCards() {
+ const tz = getTopZone();
+ return !!tz && tz.querySelectorAll('#uploadCard, #folderManagementCard').length > 0;
}
-
-export function loadHeaderOrder() {
- const headerDropArea = document.getElementById('headerDropArea');
- if (!headerDropArea) return;
-
- // If panels are expanded, do not re-create header icons.
- if (!isZonesCollapsed()) {
- headerDropArea.innerHTML = '';
- localStorage.removeItem('headerOrder');
- return;
- }
-
- headerDropArea.innerHTML = '';
- let stored;
- try { stored = JSON.parse(localStorage.getItem('headerOrder') || '[]'); } catch { stored = []; }
- const uniqueIds = Array.from(new Set(stored));
- uniqueIds.forEach(id => {
- const card = document.getElementById(id);
- if (card) insertCardInHeader(card, null);
- });
- localStorage.setItem('headerOrder', JSON.stringify(uniqueIds));
-}
-
-// Internal helper: update sidebar visibility based on its content.
-// NOTE: do NOT auto-hide if user manually collapsed; that is separate.
function updateSidebarVisibility() {
- const sidebar = getSidebar();
- if (!sidebar) return;
-
- const anyCards = hasSidebarCards();
-
- // clear any leftover drag height
- sidebar.style.height = '';
-
- if (anyCards) {
- sidebar.classList.add('active');
- // respect the unified zones-collapsed switch
- sidebar.style.display = isZonesCollapsed() ? 'none' : 'block';
- } else {
- sidebar.classList.remove('active');
- sidebar.style.display = 'none';
- }
-
- // Save order and update toggle visibility
- saveSidebarOrder();
- // Mark layout initialized so the first-run default won't fire on reload
- localStorage.setItem('layoutDefaultApplied_v1', '1');
-
- ensureZonesToggle();
- clampSidebarWhenEmpty();
+ const sb = getSidebar();
+ if (!sb) return;
+ const any = hasSidebarCards();
+ sb.style.display = (isZonesCollapsed() || !any) ? 'none' : 'block';
}
-
-// NEW: Save header order to localStorage.
-function saveHeaderOrder() {
- const headerDropArea = document.getElementById('headerDropArea');
- if (headerDropArea) {
- const icons = Array.from(headerDropArea.children);
- // Each header icon stores its associated card in the property cardElement.
- const order = icons.map(icon => icon.cardElement.id);
- localStorage.setItem('headerOrder', JSON.stringify(order));
- }
-}
-
-// Internal helper: update top zone layout (center a card if one column is empty).
function updateTopZoneLayout() {
- const topZone = getTopZone();
- const leftCol = document.getElementById('leftCol');
- const rightCol = document.getElementById('rightCol');
+ const top = getTopZone();
+ const left = getLeftCol();
+ const right = getRightCol();
+ const hasUpload = !!top?.querySelector('#uploadCard');
+ const hasFolder = !!top?.querySelector('#folderManagementCard');
- const hasUpload = !!topZone?.querySelector('#uploadCard');
- const hasFolder = !!topZone?.querySelector('#folderManagementCard');
-
- if (leftCol && rightCol) {
+ if (left && right) {
if (hasUpload && !hasFolder) {
- rightCol.style.display = 'none';
- leftCol.style.margin = '0 auto';
- leftCol.style.display = '';
+ right.style.display = 'none';
+ left.style.display = '';
+ left.style.margin = '0 auto';
} else if (!hasUpload && hasFolder) {
- leftCol.style.display = 'none';
- rightCol.style.margin = '0 auto';
- rightCol.style.display = '';
+ left.style.display = 'none';
+ right.style.display = '';
+ right.style.margin = '0 auto';
} else {
- leftCol.style.display = '';
- rightCol.style.display = '';
- leftCol.style.margin = '';
- rightCol.style.margin = '';
+ left.style.display = '';
+ right.style.display = '';
+ left.style.margin = '';
+ right.style.margin = '';
}
}
-
- // hide whole top row when empty (kills the gap)
- if (topZone) topZone.style.display = (hasUpload || hasFolder) ? '' : 'none';
- clampSidebarWhenEmpty();
+ if (top) top.style.display = (hasUpload || hasFolder) ? '' : 'none';
}
-// When a card is being dragged, if the top drop zone is empty, set its min-height.
+// drag visual helpers
function addTopZoneHighlight() {
- const topZone = document.getElementById('uploadFolderRow');
- if (topZone) {
- topZone.classList.add('highlight');
- if (topZone.querySelectorAll('#uploadCard, #folderManagementCard').length === 0) {
- topZone.style.minHeight = '375px';
- }
+ const top = getTopZone();
+ if (!top) return;
+ top.classList.add('highlight');
+ if (top.querySelectorAll('#uploadCard, #folderManagementCard').length === 0) {
+ top.style.minHeight = '375px';
}
}
-
-// When the drag ends, remove the extra min-height.
function removeTopZoneHighlight() {
- const topZone = document.getElementById('uploadFolderRow');
- if (topZone) {
- topZone.classList.remove('highlight');
- topZone.style.minHeight = '';
- }
+ const top = getTopZone();
+ if (!top) return;
+ top.classList.remove('highlight');
+ top.style.minHeight = '';
}
-
-// Vertical slide/fade animation helper.
-function animateVerticalSlide(card) {
- card.style.transform = 'translateY(30px)';
- card.style.opacity = '0';
- // Force reflow.
- card.offsetWidth;
- requestAnimationFrame(() => {
- card.style.transition = 'transform 0.3s ease, opacity 0.3s ease';
- card.style.transform = 'translateY(0)';
- card.style.opacity = '1';
- });
- setTimeout(() => {
- card.style.transition = '';
- card.style.transform = '';
- card.style.opacity = '';
- }, 310);
-}
-
-// Internal helper: insert card into sidebar at a proper position based on event.clientY.
-function insertCardInSidebar(card, event) {
- const sidebar = getSidebar();
- if (!sidebar) return;
-
- const existingCards = Array.from(sidebar.querySelectorAll('#uploadCard, #folderManagementCard'));
- let inserted = false;
- for (const currentCard of existingCards) {
- const rect = currentCard.getBoundingClientRect();
- const midY = rect.top + rect.height / 2;
- if (event.clientY < midY) {
- sidebar.insertBefore(card, currentCard);
- inserted = true;
- break;
- }
- }
- if (!inserted) sidebar.appendChild(card);
-
- // Make it fill the sidebar and clear any sticky width from header/top zone.
- card.style.width = '100%';
- removeHeaderIconForCard(card); // NEW: remove any header artifacts
- card.dataset.originalContainerId = 'sidebarDropArea';
- animateVerticalSlide(card);
-
- // SAVE order & refresh minimal UI, but DO NOT collapse/restore here:
- saveSidebarOrder();
-
- // NEW: persist user manual placement (used on normal refresh)
- snapshotUserZones();
-
- updateSidebarVisibility();
- ensureZonesToggle();
- updateZonesToggleUI();
-}
-
-// Internal helper: save the current sidebar card order to localStorage.
-function saveSidebarOrder() {
- const sidebar = getSidebar();
- if (sidebar) {
- const cards = sidebar.querySelectorAll('#uploadCard, #folderManagementCard');
- const order = Array.from(cards).map(card => card.id);
- localStorage.setItem('sidebarOrder', JSON.stringify(order));
- }
-}
-
-// Helper: move cards from sidebar back to the top drop area when on small screens.
-function moveSidebarCardsToTop() {
- if (window.innerWidth < 1205) {
- const sidebar = getSidebar();
- if (!sidebar) return;
- const cards = Array.from(sidebar.querySelectorAll('#uploadCard, #folderManagementCard'));
- cards.forEach(card => {
- const orig = document.getElementById(card.dataset.originalContainerId);
- if (orig) {
- orig.appendChild(card);
- animateVerticalSlide(card);
- }
- });
- updateSidebarVisibility();
- updateTopZoneLayout();
- }
-}
-
-// Listen for window resize to automatically move sidebar cards back to top on small screens.
-(function () {
- let rAF = null;
- window.addEventListener('resize', () => {
- if (rAF) cancelAnimationFrame(rAF);
- rAF = requestAnimationFrame(() => {
- enforceResponsiveZones();
- });
- });
-})();
-
function showTopZoneWhileDragging() {
- const topZone = getTopZone();
- if (!topZone) return;
- topZone.style.display = ''; // make it droppable
- // add a temporary placeholder only if empty
- if (topZone.querySelectorAll('#uploadCard, #folderManagementCard').length === 0) {
- let ph = topZone.querySelector('.placeholder');
+ const top = getTopZone();
+ if (!top) return;
+ top.style.display = '';
+ if (top.querySelectorAll('#uploadCard, #folderManagementCard').length === 0) {
+ let ph = top.querySelector('.placeholder');
if (!ph) {
ph = document.createElement('div');
ph.className = 'placeholder';
@@ -973,426 +514,233 @@ function showTopZoneWhileDragging() {
ph.style.display = 'block';
ph.style.width = '100%';
ph.style.height = '375px';
- topZone.appendChild(ph);
+ top.appendChild(ph);
}
}
}
-
function cleanupTopZoneAfterDrop() {
- const topZone = getTopZone();
- if (!topZone) return;
-
- // remove placeholder and highlight/minHeight no matter what
- const ph = topZone.querySelector('.placeholder');
+ const top = getTopZone();
+ if (!top) return;
+ const ph = top.querySelector('.placeholder');
if (ph) ph.remove();
- topZone.classList.remove('highlight');
- topZone.style.minHeight = '';
-
- // if no cards left, hide the whole row to remove the gap
- const hasAny = topZone.querySelectorAll('#uploadCard, #folderManagementCard').length > 0;
- topZone.style.display = hasAny ? '' : 'none';
+ top.classList.remove('highlight');
+ top.style.minHeight = '';
+ const hasAny = top.querySelectorAll('#uploadCard, #folderManagementCard').length > 0;
+ top.style.display = hasAny ? '' : 'none';
}
-
-// This function ensures the top drop zone (#uploadFolderRow) has a stable width when empty.
-function ensureTopZonePlaceholder() {
- const topZone = document.getElementById('uploadFolderRow');
- if (!topZone) return;
- topZone.style.display = '';
- if (topZone.querySelectorAll('#uploadCard, #folderManagementCard').length === 0) {
- let placeholder = topZone.querySelector('.placeholder');
- if (!placeholder) {
- placeholder = document.createElement('div');
- placeholder.className = 'placeholder';
- placeholder.style.visibility = 'hidden';
- placeholder.style.display = 'block';
- placeholder.style.width = '100%';
- placeholder.style.height = '375px';
- topZone.appendChild(placeholder);
- }
- } else {
- const placeholder = topZone.querySelector('.placeholder');
- if (placeholder) placeholder.remove();
- }
-}
-
-// --- Header drop zone helpers ---
-
function showHeaderDropZone() {
- const headerDropArea = document.getElementById('headerDropArea');
- if (headerDropArea) {
- headerDropArea.style.display = 'inline-flex';
- headerDropArea.classList.add('drag-active');
+ const h = getHeaderDropArea();
+ if (h) {
+ h.style.display = 'inline-flex';
+ h.classList.add('drag-active');
}
}
-
function hideHeaderDropZone() {
- const headerDropArea = document.getElementById('headerDropArea');
- if (headerDropArea) {
- headerDropArea.classList.remove('drag-active');
- if (headerDropArea.children.length === 0) {
- headerDropArea.style.display = 'none';
- }
+ const h = getHeaderDropArea();
+ if (h) {
+ h.classList.remove('drag-active');
+ if (h.children.length === 0) h.style.display = 'none';
}
}
-// Insert card into header drop zone as a material icon
-function insertCardInHeader(card, event) {
- const headerDropArea = document.getElementById('headerDropArea');
- if (!headerDropArea) return;
+// -------------------- DnD core --------------------
+function makeCardDraggable(card) {
+ if (!card) return;
+ const header = card.querySelector('.card-header');
+ if (header) header.classList.add('drag-header');
- // Preserve the original by moving it to a hidden container.
- if (card.id === 'folderManagementCard' || card.id === 'uploadCard') {
- let hiddenContainer = document.getElementById('hiddenCardsContainer');
- if (!hiddenContainer) {
- hiddenContainer = document.createElement('div');
- hiddenContainer.id = 'hiddenCardsContainer';
- hiddenContainer.style.display = 'none';
- document.body.appendChild(hiddenContainer);
+ let isDragging = false;
+ let dragTimer = null;
+ let offsetX = 0, offsetY = 0;
+ let initialLeft, initialTop;
+
+ if (header) {
+ header.addEventListener('mousedown', function (e) {
+ e.preventDefault();
+ const c = this.closest('.card');
+ const rect = c.getBoundingClientRect();
+ const originX = ((e.clientX - rect.left) / rect.width) * 100;
+ const originY = ((e.clientY - rect.top) / rect.height) * 100;
+ c.style.transformOrigin = `${originX}% ${originY}%`;
+
+ dragTimer = setTimeout(() => {
+ isDragging = true;
+ c.classList.add('dragging');
+ c.style.pointerEvents = 'none';
+
+ addTopZoneHighlight();
+ showTopZoneWhileDragging();
+
+ const sb = getSidebar();
+ if (sb) {
+ sb.classList.add('active');
+ sb.classList.add('highlight');
+ if (!isZonesCollapsed()) sb.style.display = 'block';
+ sb.style.removeProperty('height');
+ sb.style.minWidth = '280px';
+ }
+
+ showHeaderDropZone();
+
+ initialLeft = rect.left + window.pageXOffset;
+ initialTop = rect.top + window.pageYOffset;
+ offsetX = e.pageX - initialLeft;
+ offsetY = e.pageY - initialTop;
+
+ // If represented in header, remove its icon so we can move freely
+ removeHeaderIconForCard(c);
+
+ document.body.appendChild(c);
+ Object.assign(c.style, {
+ position: 'absolute',
+ left: initialLeft + 'px',
+ top: initialTop + 'px',
+ width: rect.width + 'px',
+ height: rect.height + 'px',
+ minWidth: rect.width + 'px',
+ flexShrink: '0',
+ zIndex: '10000'
+ });
+ }, 450);
+ });
+
+ header.addEventListener('mouseup', function () { clearTimeout(dragTimer); });
+ }
+
+ document.addEventListener('mousemove', function (e) {
+ if (isDragging) {
+ card.style.left = (e.pageX - offsetX) + 'px';
+ card.style.top = (e.pageY - offsetY) + 'px';
}
- if (card.parentNode?.id !== 'hiddenCardsContainer') {
- hiddenContainer.appendChild(card);
- }
- } else if (card.parentNode) {
- card.parentNode.removeChild(card);
- }
-
- const iconButton = document.createElement('button');
- iconButton.className = 'header-card-icon';
- iconButton.style.border = 'none';
- iconButton.style.background = 'none';
- iconButton.style.outline = 'none';
- iconButton.style.cursor = 'pointer';
-
- if (card.id === 'uploadCard') {
- iconButton.innerHTML = 'cloud_upload';
- } else if (card.id === 'folderManagementCard') {
- iconButton.innerHTML = 'folder';
- } else {
- iconButton.innerHTML = 'insert_drive_file';
- }
-
- iconButton.cardElement = card;
- card.headerIconButton = iconButton;
-
- let modal = null;
- let isLocked = false;
- let hoverActive = false;
-
- function showModal() {
- if (!modal) {
- modal = document.createElement('div');
- modal.className = 'header-card-modal';
- Object.assign(modal.style, {
- position: 'fixed',
- top: '55px',
- right: '80px',
- zIndex: '11000',
- display: 'block',
- visibility: 'hidden',
- opacity: '0',
- background: 'none',
- border: 'none',
- padding: '0',
- boxShadow: 'none',
- maxWidth: '440px', // NEW: keep card from overflowing center content
- width: 'max-content' // NEW
- });
- document.body.appendChild(modal);
- modal.addEventListener('mouseover', handleMouseOver);
- modal.addEventListener('mouseout', handleMouseOut);
- iconButton.modalInstance = modal;
- }
- if (!modal.contains(card)) {
- const hiddenContainer = document.getElementById('hiddenCardsContainer');
- if (hiddenContainer && hiddenContainer.contains(card)) hiddenContainer.removeChild(card);
- // Clear sticky widths before placing in modal
- card.style.width = '';
- card.style.minWidth = '';
- modal.appendChild(card);
- }
- modal.style.visibility = 'visible';
- modal.style.opacity = '1';
- }
-
- function hideModal() {
- if (modal && !isLocked && !hoverActive) {
- modal.style.visibility = 'hidden';
- modal.style.opacity = '0';
- const hiddenContainer = document.getElementById('hiddenCardsContainer');
- if (hiddenContainer && modal.contains(card)) {
- hiddenContainer.appendChild(card);
- }
- }
- }
-
- function handleMouseOver() {
- hoverActive = true;
- showModal();
- }
-
- function handleMouseOut() {
- hoverActive = false;
- setTimeout(() => {
- if (!hoverActive && !isLocked) {
- hideModal();
- }
- }, 300);
- }
-
- iconButton.addEventListener('mouseover', handleMouseOver);
- iconButton.addEventListener('mouseout', handleMouseOut);
-
- iconButton.addEventListener('click', (e) => {
- isLocked = !isLocked;
- if (isLocked) showModal();
- else hideModal();
- e.stopPropagation();
});
- headerDropArea.appendChild(iconButton);
+ document.addEventListener('mouseup', function (e) {
+ if (!isDragging) return;
+ isDragging = false;
+ card.style.pointerEvents = '';
+ card.classList.remove('dragging');
+
+ const sb = getSidebar();
+ if (sb) {
+ sb.classList.remove('highlight');
+ sb.style.height = '';
+ sb.style.minWidth = '';
+ }
+
+ let dropped = null;
+
+ // Sidebar drop?
+ if (sb) {
+ const r = sb.getBoundingClientRect();
+ if (e.clientX >= r.left && e.clientX <= r.right && e.clientY >= r.top && e.clientY <= r.bottom) {
+ placeCardInZone(card, ZONES.SIDEBAR);
+ dropped = ZONES.SIDEBAR;
+ }
+ }
+
+ // Top zone drop?
+ if (!dropped) {
+ const top = getTopZone();
+ if (top) {
+ const r = top.getBoundingClientRect();
+ if (e.clientX >= r.left && e.clientX <= r.right && e.clientY >= r.top && e.clientY <= r.bottom) {
+ const dest = (card.id === 'uploadCard') ? ZONES.TOP_LEFT : ZONES.TOP_RIGHT;
+ placeCardInZone(card, dest);
+ dropped = dest;
+ }
+ }
+ }
+
+ // Header drop?
+ if (!dropped) {
+ const h = getHeaderDropArea();
+ if (h) {
+ const r = h.getBoundingClientRect();
+ if (e.clientX >= r.left && e.clientX <= r.right && e.clientY >= r.top && e.clientY <= r.bottom) {
+ placeCardInZone(card, ZONES.HEADER);
+ dropped = ZONES.HEADER;
+ }
+ }
+ }
+
+ // If not dropped anywhere, return to original container
+ if (!dropped) {
+ const orig = $(card.dataset.originalContainerId);
+ if (orig) {
+ orig.appendChild(card);
+ card.style.removeProperty('width');
+ animateVerticalSlide(card);
+ // keep previous zone in layout (no change)
+ }
+ } else {
+ // Persist user layout on manual move (including header)
+ setLayoutFor(card.id, dropped);
+ }
+
+ // Clear inline drag styles
+ ['position', 'left', 'top', 'z-index', 'height', 'min-width', 'flex-shrink', 'transition', 'transform', 'opacity', 'width']
+ .forEach(prop => card.style.removeProperty(prop));
+
+ removeTopZoneHighlight();
+ hideHeaderDropZone();
+ cleanupTopZoneAfterDrop();
+ updateTopZoneLayout();
+ updateSidebarVisibility();
+ });
+}
+
+// -------------------- public API --------------------
+export function loadSidebarOrder() {
+ // Backward compat: act as "apply layout"
+ applyUserLayoutOrDefault();
+ ensureZonesToggle();
+ updateZonesToggleUI();
+}
+
+export function loadHeaderOrder() {
+ const header = getHeaderDropArea();
+ if (!header) return;
+ header.innerHTML = '';
+
+ const layout = readLayout();
+
+ // If collapsed: all cards appear as header icons
+ if (isZonesCollapsed()) {
+ getCards().forEach(insertCardInHeader);
+ saveHeaderOrder();
+ return;
+ }
+
+ // Not collapsed: only cards saved to header zone appear as icons
+ getCards().forEach(card => {
+ if (layout[card.id] === ZONES.HEADER) insertCardInHeader(card);
+ });
saveHeaderOrder();
}
-// === Main Drag and Drop Initialization ===
export function initDragAndDrop() {
function run() {
- // make sure toggle exists even if user hasn't dragged yet
- loadSidebarOrder();
+ // 1) Layout on first paint
+ applyUserLayoutOrDefault();
loadHeaderOrder();
- // 2) Then paint visibility/toggle
- applyZonesCollapsed();
+ // 2) Paint controls/UI
ensureZonesToggle();
updateZonesToggleUI();
- const draggableCards = document.querySelectorAll('#uploadCard, #folderManagementCard');
- draggableCards.forEach(card => {
- if (!card.dataset.originalContainerId && card.parentNode) {
- card.dataset.originalContainerId = card.parentNode.id;
- }
- const header = card.querySelector('.card-header');
- if (header) {
- header.classList.add('drag-header');
- }
+ // 3) Make cards draggable
+ getCards().forEach(makeCardDraggable);
- let isDragging = false;
- let dragTimer = null;
- let offsetX = 0, offsetY = 0;
- let initialLeft, initialTop;
-
- if (header) {
- header.addEventListener('mousedown', function (e) {
- e.preventDefault();
- const card = this.closest('.card');
- const initialRect = card.getBoundingClientRect();
- const originX = ((e.clientX - initialRect.left) / initialRect.width) * 100;
- const originY = ((e.clientY - initialRect.top) / initialRect.height) * 100;
- card.style.transformOrigin = `${originX}% ${originY}%`;
-
- dragTimer = setTimeout(() => {
- isDragging = true;
- card.classList.add('dragging');
- card.style.pointerEvents = 'none';
- addTopZoneHighlight();
- showTopZoneWhileDragging();
-
- const sidebar = getSidebar();
-if (sidebar) {
- unclampSidebarForDrag(); // <— NEW
- sidebar.classList.add('active');
- sidebar.classList.add('highlight');
- // keep it visible, but don't force a fixed height
- sidebar.style.removeProperty('height'); // <— no 800px box
- // ensure it's actually visible if not collapsed
- if (!isZonesCollapsed()) sidebar.style.display = 'block';
-}
-
- showHeaderDropZone();
- const topZone = getTopZone();
- if (topZone) {
- topZone.style.display = '';
- ensureTopZonePlaceholder();
- }
-
- initialLeft = initialRect.left + window.pageXOffset;
- initialTop = initialRect.top + window.pageYOffset;
- offsetX = e.pageX - initialLeft;
- offsetY = e.pageY - initialTop;
-
- if (card.headerIconButton) {
- if (card.headerIconButton.parentNode) {
- card.headerIconButton.parentNode.removeChild(card.headerIconButton);
- }
- if (card.headerIconButton.modalInstance && card.headerIconButton.modalInstance.parentNode) {
- card.headerIconButton.modalInstance.parentNode.removeChild(card.headerIconButton.modalInstance);
- }
- card.headerIconButton = null;
- saveHeaderOrder();
- }
-
- document.body.appendChild(card);
- card.style.position = 'absolute';
- card.style.left = initialLeft + 'px';
- card.style.top = initialTop + 'px';
- card.style.width = initialRect.width + 'px';
- card.style.height = initialRect.height + 'px';
- card.style.minWidth = initialRect.width + 'px';
- card.style.flexShrink = '0';
- card.style.zIndex = '10000';
- }, 500);
- });
-
- header.addEventListener('mouseup', function () {
- clearTimeout(dragTimer);
- });
- }
-
- document.addEventListener('mousemove', function (e) {
- if (isDragging) {
- card.style.left = (e.pageX - offsetX) + 'px';
- card.style.top = (e.pageY - offsetY) + 'px';
- }
+ // 4) Enforce responsive (and keep doing so)
+ let raf = null;
+ const onResize = () => {
+ if (raf) cancelAnimationFrame(raf);
+ raf = requestAnimationFrame(() => {
+ enforceResponsiveZones();
});
-
- document.addEventListener('mouseup', function (e) {
- if (isDragging) {
- isDragging = false;
- card.style.pointerEvents = '';
- card.classList.remove('dragging');
-
-
- const sidebar = getSidebar();
- if (sidebar) {
- sidebar.classList.remove('highlight');
- sidebar.style.height = '';
- sidebar.style.minWidth = '';
- }
-
- if (card.headerIconButton) {
- if (card.headerIconButton.parentNode) {
- card.headerIconButton.parentNode.removeChild(card.headerIconButton);
- }
- if (card.headerIconButton.modalInstance && card.headerIconButton.modalInstance.parentNode) {
- card.headerIconButton.modalInstance.parentNode.removeChild(card.headerIconButton.modalInstance);
- }
- card.headerIconButton = null;
- saveHeaderOrder();
- }
-
- let droppedInSidebar = false;
- let droppedInTop = false;
- let droppedInHeader = false;
-
- // Check if dropped in sidebar drop zone.
- const sidebarElem = getSidebar();
- if (sidebarElem) {
- const rect = sidebarElem.getBoundingClientRect();
- if (
- e.clientX >= rect.left &&
- e.clientX <= rect.right &&
- e.clientY >= rect.top &&
- e.clientY <= rect.bottom
- ) {
- insertCardInSidebar(card, e);
- droppedInSidebar = true;
- }
- }
-
- // Check the top drop zone.
- const topRow = document.getElementById('uploadFolderRow');
- if (!droppedInSidebar && topRow) {
- const rect = topRow.getBoundingClientRect();
- if (
- e.clientX >= rect.left &&
- e.clientX <= rect.right &&
- e.clientY >= rect.top &&
- e.clientY <= rect.bottom
- ) {
- let container;
- if (card.id === 'uploadCard') {
- container = document.getElementById('leftCol');
- } else if (card.id === 'folderManagementCard') {
- container = document.getElementById('rightCol');
- }
- if (container) {
- ensureTopZonePlaceholder();
- updateTopZoneLayout();
- container.appendChild(card);
- card.dataset.originalContainerId = container.id;
- droppedInTop = true;
- card.style.width = "363px";
- animateVerticalSlide(card);
- setTimeout(() => {
- card.style.removeProperty('width');
- }, 210);
-
- // NEW: persist user manual placement
- snapshotUserZones();
- }
- }
- }
-
- // Check the header drop zone.
- const headerDropArea = document.getElementById('headerDropArea');
- if (!droppedInSidebar && !droppedInTop && headerDropArea) {
- const rect = headerDropArea.getBoundingClientRect();
- if (
- e.clientX >= rect.left &&
- e.clientX <= rect.right &&
- e.clientY >= rect.top &&
- e.clientY <= rect.bottom
- ) {
- insertCardInHeader(card, e);
- droppedInHeader = true;
- // header mode is transient; do not overwrite userZones here
- }
- }
-
- // If card was not dropped in any zone, return it to its original container.
- if (!droppedInSidebar && !droppedInTop && !droppedInHeader) {
- const orig = document.getElementById(card.dataset.originalContainerId);
- if (orig) {
- orig.appendChild(card);
- card.style.removeProperty('width');
- }
- }
-
- // Clear inline drag-related styles.
- [
- 'position',
- 'left',
- 'top',
- 'z-index',
- 'height',
- 'min-width',
- 'flex-shrink',
- 'transition',
- 'transform',
- 'opacity'
- ].forEach(prop => card.style.removeProperty(prop));
-
- // For sidebar drops, force width to 100%.
- if (droppedInSidebar) {
- card.style.width = '100%';
- }
-
- updateTopZoneLayout();
- updateSidebarVisibility();
- clampSidebarWhenEmpty();
- if (sidebar) {
- sidebar.classList.remove('highlight');
- sidebar.style.height = '';
- sidebar.style.minWidth = '';
- }
- hideHeaderDropZone();
-
- cleanupTopZoneAfterDrop();
- snapshotZoneLocations(); // keep original (collapse/expand)
- const tz = getTopZone();
- if (tz) tz.style.minHeight = '';
- }
- });
- });
+ };
+ window.addEventListener('resize', onResize);
+ enforceResponsiveZones();
}
if (document.readyState === 'loading') {
diff --git a/public/js/main.js b/public/js/main.js
index 70e4989..d17c720 100644
--- a/public/js/main.js
+++ b/public/js/main.js
@@ -403,39 +403,57 @@ function bindDarkMode() {
function applySiteConfig(cfg, { phase = 'final' } = {}) {
try {
const title = (cfg && cfg.header_title) ? String(cfg.header_title) : 'FileRise';
-
+
// Always keep correct early (no visual flicker)
document.title = title;
-
+
// --- Login options (apply in BOTH phases so login page is correct) ---
const lo = (cfg && cfg.loginOptions) ? cfg.loginOptions : {};
- const disableForm = !!lo.disableFormLogin;
- const disableOIDC = !!lo.disableOIDCLogin;
- const disableBasic = !!lo.disableBasicAuth;
-
- const row = $('#loginForm');
- if (row) {
- if (disableForm) {
- row.setAttribute('hidden', '');
- row.style.display = ''; // don't leave display:none lying around
+
+
+ // be tolerant to key variants just in case
+ const disableForm = !!(lo.disableFormLogin ?? lo.disable_form_login ?? lo.disableForm);
+ const disableOIDC = !!(lo.disableOIDCLogin ?? lo.disable_oidc_login ?? lo.disableOIDC);
+ const disableBasic = !!(lo.disableBasicAuth ?? lo.disable_basic_auth ?? lo.disableBasic);
+
+ const showForm = !disableForm;
+ const showOIDC = !disableOIDC;
+ const showBasic = !disableBasic;
+
+ const loginWrap = $('#loginForm'); // outer wrapper that contains buttons + form
+ const authForm = $('#authForm'); // inner username/password form
+ const oidcBtn = $('#oidcLoginBtn'); // OIDC button
+ const basicLink = document.querySelector('a[href="/api/auth/login_basic.php"]');
+
+ // 1) Show the wrapper if ANY method is enabled (form OR OIDC OR basic)
+ if (loginWrap) {
+ const anyMethod = showForm || showOIDC || showBasic;
+ if (anyMethod) {
+ loginWrap.removeAttribute('hidden'); // remove [hidden], which beats display:
+ loginWrap.style.display = ''; // let CSS decide
} else {
- row.removeAttribute('hidden');
- row.style.display = '';
+ loginWrap.setAttribute('hidden', '');
+ loginWrap.style.display = '';
}
}
- const oidc = $('#oidcLoginBtn'); if (oidc) oidc.style.display = disableOIDC ? 'none' : '';
+
+ // 2) Toggle the pieces inside the wrapper
+ if (authForm) authForm.style.display = showForm ? '' : 'none';
+ if (oidcBtn) oidcBtn.style.display = showOIDC ? '' : 'none';
+ if (basicLink) basicLink.style.display = showBasic ? '' : 'none';
+ const oidc = $('#oidcLoginBtn'); if (oidc) oidc.style.display = disableOIDC ? 'none' : '';
const basic = document.querySelector('a[href="/api/auth/login_basic.php"]');
if (basic) basic.style.display = disableBasic ? 'none' : '';
-
+
// --- Header only in the FINAL phase (prevents visible flips) ---
if (phase === 'final') {
const h1 = document.querySelector('.header-title h1');
if (h1) {
// prevent i18n or legacy from overwriting it
if (h1.hasAttribute('data-i18n-key')) h1.removeAttribute('data-i18n-key');
-
+
if (h1.textContent !== title) h1.textContent = title;
-
+
// lock it so late code can't stomp it
if (!h1.__titleLock) {
const mo = new MutationObserver(() => {
@@ -1037,6 +1055,21 @@ function bindDarkMode() {
if (login) login.style.display = '';
// …wire stuff…
applySiteConfig(window.__FR_SITE_CFG__ || {}, { phase: 'final' });
+ // Auto-SSO if OIDC is the only enabled method (add ?noauto=1 to skip)
+ (() => {
+ const lo = (window.__FR_SITE_CFG__ && window.__FR_SITE_CFG__.loginOptions) || {};
+ const disableForm = !!(lo.disableFormLogin ?? lo.disable_form_login ?? lo.disableForm);
+ const disableBasic = !!(lo.disableBasicAuth ?? lo.disable_basic_auth ?? lo.disableBasic);
+ const disableOIDC = !!(lo.disableOIDCLogin ?? lo.disable_oidc_login ?? lo.disableOIDC);
+
+ const onlyOIDC = disableForm && disableBasic && !disableOIDC;
+ const qp = new URLSearchParams(location.search);
+
+ if (onlyOIDC && qp.get('noauto') !== '1') {
+ const btn = document.getElementById('oidcLoginBtn');
+ if (btn) setTimeout(() => btn.click(), 250);
+ }
+ })();
await revealAppAndHideOverlay();
const hb = document.querySelector('.header-buttons');
if (hb) hb.style.visibility = 'hidden';
@@ -1102,7 +1135,7 @@ function bindDarkMode() {
const onHttps = location.protocol === 'https:' || location.hostname === 'localhost';
if ('serviceWorker' in navigator && onHttps && !hasCapBridge && !isCapUA) {
window.addEventListener('load', () => {
- navigator.serviceWorker.register(`/js/pwa/sw.js?v=${encodeURIComponent(QVER)}`).catch(() => {});
+ navigator.serviceWorker.register(`/js/pwa/sw.js?v=${encodeURIComponent(QVER)}`).catch(() => { });
});
}
})();
\ No newline at end of file