// Initialize extension document.addEventListener('DOMContentLoaded', async () => { await updateStats(); setupEventListeners(); }); // Update tab and window count statistics async function updateStats() { try { const tabs = await chrome.tabs.query({}); const windows = await chrome.windows.getAll(); document.getElementById('tab-count').textContent = tabs.length; document.getElementById('window-count').textContent = windows.length; } catch (error) { console.error('Error updating stats:', error); } } // Setup event listeners function setupEventListeners() { const exportBtn = document.getElementById('export-btn'); exportBtn.addEventListener('click', handleExport); // Keyboard accessibility for options const checkboxes = document.querySelectorAll('input[type="checkbox"]'); checkboxes.forEach(checkbox => { checkbox.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); checkbox.checked = !checkbox.checked; } }); }); } // Main export handler async function handleExport() { const exportBtn = document.getElementById('export-btn'); const statusEl = document.getElementById('export-status'); // Get user options const includeDescriptions = document.getElementById('include-descriptions').checked; const groupByWindow = document.getElementById('group-by-window').checked; const includeTimestamp = document.getElementById('include-timestamp').checked; // Disable button and show loading state exportBtn.disabled = true; const originalContent = exportBtn.innerHTML; exportBtn.innerHTML = 'Exporting...'; statusEl.textContent = 'Collecting tab information...'; statusEl.className = 'status-message loading'; try { // Get all tabs const tabs = await chrome.tabs.query({}); // Fetch descriptions if needed if (includeDescriptions) { statusEl.textContent = `Fetching descriptions (0/${tabs.length})...`; await fetchDescriptions(tabs, statusEl); } // Generate markdown content const markdown = await generateMarkdown(tabs, { includeDescriptions, groupByWindow, includeTimestamp }); // Trigger download downloadMarkdown(markdown); // Show success message statusEl.textContent = '✓ Successfully exported ' + tabs.length + ' tabs!'; statusEl.className = 'status-message success'; } catch (error) { console.error('Export error:', error); statusEl.textContent = '✗ Error: ' + error.message; statusEl.className = 'status-message error'; } finally { // Re-enable button exportBtn.disabled = false; exportBtn.innerHTML = originalContent; } } // Fetch descriptions from tabs async function fetchDescriptions(tabs, statusEl) { for (let i = 0; i < tabs.length; i++) { const tab = tabs[i]; statusEl.textContent = `Fetching descriptions (${i + 1}/${tabs.length})...`; try { // Try to execute script to get description const results = await chrome.scripting.executeScript({ target: { tabId: tab.id }, func: extractDescription }); if (results && results[0] && results[0].result) { tab.description = results[0].result; } } catch (error) { // Tab might not support script injection (e.g., chrome:// pages) tab.description = null; } } } // Function to extract description (runs in tab context) function extractDescription() { const metaDesc = document.querySelector('meta[name="description"]'); const ogDesc = document.querySelector('meta[property="og:description"]'); return (metaDesc && metaDesc.content) || (ogDesc && ogDesc.content) || null; } // Generate markdown content async function generateMarkdown(tabs, options) { let markdown = '# Browser Tabs Export\n\n'; if (options.includeTimestamp) { const now = new Date(); const timestamp = now.toLocaleString('en-US', { dateStyle: 'full', timeStyle: 'short' }); markdown += `**Exported:** ${timestamp}\n\n`; } markdown += `**Total Tabs:** ${tabs.length}\n\n`; markdown += '---\n\n'; if (options.groupByWindow) { // Group tabs by window const windows = await chrome.windows.getAll(); const tabsByWindow = {}; tabs.forEach(tab => { if (!tabsByWindow[tab.windowId]) { tabsByWindow[tab.windowId] = []; } tabsByWindow[tab.windowId].push(tab); }); let windowIndex = 1; for (const windowId in tabsByWindow) { markdown += `## Window ${windowIndex}\n\n`; markdown += formatTabs(tabsByWindow[windowId], options.includeDescriptions); markdown += '---\n\n'; windowIndex++; } } else { // List all tabs together markdown += formatTabs(tabs, options.includeDescriptions); } return markdown; } // Format tabs as markdown function formatTabs(tabs, includeDescriptions) { let content = ''; tabs.forEach((tab, index) => { // List item with title and URL content += `- **${escapeMarkdown(tab.title)}**\n`; content += ` - URL: ${tab.url}\n`; // Description if available if (includeDescriptions && tab.description) { content += ` - Description: ${escapeMarkdown(tab.description)}\n`; } content += '\n'; }); return content; } // Escape markdown special characters function escapeMarkdown(text) { if (!text) return ''; return text.replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&'); } // Download markdown file function downloadMarkdown(content) { const blob = new Blob([content], { type: 'text/markdown;charset=utf-8' }); const url = URL.createObjectURL(blob); const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, '-'); const filename = `tabs-export-${timestamp}.md`; chrome.downloads.download({ url: url, filename: filename, saveAs: true }, (downloadId) => { // Cleanup object URL after download starts setTimeout(() => URL.revokeObjectURL(url), 100); }); }