From 866ebe680dcba1cb282edde41563e6329e19c6e4 Mon Sep 17 00:00:00 2001 From: wuxiangyu05 Date: Tue, 30 Jun 2026 17:32:10 +0800 Subject: [PATCH 1/2] Add bookmarks sidebar --- README.md | 7 +- extension/app.js | 163 +++++++++++++++++++++++++++++++++++++++- extension/index.html | 21 +++++- extension/manifest.json | 2 +- extension/style.css | 133 ++++++++++++++++++++++++++++++-- 5 files changed, 311 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index c95960bde..89b4a3a83 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ Tab Out is a Chrome extension that replaces your new tab page with a dashboard of everything you have open. Tabs are grouped by domain, with homepages (Gmail, X, LinkedIn, etc.) pulled into their own group. Close tabs with a satisfying swoosh + confetti. +This optimized fork adds a bookmarks bar sidebar and Chrome Manifest V3 CSP cleanup while preserving the original Tab Out experience. + No server. No account. No external API calls. Just a Chrome extension. --- @@ -27,6 +29,7 @@ The agent will walk you through it. Takes about 1 minute. - **Close tabs with style** with swoosh sound + confetti burst - **Duplicate detection** flags when you have the same page open twice, with one-click cleanup - **Click any tab to jump to it** across windows, no new tab opened +- **Browse your bookmarks bar** from the left sidebar when bookmarks are available - **Save for later** bookmark tabs to a checklist before closing them - **Localhost grouping** shows port numbers next to each tab so you can tell your vibe coding projects apart - **Expandable groups** show the first 8 tabs with a clickable "+N more" @@ -88,4 +91,6 @@ MIT --- -Built by [Zara](https://x.com/zarazhangrui) +Originally built by [Zara](https://x.com/zarazhangrui). + +Optimized by [wuxiangyu05](https://github.com/wuxiangyu05), with original authorship and license attribution preserved. diff --git a/extension/app.js b/extension/app.js index b2ecb78c6..d4d3c004b 100644 --- a/extension/app.js +++ b/extension/app.js @@ -54,6 +54,42 @@ async function fetchOpenTabs() { } } +/** + * fetchBookmarksBar() + * + * Reads Chrome's bookmarks bar folder. Chrome usually assigns id "1" to + * it, but the tree lookup keeps this working across localized browsers. + */ +async function fetchBookmarksBar() { + if (!chrome.bookmarks) { + return { + status: 'missing-permission', + items: [], + }; + } + + try { + const tree = await chrome.bookmarks.getTree(); + const rootChildren = tree[0]?.children || []; + const titleMatch = /^(bookmarks bar|书签栏|收藏夹栏|favorites bar)$/i; + const bar = + rootChildren.find(node => node.id === '1') || + rootChildren.find(node => titleMatch.test(node.title || '')) || + rootChildren[0]; + + return { + status: 'ok', + items: bar?.children || [], + }; + } catch (err) { + console.warn('[tab-out] Could not read bookmarks bar:', err); + return { + status: 'read-error', + items: [], + }; + } +} + /** * closeTabsByUrls(urls) * @@ -442,6 +478,16 @@ function showToast(message) { setTimeout(() => toast.classList.remove('visible'), 2500); } +function loadOptionalConfig() { + return new Promise(resolve => { + const script = document.createElement('script'); + script.src = 'config.local.js'; + script.addEventListener('load', resolve, { once: true }); + script.addEventListener('error', resolve, { once: true }); + document.head.appendChild(script); + }); +} + /** * checkAndShowEmptyState() * @@ -611,6 +657,15 @@ function capitalize(str) { return str.charAt(0).toUpperCase() + str.slice(1); } +function escapeHtml(value) { + return String(value || '') + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + function stripTitleNoise(title) { if (!title) return ''; // Strip leading notification count: "(2) Title" @@ -769,7 +824,7 @@ function buildOverflowChips(hiddenTabs, urlCounts = {}) { try { domain = new URL(tab.url).hostname; } catch {} const faviconUrl = domain ? `https://www.google.com/s2/favicons?domain=${domain}&sz=16` : ''; return `
- ${faviconUrl ? `` : ''} + ${faviconUrl ? `` : ''} ${label}${dupeTag}