'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 ? `
})
` : ''}
${escapeText(fallbackLabel)}
${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 `
`;
}
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);