'use strict'; const SAVED_TAB_SESSION_COLLAPSED_KEY = 'savedTabSessionCollapsedState'; const SAVED_TAB_SESSION_ORDER_KEY = 'savedTabSessionOrder'; const sessionDataApi = typeof require === 'function' ? require('./tab-sessions.js') : (globalThis.TabHarborTabSessions || {}); const { buildSessionGroupsFromTabs: sessionManagerBuildSessionGroupsFromTabs, createSavedTabSessionFromTab: sessionManagerCreateSavedTabSessionFromTab, } = sessionDataApi; function normalizeSavedTabSessionOrder(input) { if (!Array.isArray(input)) return []; return [...new Set(input.map(id => String(id || '')).filter(Boolean))]; } function normalizeSavedTabSessionCollapsedState(input, sessions = []) { const sessionIds = new Set( (Array.isArray(sessions) ? sessions : []) .map(session => String(session?.id || '').trim()) .filter(Boolean) ); if (!input || typeof input !== 'object' || Array.isArray(input)) return {}; return Object.fromEntries( Object.entries(input) .map(([sessionId, collapsed]) => [String(sessionId || '').trim(), collapsed === true]) .filter(([sessionId, collapsed]) => sessionId && collapsed && (!sessionIds.size || sessionIds.has(sessionId))) ); } function applySavedTabSessionOrder(sessions = [], orderIds = []) { const list = Array.isArray(sessions) ? sessions.slice() : []; const normalizedOrder = normalizeSavedTabSessionOrder(orderIds); if (!list.length || !normalizedOrder.length) return list; const sessionMap = new Map(list.map(session => [String(session?.id || ''), session])); const ordered = normalizedOrder.map(id => sessionMap.get(id)).filter(Boolean); const seen = new Set(ordered.map(session => String(session?.id || ''))); const remainder = list.filter(session => !seen.has(String(session?.id || ''))); return [...ordered, ...remainder]; } function buildSavedTabSessionOrder(sessions = []) { return [...new Set((Array.isArray(sessions) ? sessions : []).map(session => String(session?.id || '')).filter(Boolean))]; } function buildSavedTabSessionOrderWithInsertedSession(sessions = [], insertedSessionId = '', { placement = 'after', insertBeforeSessionId = '', } = {}) { const normalizedInsertedId = String(insertedSessionId || '').trim(); if (!normalizedInsertedId) return buildSavedTabSessionOrder(sessions); const currentOrder = buildSavedTabSessionOrder(sessions) .filter(id => id !== normalizedInsertedId); const normalizedBeforeId = String(insertBeforeSessionId || '').trim(); if (normalizedBeforeId) { const insertIndex = currentOrder.indexOf(normalizedBeforeId); if (insertIndex >= 0) { currentOrder.splice(insertIndex, 0, normalizedInsertedId); return currentOrder; } } if (placement === 'before') { currentOrder.unshift(normalizedInsertedId); return currentOrder; } currentOrder.push(normalizedInsertedId); return currentOrder; } function buildSavedSessionTabToken(sessionId = '', tabIndex = 0) { const cleanSessionId = String(sessionId || '').trim(); const cleanTabIndex = Number(tabIndex); if (!cleanSessionId || !Number.isInteger(cleanTabIndex)) return ''; return `${cleanSessionId}::${cleanTabIndex}`; } function parseSavedSessionTabToken(token = '') { const raw = String(token || ''); const separatorIndex = raw.lastIndexOf('::'); if (separatorIndex <= 0) return null; const sessionId = raw.slice(0, separatorIndex).trim(); const tabIndex = Number(raw.slice(separatorIndex + 2)); if (!sessionId || !Number.isInteger(tabIndex)) return null; return { sessionId, tabIndex }; } function reorderSavedSessionTabsByTokens(session = {}, orderTokens = []) { const tabs = Array.isArray(session?.tabs) ? session.tabs : []; const normalizedTokens = [...new Set((Array.isArray(orderTokens) ? orderTokens : []).map(String).filter(Boolean))]; return normalizedTokens .map(token => { const parsed = parseSavedSessionTabToken(token); if (!parsed || parsed.sessionId !== String(session?.id || '')) return null; return tabs[parsed.tabIndex] || null; }) .filter(Boolean); } function moveSavedSessionTabsByTokens({ sourceSession = {}, targetSession = {}, sourceOrderTokens = [], targetOrderTokens = [], draggedToken = '', } = {}) { const draggedMeta = parseSavedSessionTabToken(draggedToken); if (!draggedMeta) return { sourceTabs: [], targetTabs: [] }; const sourceTabs = Array.isArray(sourceSession?.tabs) ? sourceSession.tabs : []; const targetTabs = Array.isArray(targetSession?.tabs) ? targetSession.tabs : []; const draggedTab = sourceTabs[draggedMeta.tabIndex] || null; if (!draggedTab) return { sourceTabs: [], targetTabs: [] }; const nextSourceTabs = [...new Set((Array.isArray(sourceOrderTokens) ? sourceOrderTokens : []).map(String).filter(Boolean))] .map(token => { const parsed = parseSavedSessionTabToken(token); if (!parsed || parsed.sessionId !== String(sourceSession?.id || '')) return null; return sourceTabs[parsed.tabIndex] || null; }) .filter(Boolean); const nextTargetTabs = [...new Set((Array.isArray(targetOrderTokens) ? targetOrderTokens : []).map(String).filter(Boolean))] .map(token => { if (token === draggedToken) return draggedTab; const parsed = parseSavedSessionTabToken(token); if (!parsed || parsed.sessionId !== String(targetSession?.id || '')) return null; return targetTabs[parsed.tabIndex] || null; }) .filter(Boolean); return { sourceTabs: nextSourceTabs, targetTabs: nextTargetTabs, }; } function detachSavedSessionTabToNewSession({ sessions = [], draggedToken = '', placement = 'after', insertBeforeSessionId = '', now = new Date().toISOString(), } = {}) { const draggedMeta = parseSavedSessionTabToken(draggedToken); if (!draggedMeta || typeof sessionManagerCreateSavedTabSessionFromTab !== 'function') { return { sessions: applySavedTabSessionOrder(sessions, buildSavedTabSessionOrder(sessions)), orderIds: buildSavedTabSessionOrder(sessions), createdSession: null, sourceSessionRemoved: false, }; } const sourceSession = (Array.isArray(sessions) ? sessions : []) .find(session => String(session?.id || '') === draggedMeta.sessionId); const draggedTab = sourceSession?.tabs?.[draggedMeta.tabIndex] || null; if (!sourceSession || !draggedTab) { return { sessions: applySavedTabSessionOrder(sessions, buildSavedTabSessionOrder(sessions)), orderIds: buildSavedTabSessionOrder(sessions), createdSession: null, sourceSessionRemoved: false, }; } const createdSession = sessionManagerCreateSavedTabSessionFromTab({ sourceSession, tab: draggedTab, existingSessions: sessions, now, }); const remainingTabs = (sourceSession.tabs || []) .filter((_, index) => index !== draggedMeta.tabIndex); const sourceSessionRemoved = remainingTabs.length === 0; const nextSessions = (Array.isArray(sessions) ? sessions : []) .flatMap(session => { if (String(session?.id || '') !== draggedMeta.sessionId) return [session]; if (sourceSessionRemoved) return []; return [{ ...session, tabs: remainingTabs, groups: typeof sessionManagerBuildSessionGroupsFromTabs === 'function' ? sessionManagerBuildSessionGroupsFromTabs(remainingTabs) : (session.groups || []), }]; }); const nextOrder = buildSavedTabSessionOrderWithInsertedSession(nextSessions, createdSession.id, { placement, insertBeforeSessionId, }); return { sessions: applySavedTabSessionOrder([...nextSessions, createdSession], nextOrder), orderIds: nextOrder, createdSession, sourceSessionRemoved, }; } (function attachTabHarborSessionManager(globalScope) { const exportsApi = { SAVED_TAB_SESSION_COLLAPSED_KEY, SAVED_TAB_SESSION_ORDER_KEY, normalizeSavedTabSessionOrder, normalizeSavedTabSessionCollapsedState, applySavedTabSessionOrder, buildSavedTabSessionOrder, buildSavedTabSessionOrderWithInsertedSession, buildSavedSessionTabToken, detachSavedSessionTabToNewSession, parseSavedSessionTabToken, reorderSavedSessionTabsByTokens, moveSavedSessionTabsByTokens, }; if (!globalScope?.document || !globalScope?.chrome?.storage?.local) { if (typeof module !== 'undefined' && module.exports) { module.exports = exportsApi; } return; } const sessionStore = globalScope.TabHarborTabSessions || {}; const sessionIcons = globalScope.TabOutIconUtils || {}; const sessionI18n = globalScope.TabHarborI18n || {}; const { addSavedTabSession: managerAddSavedTabSession, buildSessionGroupsFromTabs: managerBuildSessionGroupsFromTabs, createSavedTabSessionFromTab: managerCreateSavedTabSessionFromTab, deleteSavedTabSession: managerDeleteSavedTabSession, getSavedTabSessions: managerGetSavedTabSessions, renameSavedTabSession: managerRenameSavedTabSession, saveSavedTabSessions: managerSaveSavedTabSessions, updateSavedTabSessionTabs: managerUpdateSavedTabSessionTabs, } = sessionStore; const { escapeHtml: managerEscapeHtml, escapeHtmlAttribute: managerEscapeHtmlAttribute, getFallbackLabel: managerGetFallbackLabel, getGroupIcon: managerGetGroupIcon, getIconSources: managerGetIconSources, } = sessionIcons; const managerT = sessionI18n.t; let sessionManagerPage = 'home'; let savedSessionOrderState = []; let savedSessionRenameState = null; let savedSessionSettingsState = null; let savedSessionCollapsedState = {}; let savedSessionTabSuppressClickUntil = 0; let draggedSavedSessionId = ''; let draggedSavedSessionButtonEl = null; let savedSessionDragStartPoint = null; let savedSessionDragPlaceholderEl = null; let savedSessionTabDragState = null; let draggedSavedSessionTabRowEl = null; let savedSessionNewSessionSlotEl = null; let savedSessionTabPlaceholderEl = null; const SAVED_SESSION_SETTINGS_ICON = ``; const SAVED_SESSION_RESTORE_ICON = ``; function getDashboardRuntime() { return globalScope.TabHarborDashboardRuntime || {}; } function getThemeControls() { return globalScope.TabOutThemeControls || {}; } function getSavedSessionRestoreModeValue() { const themeControls = getThemeControls(); return typeof themeControls.getSavedSessionRestoreMode === 'function' ? themeControls.getSavedSessionRestoreMode() : 'new-window'; } function getSavedSessionNavDisplayModeValue() { const themeControls = getThemeControls(); return typeof themeControls.getSavedSessionNavDisplayMode === 'function' ? themeControls.getSavedSessionNavDisplayMode() : 'name'; } async function persistSavedSessionRestoreMode(mode) { const themeControls = getThemeControls(); if (typeof themeControls.saveSavedSessionRestoreMode === 'function') { return themeControls.saveSavedSessionRestoreMode(mode); } return null; } async function persistSavedSessionNavDisplayMode(mode) { const themeControls = getThemeControls(); if (typeof themeControls.saveSavedSessionNavDisplayMode === 'function') { return themeControls.saveSavedSessionNavDisplayMode(mode); } return null; } function escapeText(value = '') { return managerEscapeHtml ? managerEscapeHtml(value) : String(value || '') .replace(/&/g, '&') .replace(//g, '>'); } function escapeAttr(value = '') { return managerEscapeHtmlAttribute ? managerEscapeHtmlAttribute(value) : String(value || '') .replace(/&/g, '&') .replace(/"/g, '"') .replace(//g, '>'); } function formatRelativeTime(dateValue = '') { if (typeof timeAgo === 'function') return timeAgo(dateValue); return String(dateValue || ''); } function getSessionTabWord(count) { return count === 1 ? (managerT ? managerT('tabsWordSingular') : 'tab') : (managerT ? managerT('tabsWordPlural') : 'tabs'); } function showManagerToast(message) { if (typeof showToast === 'function') showToast(message); } function getErrorToast() { return managerT ? managerT('toastSessionActionFailed') : 'Could not update saved tabs'; } function resolvePageFromHash() { return globalScope.location?.hash === '#saved-tabs' ? 'saved-tabs' : 'home'; } async function setWorkspacePage(page, { updateHash = true, render = true } = {}) { sessionManagerPage = page === 'saved-tabs' ? 'saved-tabs' : 'home'; document.querySelectorAll('[data-workspace-page]').forEach(panel => { const isActive = panel.dataset.workspacePage === sessionManagerPage; panel.classList.toggle('is-active', isActive); panel.hidden = !isActive; }); document.querySelectorAll('[data-action="switch-workspace-page"]').forEach(button => { const isActive = button.dataset.page === sessionManagerPage; button.classList.toggle('is-active', isActive); button.setAttribute('aria-pressed', String(isActive)); }); document.body.classList.toggle('showing-saved-tabs-page', sessionManagerPage === 'saved-tabs'); if (updateHash) { const nextHash = sessionManagerPage === 'saved-tabs' ? '#saved-tabs' : '#home'; if (globalScope.location && globalScope.location.hash !== nextHash) { globalScope.history.replaceState(null, '', nextHash); } } if (!render) return; if (sessionManagerPage === 'saved-tabs') { await renderSavedTabsPage(); return; } const runtime = getDashboardRuntime(); if (typeof runtime.renderDashboard === 'function') { await runtime.renderDashboard(); } } function getSessionSummary(session) { const tabCount = Array.isArray(session?.tabs) ? session.tabs.length : 0; const tabWord = getSessionTabWord(tabCount); return `${tabCount} ${tabWord} saved ${formatRelativeTime(session.savedAt)}`; } function renderSavedSessionTabRows(session) { const tabs = Array.isArray(session?.tabs) ? session.tabs : []; if (!tabs.length) return ''; return `
${tabs.map((tab, index) => { const tabToken = buildSavedSessionTabToken(session.id, index); const iconData = managerGetIconSources ? managerGetIconSources(tab, 16) : { hostname: '', sources: [] }; const fallbackLabel = managerGetFallbackLabel ? managerGetFallbackLabel(tab.title || tab.url, iconData.hostname) : '?'; const faviconUrl = iconData.sources?.[0] || ''; const fallbackUrl = iconData.sources?.[1] || ''; const fallbackSrcset = escapeAttr(JSON.stringify(iconData.sources?.slice(2) || [])); const label = tab.title || tab.url || 'Tab'; const safeLabel = escapeText(label); const safeUrl = escapeText(tab.url || ''); const safeTooltip = escapeAttr(label); return `
${faviconUrl ? `` : ''} ${safeLabel}
`; }).join('')}
`; } function renderSessionTabPreview(session) { return renderSavedSessionTabRows(session); } function openSavedSessionRenameEditor(session) { if (!session?.id) return; savedSessionRenameState = { sessionId: String(session.id), value: String(session.name || '').trim() || 'Saved tabs', shouldFocus: true, }; void renderSavedTabsPage(); } function closeSavedSessionRenameEditor() { savedSessionRenameState = null; } function openSavedSessionSettings(sessionId = '') { const cleanSessionId = String(sessionId || '').trim(); if (!cleanSessionId) return; savedSessionSettingsState = { sessionId: cleanSessionId }; void renderSavedTabsPage(); } function closeSavedSessionSettings() { if (!savedSessionSettingsState) return; savedSessionSettingsState = null; } function toggleSavedSessionSettings(sessionId = '') { const cleanSessionId = String(sessionId || '').trim(); if (!cleanSessionId) return; if (savedSessionSettingsState?.sessionId === cleanSessionId) { closeSavedSessionSettings(); void renderSavedTabsPage(); return; } openSavedSessionSettings(cleanSessionId); } function isSavedSessionExpanded(sessionId = '') { return savedSessionCollapsedState[String(sessionId || '')] !== true; } async function loadSavedSessionCollapsedState(sessions = []) { const stored = await chrome.storage.local.get(SAVED_TAB_SESSION_COLLAPSED_KEY); const normalized = normalizeSavedTabSessionCollapsedState(stored[SAVED_TAB_SESSION_COLLAPSED_KEY], sessions); savedSessionCollapsedState = normalized; await chrome.storage.local.set({ [SAVED_TAB_SESSION_COLLAPSED_KEY]: normalized }); return normalized; } async function saveSavedSessionCollapsedState(nextState = {}, sessions = []) { savedSessionCollapsedState = normalizeSavedTabSessionCollapsedState(nextState, sessions); await chrome.storage.local.set({ [SAVED_TAB_SESSION_COLLAPSED_KEY]: savedSessionCollapsedState }); return savedSessionCollapsedState; } async function toggleSavedSessionCollapsed(sessionId = '') { const cleanSessionId = String(sessionId || '').trim(); if (!cleanSessionId) return; const sessions = await getOrderedSavedSessions(); await saveSavedSessionCollapsedState({ ...savedSessionCollapsedState, [cleanSessionId]: isSavedSessionExpanded(cleanSessionId), }, sessions); void renderSavedTabsPage(); } async function submitSavedSessionRename() { if (!savedSessionRenameState || !managerRenameSavedTabSession) return; const targetId = String(savedSessionRenameState.sessionId || ''); const cleanName = String(savedSessionRenameState.value || '').trim(); if (!targetId || !cleanName) { closeSavedSessionRenameEditor(); await renderSavedTabsPage(); return; } await managerRenameSavedTabSession(targetId, cleanName); closeSavedSessionRenameEditor(); await renderSavedTabsPage(); showManagerToast(managerT ? managerT('toastRenamedGroup', { name: cleanName }) : `Renamed to ${cleanName}`); } async function saveSavedSessionTabsFromIndexes(sessionId, tabIndexes = []) { if (!managerUpdateSavedTabSessionTabs) return; const sessions = await getOrderedSavedSessions(); const session = sessions.find(item => String(item?.id || '') === String(sessionId || '')); if (!session) return; const normalizedIndexes = [...new Set((Array.isArray(tabIndexes) ? tabIndexes : []).map(index => Number(index)).filter(Number.isInteger))]; const nextTabs = normalizedIndexes .map(index => session.tabs?.[index] || null) .filter(Boolean); await managerUpdateSavedTabSessionTabs(sessionId, nextTabs); } async function saveSavedSessionTabsFromTokens(sessionId, orderTokens = []) { if (!managerUpdateSavedTabSessionTabs) return; const sessions = await getOrderedSavedSessions(); const session = sessions.find(item => String(item?.id || '') === String(sessionId || '')); if (!session) return; const nextTabs = reorderSavedSessionTabsByTokens(session, orderTokens); await managerUpdateSavedTabSessionTabs(sessionId, nextTabs); } function renderSavedSessionSettingsMenu(sessionId = '') { const currentMode = getSavedSessionRestoreModeValue(); const navDisplayMode = getSavedSessionNavDisplayModeValue(); return ` `; } function renderSavedSessionsCountMarkup(sessions = []) { const sessionWord = sessions.length === 1 ? (managerT ? managerT('sessionWordSingular') : 'session') : (managerT ? managerT('sessionWordPlural') : 'sessions'); const settingsOpen = Boolean(savedSessionSettingsState); return `
${settingsOpen ? renderSavedSessionSettingsMenu('global') : ''}
${sessions.length ? `${sessions.length} ${sessionWord}` : ''}
`; } function renderSavedSessionCard(session) { const sessionId = escapeAttr(session.id); const expanded = isSavedSessionExpanded(session.id); const renameEditorOpen = savedSessionRenameState?.sessionId === String(session.id); const renameValue = renameEditorOpen ? (savedSessionRenameState?.value || session.name || 'Saved tabs') : (session.name || 'Saved tabs'); const safeRenameValue = escapeAttr(renameValue); return `
${renameEditorOpen ? `
` : ` `} ${escapeText(getSessionSummary(session))}
${renderSessionTabPreview(session)}
`; } function getSavedSessionNavIconData(session) { const firstTab = Array.isArray(session?.tabs) ? session.tabs[0] : null; const label = getSavedSessionNavLabel(session); if (managerGetGroupIcon) { return managerGetGroupIcon({ tabs: firstTab ? [firstTab] : [] }, label, 32); } const iconData = managerGetIconSources ? managerGetIconSources(firstTab || { title: label, url: firstTab?.url || '' }, 32) : { hostname: '', primaryDomain: '', sources: [] }; const fallbackLabel = managerGetFallbackLabel ? managerGetFallbackLabel(label, iconData.primaryDomain || iconData.hostname || '') : '?'; return { hostname: iconData.hostname || '', primaryDomain: iconData.primaryDomain || '', src: iconData.sources?.[0] || '', fallbackSrc: iconData.sources?.[1] || '', fallbackLabel, }; } function getSavedSessionNavLabel(session) { return String(session?.name || (managerT ? managerT('workspacePageSavedTabs') : 'Saved tabs')).trim() || 'Saved tabs'; } function renderSavedSessionNavButton(session) { const sessionId = String(session?.id || ''); const safeSessionId = escapeAttr(sessionId); const label = getSavedSessionNavLabel(session); const safeTooltip = escapeAttr(label); const iconData = getSavedSessionNavIconData(session); const navDisplayMode = getSavedSessionNavDisplayModeValue(); if (navDisplayMode === 'name') { return ` `; } return ` `; } function renderSavedTabsNavArea(sessions = []) { const runtime = getDashboardRuntime(); const renderSwitch = typeof runtime.renderWorkspacePageSwitch === 'function' ? runtime.renderWorkspacePageSwitch('saved-tabs') : ''; const renderTheme = typeof runtime.renderWorkspaceThemeTools === 'function' ? runtime.renderWorkspaceThemeTools() : ''; return `
${sessions.map(renderSavedSessionNavButton).join('')}
${renderSwitch} ${renderTheme}`; } async function loadSavedTabSessionOrder(sessions = []) { const stored = await chrome.storage.local.get(SAVED_TAB_SESSION_ORDER_KEY); const normalized = normalizeSavedTabSessionOrder(stored[SAVED_TAB_SESSION_ORDER_KEY]); const orderedSessions = applySavedTabSessionOrder(sessions, normalized); const nextOrder = buildSavedTabSessionOrder(orderedSessions); savedSessionOrderState = nextOrder; await chrome.storage.local.set({ [SAVED_TAB_SESSION_ORDER_KEY]: nextOrder }); return nextOrder; } async function saveSavedTabSessionOrder(orderIds = []) { savedSessionOrderState = normalizeSavedTabSessionOrder(orderIds); await chrome.storage.local.set({ [SAVED_TAB_SESSION_ORDER_KEY]: savedSessionOrderState }); return savedSessionOrderState; } async function getOrderedSavedSessions() { if (!managerGetSavedTabSessions) return []; const sessions = await managerGetSavedTabSessions(); await loadSavedSessionCollapsedState(sessions); await loadSavedTabSessionOrder(sessions); return applySavedTabSessionOrder(sessions, savedSessionOrderState); } function getSavedSessionNavListEl() { return document.querySelector('#workspaceTopNav .group-nav-list[data-nav-kind="saved-sessions"]'); } function animateSavedSessionNavButtonNode(button, previousRect) { if (!button || !previousRect || button.classList.contains('is-dragging')) return; const nextRect = button.getBoundingClientRect(); const deltaX = previousRect.left - nextRect.left; const deltaY = previousRect.top - nextRect.top; if (!deltaX && !deltaY) return; button.style.transition = 'none'; button.style.transform = `translate3d(${deltaX}px, ${deltaY}px, 0)`; requestAnimationFrame(() => { button.style.transition = 'transform 180ms cubic-bezier(0.22, 1, 0.36, 1)'; button.style.transform = ''; }); } function animateSavedSessionNavButtons(navListEl, previousRects) { navListEl?.querySelectorAll('.saved-session-nav-button').forEach(button => { const key = button.dataset.sessionId || ''; animateSavedSessionNavButtonNode(button, previousRects.get(key)); }); } function ensureSavedSessionDragPlaceholder() { if (savedSessionDragPlaceholderEl || !draggedSavedSessionButtonEl) return savedSessionDragPlaceholderEl; savedSessionDragPlaceholderEl = document.createElement('div'); savedSessionDragPlaceholderEl.className = 'group-nav-placeholder'; draggedSavedSessionButtonEl.insertAdjacentElement('afterend', savedSessionDragPlaceholderEl); return savedSessionDragPlaceholderEl; } function clearSavedSessionDragState() { draggedSavedSessionId = ''; savedSessionDragStartPoint = null; draggedSavedSessionButtonEl = null; savedSessionDragPlaceholderEl?.remove(); savedSessionDragPlaceholderEl = null; document.body.classList.remove('group-dragging'); document.querySelectorAll('.saved-session-nav-button.is-dragging').forEach(button => { button.classList.remove('is-dragging'); button.style.removeProperty('--drag-left'); button.style.removeProperty('--drag-top'); button.style.removeProperty('transition'); button.style.removeProperty('transform'); }); } function ensureSavedSessionTabPlaceholder(listEl) { if (!draggedSavedSessionTabRowEl) return null; if (!savedSessionTabPlaceholderEl) { savedSessionTabPlaceholderEl = document.createElement('div'); savedSessionTabPlaceholderEl.className = 'saved-session-tab-placeholder'; savedSessionTabPlaceholderEl.style.height = `${draggedSavedSessionTabRowEl.getBoundingClientRect().height}px`; } if (listEl && savedSessionTabPlaceholderEl.parentElement !== listEl) { listEl.appendChild(savedSessionTabPlaceholderEl); } return savedSessionTabPlaceholderEl; } function clearSavedSessionTabDropPreview() { document.querySelectorAll('.saved-session-card.is-drop-target').forEach(card => { card.classList.remove('is-drop-target'); }); document.body.classList.remove('saved-session-drop-new'); savedSessionNewSessionSlotEl?.remove(); savedSessionNewSessionSlotEl = null; } function setSavedSessionTabDropPreview(cardEl, { createNewSession = false, newSessionPlacement = 'after', insertBeforeCardEl = null, } = {}) { clearSavedSessionTabDropPreview(); if (cardEl) cardEl.classList.add('is-drop-target'); if (!createNewSession) return; document.body.classList.add('saved-session-drop-new'); const listEl = document.getElementById('savedSessionsList'); if (!listEl) return; savedSessionNewSessionSlotEl = document.createElement('div'); savedSessionNewSessionSlotEl.className = 'saved-session-new-slot'; savedSessionNewSessionSlotEl.innerHTML = ''; const firstCard = listEl.querySelector('.saved-session-card'); if (insertBeforeCardEl) { listEl.insertBefore(savedSessionNewSessionSlotEl, insertBeforeCardEl); } else if (newSessionPlacement === 'before' && firstCard) { listEl.insertBefore(savedSessionNewSessionSlotEl, firstCard); } else { listEl.appendChild(savedSessionNewSessionSlotEl); } } function clearSavedSessionTabDragState({ removeNode = false } = {}) { const handleEl = savedSessionTabDragState?.handleEl || null; const pointerId = savedSessionTabDragState?.pointerId; savedSessionTabDragState = null; savedSessionTabPlaceholderEl?.remove(); savedSessionTabPlaceholderEl = null; clearSavedSessionTabDropPreview(); document.body.classList.remove('page-chip-list-dragging'); document.body.classList.remove('page-chip-drag-armed'); if (draggedSavedSessionTabRowEl) { if (removeNode) draggedSavedSessionTabRowEl.remove(); draggedSavedSessionTabRowEl.classList.remove('is-dragging'); draggedSavedSessionTabRowEl.style.removeProperty('--drag-left'); draggedSavedSessionTabRowEl.style.removeProperty('--drag-top'); draggedSavedSessionTabRowEl.style.removeProperty('--drag-width'); } if (handleEl && pointerId != null && typeof handleEl.releasePointerCapture === 'function') { try { if (handleEl.hasPointerCapture?.(pointerId)) handleEl.releasePointerCapture(pointerId); } catch {} } draggedSavedSessionTabRowEl = null; } function updateDraggedSavedSessionTabPosition(clientX, clientY) { if (!draggedSavedSessionTabRowEl || !savedSessionTabDragState) return; draggedSavedSessionTabRowEl.style.setProperty('--drag-left', `${clientX - savedSessionTabDragState.offsetX}px`); draggedSavedSessionTabRowEl.style.setProperty('--drag-top', `${clientY - savedSessionTabDragState.offsetY}px`); } function previewSavedSessionTabOrder(clientY) { const listEl = savedSessionTabDragState?.listEl; if (!listEl || !draggedSavedSessionTabRowEl || !savedSessionTabDragState) return; const placeholder = ensureSavedSessionTabPlaceholder(listEl); const items = [...listEl.querySelectorAll('.saved-session-tab-row:not(.is-dragging)')]; let insertBeforeItem = null; for (const item of items) { const rect = item.getBoundingClientRect(); if (clientY < rect.top + rect.height / 2) { insertBeforeItem = item; break; } } if (insertBeforeItem) { listEl.insertBefore(placeholder, insertBeforeItem); } else { listEl.appendChild(placeholder); } } function buildSavedSessionTabOrderFromPreview() { const listEl = savedSessionTabDragState?.listEl; if (!listEl) return []; return [...listEl.children] .map(node => { if (node === savedSessionTabPlaceholderEl) return savedSessionTabDragState?.tabToken || ''; return node.dataset?.savedTabToken || ''; }) .filter(Boolean); } function getSavedSessionCardFromPoint(clientX, clientY) { const element = document.elementFromPoint(clientX, clientY); return element?.closest?.('.saved-session-card') || null; } function getSavedSessionListFromCard(cardEl) { return cardEl?.querySelector?.('[data-saved-session-tab-list]') || null; } function getSavedSessionTabDropTarget(clientX, clientY) { const listEl = document.getElementById('savedSessionsList'); const cards = listEl ? [...listEl.querySelectorAll('.saved-session-card')] : []; if (!listEl || !cards.length) return null; const firstRect = cards[0].getBoundingClientRect(); const lastRect = cards[cards.length - 1].getBoundingClientRect(); const edgeThreshold = 18; const gapThreshold = 24; if (clientY <= firstRect.top + edgeThreshold) { return { kind: 'new-session', placement: 'before', reason: 'top-edge' }; } if (clientY >= lastRect.bottom - edgeThreshold) { return { kind: 'new-session', placement: 'after', reason: 'bottom-edge' }; } for (let index = 0; index < cards.length - 1; index += 1) { const currentRect = cards[index].getBoundingClientRect(); const nextCardEl = cards[index + 1]; const nextRect = nextCardEl.getBoundingClientRect(); const gapTop = currentRect.bottom - gapThreshold; const gapBottom = nextRect.top + gapThreshold; if (clientY >= gapTop && clientY <= gapBottom) { return { kind: 'new-session', placement: 'before', insertBeforeCardEl: nextCardEl, insertBeforeSessionId: nextCardEl.dataset?.sessionId || '', reason: 'card-gap', }; } } for (const cardEl of cards) { const rect = cardEl.getBoundingClientRect(); const withinCardY = clientY >= rect.top && clientY <= rect.bottom; if (!withinCardY) continue; const targetListEl = getSavedSessionListFromCard(cardEl); const sessionId = cardEl.dataset?.sessionId || ''; if (targetListEl && sessionId) { return { kind: 'session', cardEl, listEl: targetListEl, sessionId, }; } } return null; } function buildSavedSessionTabOrderFromList(listEl, draggedToken = '') { if (!listEl) return []; return [...listEl.children] .map(node => { if (node === savedSessionTabPlaceholderEl) return draggedToken; return node.dataset?.savedTabToken || ''; }) .filter(Boolean); } function syncSavedSessionTabDropTarget(clientX, clientY) { if (!savedSessionTabDragState) return; const dropTarget = getSavedSessionTabDropTarget(clientX, clientY); if (!dropTarget) { savedSessionTabDragState = { ...savedSessionTabDragState, listEl: savedSessionTabDragState.sourceListEl || savedSessionTabDragState.listEl, targetSessionId: savedSessionTabDragState.sessionId, createNewSession: false, newSessionPlacement: '', insertBeforeSessionId: '', lastResolvedDropTarget: null, }; clearSavedSessionTabDropPreview(); return; } if (dropTarget.kind === 'new-session') { savedSessionTabDragState = { ...savedSessionTabDragState, listEl: null, targetSessionId: '', createNewSession: true, newSessionPlacement: dropTarget.placement || 'after', insertBeforeSessionId: dropTarget.insertBeforeSessionId || '', lastResolvedDropTarget: { kind: 'new-session', placement: dropTarget.placement || 'after', insertBeforeSessionId: dropTarget.insertBeforeSessionId || '', reason: dropTarget.reason || '', }, }; savedSessionTabPlaceholderEl?.remove(); setSavedSessionTabDropPreview(null, { createNewSession: true, newSessionPlacement: savedSessionTabDragState.newSessionPlacement, insertBeforeCardEl: dropTarget.insertBeforeCardEl || null, }); return; } savedSessionTabDragState = { ...savedSessionTabDragState, listEl: dropTarget.listEl || savedSessionTabDragState.sourceListEl || savedSessionTabDragState.listEl, targetSessionId: dropTarget.sessionId || savedSessionTabDragState.sessionId, createNewSession: false, newSessionPlacement: '', insertBeforeSessionId: '', lastResolvedDropTarget: { kind: 'session', sessionId: dropTarget.sessionId || '', }, }; setSavedSessionTabDropPreview(dropTarget.cardEl || null); } async function moveSavedSessionTabAcrossSessions(sourceSessionId, targetSessionId, sourceOrder, targetOrder, draggedToken) { if (!managerUpdateSavedTabSessionTabs) return; const sessions = await getOrderedSavedSessions(); const sourceSession = sessions.find(item => String(item?.id || '') === String(sourceSessionId || '')); const targetSession = sessions.find(item => String(item?.id || '') === String(targetSessionId || '')); if (!sourceSession || !targetSession) return; const { sourceTabs: nextSourceTabs, targetTabs: nextTargetTabs } = moveSavedSessionTabsByTokens({ sourceSession, targetSession, sourceOrderTokens: sourceOrder, targetOrderTokens: targetOrder, draggedToken, }); await managerUpdateSavedTabSessionTabs(sourceSessionId, nextSourceTabs); await managerUpdateSavedTabSessionTabs(targetSessionId, nextTargetTabs); } async function detachDraggedSavedSessionTabToNewSession(draggedToken, { placement = 'after', insertBeforeSessionId = '', } = {}) { if (!managerSaveSavedTabSessions) return null; const sessions = await getOrderedSavedSessions(); const result = detachSavedSessionTabToNewSession({ sessions, draggedToken, placement, insertBeforeSessionId, now: new Date().toISOString(), }); if (!result?.createdSession) return null; await managerSaveSavedTabSessions(result.sessions); await saveSavedTabSessionOrder(result.orderIds); return result; } async function restoreSavedSessionTab(sessionId, tabIndex) { const sessions = await getOrderedSavedSessions(); const session = sessions.find(item => String(item?.id || '') === String(sessionId || '')); const tab = session?.tabs?.[tabIndex] || null; if (!tab?.url) return; const runtime = getDashboardRuntime(); if (typeof runtime.restoreSavedTabToBrowser === 'function') { await runtime.restoreSavedTabToBrowser(tab.url); } else { await chrome.tabs.create({ url: tab.url, active: true }); } showManagerToast(managerT ? managerT('toastSavedSessionTabRestored') : 'Restored tab'); } function updateDraggedSavedSessionPosition(clientX, clientY) { if (!draggedSavedSessionButtonEl || !savedSessionDragStartPoint) return; draggedSavedSessionButtonEl.style.setProperty('--drag-left', `${clientX - savedSessionDragStartPoint.offsetX}px`); draggedSavedSessionButtonEl.style.setProperty('--drag-top', `${clientY - savedSessionDragStartPoint.offsetY}px`); } function previewSavedSessionOrder(clientX) { const navListEl = getSavedSessionNavListEl(); if (!navListEl || !draggedSavedSessionId) return; const placeholder = ensureSavedSessionDragPlaceholder(); const previousRects = new Map(); const buttons = [...navListEl.querySelectorAll('.saved-session-nav-button:not(.is-dragging)')]; buttons.forEach(button => { previousRects.set(button.dataset.sessionId || '', button.getBoundingClientRect()); }); let insertBeforeButton = null; for (const button of buttons) { const rect = button.getBoundingClientRect(); if (clientX < rect.left + rect.width / 2) { insertBeforeButton = button; break; } } if (insertBeforeButton) { navListEl.insertBefore(placeholder, insertBeforeButton); } else { navListEl.appendChild(placeholder); } animateSavedSessionNavButtons(navListEl, previousRects); } function buildSavedSessionOrderFromPreview() { const navListEl = getSavedSessionNavListEl(); if (!navListEl || !draggedSavedSessionId) return savedSessionOrderState.slice(); return [...navListEl.children] .map(node => { if (node === savedSessionDragPlaceholderEl) return draggedSavedSessionId; return node.dataset?.sessionId || ''; }) .filter(Boolean); } function highlightSavedSessionCard(sessionId = '') { const target = document.querySelector(`.saved-session-card[data-session-id="${CSS.escape(String(sessionId || ''))}"]`); if (!target) return; target.scrollIntoView({ behavior: 'smooth', block: 'start' }); target.classList.add('group-nav-target'); setTimeout(() => target.classList.remove('group-nav-target'), 1200); } async function renderSavedSessionsList() { const listEl = document.getElementById('savedSessionsList'); const emptyEl = document.getElementById('savedSessionsEmpty'); const countEl = document.getElementById('savedSessionsCount'); if (!listEl) return; const sessions = await getOrderedSavedSessions(); const runtime = getDashboardRuntime(); if (typeof runtime.syncWorkspaceTopNavMarkup === 'function') { runtime.syncWorkspaceTopNavMarkup(renderSavedTabsNavArea(sessions), true); } if (countEl) { countEl.innerHTML = renderSavedSessionsCountMarkup(sessions); } listEl.innerHTML = sessions.map(renderSavedSessionCard).join(''); if (emptyEl) emptyEl.hidden = sessions.length > 0; } async function renderSavedTabsPage() { await renderSavedSessionsList(); if (typeof setupImageErrorHandlers === 'function') setupImageErrorHandlers(); if (savedSessionRenameState?.shouldFocus) { const focusId = String(savedSessionRenameState.sessionId || ''); requestAnimationFrame(() => { const renameInput = document.querySelector(`[data-saved-session-rename-input="${CSS.escape(focusId)}"]`); if (!renameInput) return; renameInput.focus(); renameInput.select?.(); if (savedSessionRenameState && savedSessionRenameState.sessionId === focusId) { savedSessionRenameState.shouldFocus = false; } }); } } async function saveCurrentWindowSession() { const runtime = getDashboardRuntime(); if (typeof runtime.saveCurrentWindowTabSession !== 'function') return; try { const result = await runtime.saveCurrentWindowTabSession(); if (sessionManagerPage === 'saved-tabs') await renderSavedTabsPage(); showManagerToast(managerT ? managerT('toastSessionSaved', { count: result?.session?.tabs?.length || 0 }) : 'Session saved'); } catch (error) { console.error('[tab-harbor] Failed to save current window session:', error); showManagerToast(getErrorToast()); } } async function restoreSession(sessionId) { const runtime = getDashboardRuntime(); if (typeof runtime.restoreSavedTabSession !== 'function') return; try { const result = await runtime.restoreSavedTabSession(sessionId); await renderSavedTabsPage(); showManagerToast(managerT ? managerT('toastSessionRestored', { count: result?.restoredCount || 0 }) : 'Session restored'); } catch (error) { console.error('[tab-harbor] Failed to restore tab session:', error); showManagerToast(getErrorToast()); } } async function deleteSession(sessionId) { if (!managerDeleteSavedTabSession) return; try { await managerDeleteSavedTabSession(sessionId); await renderSavedTabsPage(); showManagerToast(managerT ? managerT('toastSessionDeleted') : 'Session deleted'); } catch (error) { console.error('[tab-harbor] Failed to delete tab session:', error); showManagerToast(getErrorToast()); } } function syncSavedSessionRenameInputValue(inputEl) { if (!inputEl || !savedSessionRenameState) return; savedSessionRenameState = { ...savedSessionRenameState, value: inputEl.value, }; } document.addEventListener('click', async event => { if (event.target.closest('.saved-session-reorder-handle')) return; const actionEl = event.target.closest('[data-action]'); if (!actionEl) return; const action = actionEl.dataset.action; if (action === 'switch-workspace-page') { await setWorkspacePage(actionEl.dataset.page || 'home'); return; } if (action === 'save-current-window-session') { if (actionEl.closest('#homePage')) return; event.preventDefault(); await saveCurrentWindowSession(); return; } if (action === 'jump-to-saved-session') { event.preventDefault(); highlightSavedSessionCard(actionEl.dataset.sessionId || ''); return; } if (action === 'restore-saved-session-tab') { if (Date.now() < savedSessionTabSuppressClickUntil) return; await restoreSavedSessionTab(actionEl.dataset.sessionId || '', Number(actionEl.dataset.tabIndex)); return; } if (action === 'rename-saved-session') { event.preventDefault(); const sessions = await getOrderedSavedSessions(); const session = sessions.find(item => String(item?.id || '') === String(actionEl.dataset.sessionId || '')); if (session) openSavedSessionRenameEditor(session); return; } if (action === 'toggle-saved-session-collapse') { event.preventDefault(); await toggleSavedSessionCollapsed(actionEl.dataset.sessionId || ''); return; } if (action === 'toggle-saved-session-settings') { event.preventDefault(); event.stopPropagation(); toggleSavedSessionSettings(actionEl.dataset.sessionId || ''); return; } if (action === 'submit-saved-session-rename') { event.preventDefault(); await submitSavedSessionRename(); return; } if (action === 'restore-tab-session') { await restoreSession(actionEl.dataset.sessionId || ''); return; } if (action === 'delete-saved-session-tab') { const sessionId = actionEl.dataset.sessionId || ''; const tabIndex = Number(actionEl.dataset.tabIndex); if (!sessionId || !Number.isInteger(tabIndex) || !managerUpdateSavedTabSessionTabs) return; const sessions = await getOrderedSavedSessions(); const session = sessions.find(item => String(item?.id || '') === String(sessionId)); if (!session) return; const nextTabs = (session.tabs || []).filter((_, index) => index !== tabIndex); await managerUpdateSavedTabSessionTabs(sessionId, nextTabs); await renderSavedTabsPage(); showManagerToast(managerT ? managerT('toastSessionDeleted') : 'Session deleted'); return; } if (action === 'delete-tab-session') { await deleteSession(actionEl.dataset.sessionId || ''); } }); document.addEventListener('pointerdown', event => { const button = event.target.closest('.saved-session-nav-button[data-nav-kind="saved-sessions"]'); if (!button || sessionManagerPage !== 'saved-tabs') return; draggedSavedSessionId = button.dataset.sessionId || ''; draggedSavedSessionButtonEl = button; const rect = button.getBoundingClientRect(); savedSessionDragStartPoint = { x: event.clientX, y: event.clientY, offsetX: event.clientX - rect.left, offsetY: event.clientY - rect.top, moved: false, }; }); document.addEventListener('pointermove', event => { if (!draggedSavedSessionId || !savedSessionDragStartPoint || sessionManagerPage !== 'saved-tabs') return; const distance = Math.hypot(event.clientX - savedSessionDragStartPoint.x, event.clientY - savedSessionDragStartPoint.y); if (!savedSessionDragStartPoint.moved && distance < 2) return; if (!savedSessionDragStartPoint.moved) { savedSessionDragStartPoint.moved = true; document.body.classList.add('group-dragging'); draggedSavedSessionButtonEl?.classList.add('is-dragging'); ensureSavedSessionDragPlaceholder(); } updateDraggedSavedSessionPosition(event.clientX, event.clientY); previewSavedSessionOrder(event.clientX); }); document.addEventListener('pointerup', async () => { if (!draggedSavedSessionId) return; const moved = savedSessionDragStartPoint?.moved; if (moved) { await saveSavedTabSessionOrder(buildSavedSessionOrderFromPreview()); } clearSavedSessionDragState(); if (moved && sessionManagerPage === 'saved-tabs') { await renderSavedTabsPage(); } }); document.addEventListener('pointercancel', () => { if (!draggedSavedSessionId) return; clearSavedSessionDragState(); }); document.addEventListener('pointerdown', event => { const handleEl = event.target.closest('.saved-session-reorder-handle'); const rowEl = event.target.closest('.saved-session-tab-row'); const listEl = rowEl?.closest('[data-saved-session-tab-list]'); if (!handleEl || !rowEl || !listEl || sessionManagerPage !== 'saved-tabs') return; const sessionId = rowEl.dataset.savedSessionId || ''; const tabIndex = Number(rowEl.dataset.savedTabIndex); const tabToken = rowEl.dataset.savedTabToken || ''; if (!sessionId || !Number.isInteger(tabIndex) || !tabToken) return; const rect = rowEl.getBoundingClientRect(); draggedSavedSessionTabRowEl = rowEl; savedSessionTabDragState = { sessionId, targetSessionId: sessionId, tabIndex, tabToken, listEl, sourceListEl: listEl, handleEl, pointerId: event.pointerId, x: event.clientX, y: event.clientY, offsetX: event.clientX - rect.left, offsetY: event.clientY - rect.top, createNewSession: false, newSessionPlacement: '', insertBeforeSessionId: '', lastResolvedDropTarget: null, moved: false, }; }); document.addEventListener('pointermove', event => { if (!savedSessionTabDragState || !draggedSavedSessionTabRowEl || sessionManagerPage !== 'saved-tabs') return; const distance = Math.hypot(event.clientX - savedSessionTabDragState.x, event.clientY - savedSessionTabDragState.y); if (!savedSessionTabDragState.moved && distance < 2) return; if (!savedSessionTabDragState.moved) { savedSessionTabDragState.moved = true; document.body.classList.add('page-chip-list-dragging'); draggedSavedSessionTabRowEl.classList.add('is-dragging'); draggedSavedSessionTabRowEl.style.setProperty('--drag-width', `${draggedSavedSessionTabRowEl.getBoundingClientRect().width}px`); ensureSavedSessionTabPlaceholder(savedSessionTabDragState.listEl); } updateDraggedSavedSessionTabPosition(event.clientX, event.clientY); syncSavedSessionTabDropTarget(event.clientX, event.clientY); if (!savedSessionTabDragState?.createNewSession) { previewSavedSessionTabOrder(event.clientY); } }); document.addEventListener('pointerup', async () => { if (!savedSessionTabDragState) return; const moved = savedSessionTabDragState.moved; const sessionId = savedSessionTabDragState.sessionId; const draggedToken = savedSessionTabDragState.tabToken || ''; const nextOrder = buildSavedSessionTabOrderFromPreview(); const targetSessionId = savedSessionTabDragState.targetSessionId || sessionId; const createNewSession = savedSessionTabDragState.createNewSession; const newSessionPlacement = savedSessionTabDragState.newSessionPlacement || 'after'; const insertBeforeSessionId = savedSessionTabDragState.insertBeforeSessionId || ''; const sourceListEl = document.querySelector(`[data-saved-session-tab-list="${CSS.escape(sessionId)}"]`); const sourceOrder = buildSavedSessionTabOrderFromList(sourceListEl); clearSavedSessionTabDragState({ removeNode: moved }); if (moved) { savedSessionTabSuppressClickUntil = Date.now() + 250; if (createNewSession) { await detachDraggedSavedSessionTabToNewSession(draggedToken, { placement: newSessionPlacement, insertBeforeSessionId, }); } else if (targetSessionId === sessionId) { await saveSavedSessionTabsFromTokens(sessionId, nextOrder); } else { await moveSavedSessionTabAcrossSessions( sessionId, targetSessionId, sourceOrder.filter(token => token !== draggedToken), nextOrder, draggedToken ); } await renderSavedTabsPage(); } }); document.addEventListener('pointercancel', () => { if (!savedSessionTabDragState) return; clearSavedSessionTabDragState(); }); document.addEventListener('pointerdown', event => { if (!savedSessionSettingsState) return; if (event.target.closest('.saved-session-settings-wrap')) return; closeSavedSessionSettings(); void renderSavedTabsPage(); }); document.addEventListener('input', event => { const inputEl = event.target.closest('.saved-session-rename-input'); if (!inputEl) return; syncSavedSessionRenameInputValue(inputEl); }); document.addEventListener('change', event => { const navDisplaySelectEl = event.target.closest('[data-action="change-saved-session-nav-display-mode"]'); if (navDisplaySelectEl) { void persistSavedSessionNavDisplayMode(navDisplaySelectEl.value); return; } const selectEl = event.target.closest('[data-action="change-saved-session-restore-mode"]'); if (!selectEl) return; void persistSavedSessionRestoreMode(selectEl.value); }); document.addEventListener('keydown', event => { if (event.key === 'Escape' && savedSessionSettingsState) { closeSavedSessionSettings(); void renderSavedTabsPage(); return; } const inputEl = event.target.closest('.saved-session-rename-input'); if (!inputEl || !savedSessionRenameState) return; if (event.key === 'Enter') { event.preventDefault(); void submitSavedSessionRename(); return; } if (event.key === 'Escape') { closeSavedSessionRenameEditor(); void renderSavedTabsPage(); return; } }); document.addEventListener('focusout', event => { const inputEl = event.target.closest('.saved-session-rename-input'); if (!inputEl || !savedSessionRenameState) return; const nextFocused = event.relatedTarget; if (nextFocused && nextFocused.closest?.('.mission-rename-form')) return; void submitSavedSessionRename(); }); globalScope.addEventListener?.('hashchange', () => { void setWorkspacePage(resolvePageFromHash(), { updateHash: false }); }); function initializeSessionManager() { void setWorkspacePage(resolvePageFromHash(), { updateHash: false }); } const api = { ...exportsApi, initializeSessionManager, renderSavedTabsPage, setWorkspacePage, }; globalScope.TabHarborSessionManager = api; if (typeof module !== 'undefined' && module.exports) { module.exports = api; } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initializeSessionManager, { once: true }); } else { initializeSessionManager(); } })(typeof globalThis !== 'undefined' ? globalThis : window);