'use strict'; const DEFAULTS = { backendUrl: 'http://localhost:3000', apiToken: '', folderName: '' }; const els = { settings: document.getElementById('settings'), toggleSettings: document.getElementById('toggleSettings'), backendUrl: document.getElementById('backendUrl'), apiToken: document.getElementById('apiToken'), folderName: document.getElementById('folderName'), testBtn: document.getElementById('testBtn'), saveSettings: document.getElementById('saveSettings'), tabCount: document.getElementById('tabCount'), saveAll: document.getElementById('saveAll'), saveClose: document.getElementById('saveClose'), openBackend: document.getElementById('openBackend'), message: document.getElementById('message'), }; function getSettings() { return new Promise((resolve) => { chrome.storage.local.get(DEFAULTS, (s) => resolve(s)); }); } function setSettings(values) { return new Promise((resolve) => chrome.storage.local.set(values, resolve)); } function showMessage(text, kind) { els.message.textContent = text; els.message.className = 'message ' + (kind || ''); els.message.classList.remove('hidden'); } function normalizeBase(url) { return (url || '').trim().replace(/\/+$/, ''); } // Tabs worth archiving: skip internal chrome:// / extension pages. function archivable(tab) { return tab.url && /^https?:\/\//i.test(tab.url); } async function getCurrentWindowTabs() { return new Promise((resolve) => { chrome.tabs.query({ currentWindow: true }, (tabs) => resolve(tabs)); }); } async function refreshTabCount() { const tabs = await getCurrentWindowTabs(); els.tabCount.textContent = tabs.filter(archivable).length; } async function postBookmarks(settings, bookmarks) { const base = normalizeBase(settings.backendUrl); const res = await fetch(base + '/api/bookmarks/bulk', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + settings.apiToken, }, body: JSON.stringify({ bookmarks, folder_name: settings.folderName ? settings.folderName.trim() : undefined, }), }); if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error(err.error || ('HTTP ' + res.status)); } return res.json(); } async function handleSave(closeAfter) { const settings = await getSettings(); if (!settings.apiToken) { showMessage('Set your API token in settings (⚙️) first.', 'error'); els.settings.classList.remove('hidden'); return; } els.saveAll.disabled = true; els.saveClose.disabled = true; showMessage('Saving…', ''); try { const allTabs = await getCurrentWindowTabs(); const tabs = allTabs.filter(archivable); if (tabs.length === 0) { showMessage('No saveable tabs (only http/https tabs are archived).', 'error'); return; } const bookmarks = tabs.map((t) => ({ title: t.title, url: t.url })); const result = await postBookmarks(settings, bookmarks); const notes = []; if (result.skipped) notes.push(`${result.skipped} duplicate(s) skipped`); // `limited` is returned by the hosted backend when the free-plan cap is hit. if (result.limited) notes.push(`${result.limited} not saved — free plan limit reached`); const noteStr = notes.length ? ` (${notes.join('; ')})` : ''; showMessage(`Saved ${result.created} tab(s).${noteStr}`, result.limited ? 'error' : 'success'); // Only close once the save has been confirmed above. if (closeAfter) { await new Promise((resolve) => chrome.tabs.remove(tabs.map((t) => t.id), resolve)); } await refreshTabCount(); } catch (e) { showMessage('Failed: ' + e.message, 'error'); } finally { els.saveAll.disabled = false; els.saveClose.disabled = false; } } // --- Wire up UI --- async function init() { const s = await getSettings(); els.backendUrl.value = s.backendUrl; els.apiToken.value = s.apiToken; els.folderName.value = s.folderName; if (!s.apiToken) els.settings.classList.remove('hidden'); await refreshTabCount(); } els.toggleSettings.addEventListener('click', () => els.settings.classList.toggle('hidden')); els.saveSettings.addEventListener('click', async () => { await setSettings({ backendUrl: normalizeBase(els.backendUrl.value) || DEFAULTS.backendUrl, apiToken: els.apiToken.value.trim(), folderName: els.folderName.value.trim(), }); showMessage('Settings saved.', 'success'); els.settings.classList.add('hidden'); }); els.testBtn.addEventListener('click', async () => { const base = normalizeBase(els.backendUrl.value); const token = els.apiToken.value.trim(); if (!base || !token) { showMessage('Enter backend URL and token first.', 'error'); return; } showMessage('Testing…', ''); try { const res = await fetch(base + '/api/me', { headers: { 'Authorization': 'Bearer ' + token } }); if (!res.ok) throw new Error('HTTP ' + res.status); const data = await res.json(); showMessage('Connected as ' + data.user.email, 'success'); } catch (e) { showMessage('Connection failed: ' + e.message, 'error'); } }); els.saveAll.addEventListener('click', () => handleSave(false)); els.saveClose.addEventListener('click', () => handleSave(true)); els.openBackend.addEventListener('click', async () => { const settings = await getSettings(); const url = normalizeBase(settings.backendUrl) || DEFAULTS.backendUrl; chrome.tabs.create({ url }); }); init();