// LinkSync Popup Script // Handles bookmark management, sync, collections, and queries const Popup = { bookmarks: [], collections: [], async init() { this.setupTabs(); this.setupEventListeners(); await this.loadSettings(); await this.loadBookmarks(); await this.loadCollections(); this.updateSyncStatus(); }, setupTabs() { document.querySelectorAll(".tab").forEach((tab) => { tab.addEventListener("click", () => { document.querySelectorAll(".tab").forEach((t) => t.classList.remove("active")); document.querySelectorAll(".tab-content").forEach((c) => c.classList.remove("active")); tab.classList.add("active"); document.getElementById(`tab-${tab.dataset.tab}`).classList.add("active"); }); }); }, setupEventListeners() { document.getElementById("add-bookmark-form").addEventListener("submit", (e) => { e.preventDefault(); this.addBookmark(); }); document.getElementById("search").addEventListener("input", (e) => { this.filterBookmarks(e.target.value); }); document.getElementById("sync-btn").addEventListener("click", () => this.syncNow()); document.getElementById("settings-btn").addEventListener("click", () => this.openSettings()); document.getElementById("close-settings").addEventListener("click", () => this.closeSettings()); document.getElementById("settings-form").addEventListener("submit", (e) => { e.preventDefault(); this.saveSettings(); }); document.getElementById("toggle-key").addEventListener("click", () => this.toggleApiKey()); document.getElementById("test-connection").addEventListener("click", () => this.testConnection()); document.getElementById("parse-btn").addEventListener("click", () => this.parseQuery()); document.getElementById("execute-btn").addEventListener("click", () => this.executeQuery()); document.getElementById("settings-modal").addEventListener("click", (e) => { if (e.target.id === "settings-modal") this.closeSettings(); }); }, async loadSettings() { const settings = await browser.storage.local.get([ "linksync_server_url", "linksync_api_key", "linksync_sync_mode", "linksync_deletions", "linksync_auto_sync", ]); document.getElementById("server-url").value = settings.linksync_server_url || "http://localhost:5000"; document.getElementById("api-key").value = settings.linksync_api_key || ""; document.getElementById("sync-mode").value = settings.linksync_sync_mode || "bi-directional"; document.getElementById("deletions").checked = settings.linksync_deletions === true; document.getElementById("auto-sync").checked = settings.linksync_auto_sync === true; }, async openSettings() { document.getElementById("settings-modal").classList.add("open"); }, closeSettings() { document.getElementById("settings-modal").classList.remove("open"); }, toggleApiKey() { const input = document.getElementById("api-key"); const btn = document.getElementById("toggle-key"); if (input.type === "password") { input.type = "text"; btn.textContent = "Hide"; } else { input.type = "password"; btn.textContent = "Show"; } }, async saveSettings() { const settings = { linksync_server_url: document.getElementById("server-url").value.replace(/\/+$/, ""), linksync_api_key: document.getElementById("api-key").value, linksync_sync_mode: document.getElementById("sync-mode").value, linksync_deletions: document.getElementById("deletions").checked, linksync_auto_sync: document.getElementById("auto-sync").checked, }; await browser.storage.local.set(settings); await API.init(); this.closeSettings(); this.notify("Settings saved", "success"); await this.loadBookmarks(); }, async testConnection() { try { const result = await browser.runtime.sendMessage({ type: "TEST_CONNECTION" }); if (result.success) { this.notify("Connection successful", "success"); } else { this.notify(`Connection failed: ${result.error}`, "error"); } } catch (e) { this.notify(`Connection failed: ${e.message}`, "error"); } }, async loadBookmarks() { const container = document.getElementById("bookmarks-container"); container.innerHTML = '
Loading bookmarks...
'; try { const response = await browser.runtime.sendMessage({ type: "GET_BOOKMARKS", data: { limit: 50 } }); this.bookmarks = response || []; this.renderBookmarks(this.bookmarks); } catch (e) { container.innerHTML = `Error: ${e.message}
`; } }, renderBookmarks(bookmarks) { const container = document.getElementById("bookmarks-container"); if (!bookmarks || bookmarks.length === 0) { container.innerHTML = 'No bookmarks found
'; return; } container.innerHTML = bookmarks .map( (bm) => `Loading collections...
'; try { const response = await browser.runtime.sendMessage({ type: "GET_COLLECTIONS" }); this.collections = response || []; this.renderCollections(this.collections); } catch (e) { container.innerHTML = `Error: ${e.message}
`; } }, renderCollections(collections) { const container = document.getElementById("collections-container"); if (!collections || collections.length === 0) { container.innerHTML = 'No collections
'; return; } container.innerHTML = collections .map( (c) => `${this.escapeHtml(c.description)}
` : ""} ${c.query_type}${JSON.stringify(result.parsed, null, 2)}`;
this.notify("Query parsed successfully", "success");
} else {
output.innerHTML = `Invalid: ${this.escapeHtml(result.error)}`;
this.notify("Invalid query syntax", "error");
}
} catch (e) {
this.notify(`Parse error: ${e.message}`, "error");
}
},
async executeQuery() {
const expression = document.getElementById("query-input").value.trim();
if (!expression) {
this.notify("Enter a query expression", "info");
return;
}
const output = document.getElementById("query-result");
output.innerHTML = 'Executing...
'; try { const results = await browser.runtime.sendMessage({ type: "EXECUTE_QUERY", data: { expression, limit: 50 }, }); const items = results || []; if (items.length === 0) { output.innerHTML = 'No results
'; } else { output.innerHTML = items .map( (bm) => ` ` ) .join(""); this.notify(`Found ${items.length} results`, "success"); } } catch (e) { output.innerHTML = `Error: ${this.escapeHtml(e.message)}`; this.notify(`Query error: ${e.message}`, "error"); } }, notify(message, type = "info") { const container = document.getElementById("notification-container"); const el = document.createElement("div"); el.className = `notification ${type}`; el.textContent = message; container.appendChild(el); setTimeout(() => { el.style.opacity = "0"; el.style.transition = "opacity 0.3s"; setTimeout(() => el.remove(), 300); }, 3000); }, escapeHtml(str) { if (!str) return ""; const div = document.createElement("div"); div.textContent = str; return div.innerHTML; }, }; document.addEventListener("DOMContentLoaded", () => Popup.init());