diff --git a/extension/app.js b/extension/app.js index b2ecb78c..b679d8e3 100644 --- a/extension/app.js +++ b/extension/app.js @@ -707,6 +707,52 @@ const ICONS = { IN-MEMORY STORE FOR OPEN-TAB GROUPS ---------------------------------------------------------------- */ let domainGroups = []; +let showWindowLabels = false; +let windowNameMap = {}; + + +/* ---------------------------------------------------------------- + WINDOW MANAGEMENT + ---------------------------------------------------------------- */ + +/** + * buildWindowNameMap() + * + * Assigns sequential names (Window 1, Window 2, ...) to each unique + * windowId so we can label tabs with their window of origin. + */ +function buildWindowNameMap() { + const windowIds = [...new Set(openTabs.map(t => t.windowId))]; + windowNameMap = {}; + windowIds.forEach((id, i) => { + windowNameMap[id] = `Window ${i + 1}`; + }); +} + +/** + * getWindowCount() + * + * Returns the number of distinct Chrome windows that have open tabs. + */ +function getWindowCount() { + return new Set(openTabs.map(t => t.windowId)).size; +} + +/** + * mergeAllWindows() + * + * Moves all tabs from every other window into the current window. + * After merging, re-fetches the tab list. + */ +async function mergeAllWindows() { + const currentWindow = await chrome.windows.getCurrent(); + const allTabs = await chrome.tabs.query({}); + const tabsToMove = allTabs.filter(t => t.windowId !== currentWindow.id); + for (const tab of tabsToMove) { + await chrome.tabs.move(tab.id, { windowId: currentWindow.id, index: -1 }); + } + await fetchOpenTabs(); +} /* ---------------------------------------------------------------- @@ -849,9 +895,11 @@ function renderDomainCard(group) { let domain = ''; try { domain = new URL(tab.url).hostname; } catch {} const faviconUrl = domain ? `https://www.google.com/s2/favicons?domain=${domain}&sz=16` : ''; + const winLabel = showWindowLabels && windowNameMap[tab.windowId] + ? `${windowNameMap[tab.windowId]}` : ''; return `
${faviconUrl ? `` : ''} - ${label}${dupeTag} + ${label}${dupeTag}${winLabel}
`; + const winCount = getWindowCount(); + const mergeBtn = winCount > 1 + ? ` ` : ''; + const showWinToggle = winCount > 1 + ? ` ` : ''; + openTabsSectionCount.innerHTML = `${domainGroups.length} domain${domainGroups.length !== 1 ? 's' : ''}${winCount > 1 ? ` · ${winCount} windows` : ''}  ·  ${mergeBtn}${showWinToggle}`; openTabsMissionsEl.innerHTML = domainGroups.map(g => renderDomainCard(g)).join(''); openTabsSection.style.display = 'block'; } else if (openTabsSection) { @@ -1160,6 +1218,8 @@ async function renderStaticDashboard() { // --- Footer stats --- const statTabs = document.getElementById('statTabs'); if (statTabs) statTabs.textContent = openTabs.length; + const statWindows = document.getElementById('statWindows'); + if (statWindows) statWindows.textContent = getWindowCount(); // --- Check for duplicate Tab Out tabs --- checkTabOutDupes(); @@ -1412,6 +1472,22 @@ document.addEventListener('click', async (e) => { return; } + // ---- Merge all windows into current ---- + if (action === 'merge-windows') { + await mergeAllWindows(); + playCloseSound(); + showToast('All windows merged into one'); + await renderDashboard(); + return; + } + + // ---- Toggle window labels on tabs ---- + if (action === 'toggle-window-labels') { + showWindowLabels = !showWindowLabels; + await renderDashboard(); + return; + } + // ---- Close ALL open tabs ---- if (action === 'close-all-open-tabs') { const allUrls = openTabs diff --git a/extension/index.html b/extension/index.html index e944a2a8..966ee7cb 100644 --- a/extension/index.html +++ b/extension/index.html @@ -105,6 +105,10 @@

Saved for later

Open tabs
+
+
+
Windows
+
Tab Out by Zara diff --git a/extension/style.css b/extension/style.css index c7bc0b5d..3f101d14 100644 --- a/extension/style.css +++ b/extension/style.css @@ -1156,3 +1156,17 @@ footer { animation: fadeUp 0.5s ease 0.65s both; } opacity: 0.6; flex-shrink: 0; } + +/* ---- Window badge on tab chips ---- */ +.chip-window-badge { + font-size: 9px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--accent-slate); + background: rgba(90, 107, 122, 0.1); + padding: 1px 6px; + border-radius: 3px; + flex-shrink: 0; + white-space: nowrap; +}