init
This commit is contained in:
165
popup.js
Normal file
165
popup.js
Normal file
@@ -0,0 +1,165 @@
|
||||
'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();
|
||||
Reference in New Issue
Block a user