/** * Popup Script * Handles UI logic and communication with background script */ // DOM elements let loginScreen, captureScreen, loadingScreen; let loginHandle, loginPassword, loginButton, loginError; let currentUrl, noteInput, collectionSelect, submitButton, statusMessage, logoutButton; // State let currentTab = null; let metadata = null; let collections = []; /** * Initialize popup */ document.addEventListener('DOMContentLoaded', async () => { // Get DOM elements loginScreen = document.getElementById('loginScreen'); captureScreen = document.getElementById('captureScreen'); loadingScreen = document.getElementById('loadingScreen'); loginHandle = document.getElementById('loginHandle'); loginPassword = document.getElementById('loginPassword'); loginButton = document.getElementById('loginButton'); loginError = document.getElementById('loginError'); currentUrl = document.getElementById('currentUrl'); noteInput = document.getElementById('noteInput'); collectionSelect = document.getElementById('collectionSelect'); submitButton = document.getElementById('submitButton'); statusMessage = document.getElementById('statusMessage'); logoutButton = document.getElementById('logoutButton'); // Set up event listeners loginButton.addEventListener('click', handleLogin); submitButton.addEventListener('click', handleSubmit); logoutButton.addEventListener('click', handleLogout); collectionSelect.addEventListener('change', handleCollectionChange); // Enter key for login loginPassword.addEventListener('keypress', (e) => { if (e.key === 'Enter') { handleLogin(); } }); // Initialize await initialize(); }); /** * Initialize the popup */ async function initialize() { showScreen('loading'); try { // Check if user is authenticated const sessionResult = await sendMessage({ action: 'getSession' }); if (sessionResult.success) { // User is authenticated, show capture screen await loadCaptureScreen(); } else { // User needs to log in showScreen('login'); } } catch (error) { console.error('Initialization error:', error); showScreen('login'); } } /** * Load the capture screen */ async function loadCaptureScreen() { try { // Get current tab const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); currentTab = tab; // Display URL currentUrl.textContent = tab.url; // Extract metadata from the page try { const results = await chrome.scripting.executeScript({ target: { tabId: tab.id }, files: ['content/metadata-extractor.js'] }); // Send message to content script to extract metadata const metadataResult = await chrome.tabs.sendMessage(tab.id, { action: 'extractMetadata' }); metadata = metadataResult; console.log('Metadata extracted:', metadata); } catch (error) { console.warn('Failed to extract metadata:', error); // Use fallback metadata metadata = { url: tab.url, title: tab.title || tab.url, description: '', imageUrl: '', siteName: new URL(tab.url).hostname, author: '', type: 'website', }; } // Load collections await loadCollections(); // Show capture screen showScreen('capture'); } catch (error) { console.error('Failed to load capture screen:', error); showError('Failed to load page information. Please try again.'); showScreen('login'); } } /** * Load user's collections */ async function loadCollections() { try { const result = await sendMessage({ action: 'getCollections' }); if (result.success) { collections = result.collections; populateCollectionDropdown(); } else { throw new Error(result.error); } } catch (error) { console.error('Failed to load collections:', error); showError('Failed to load collections. Please try again.'); collections = []; populateCollectionDropdown(); } } /** * Populate the collection dropdown */ function populateCollectionDropdown() { collectionSelect.innerHTML = ''; if (collections.length === 0) { const option = document.createElement('option'); option.value = ''; option.textContent = 'No collections found'; collectionSelect.appendChild(option); submitButton.disabled = true; } else { const placeholderOption = document.createElement('option'); placeholderOption.value = ''; placeholderOption.textContent = 'Select a collection...'; collectionSelect.appendChild(placeholderOption); collections.forEach(collection => { const option = document.createElement('option'); // Semble collections have an 'id' field, not uri/cid option.value = collection.id; option.textContent = collection.name || 'Untitled Collection'; collectionSelect.appendChild(option); }); } } /** * Handle collection selection change */ function handleCollectionChange() { submitButton.disabled = !collectionSelect.value; } /** * Handle login */ async function handleLogin() { const handle = loginHandle.value.trim(); const password = loginPassword.value.trim(); if (!handle || !password) { showLoginError('Please enter both handle and password'); return; } loginButton.disabled = true; loginButton.textContent = 'Signing in...'; hideLoginError(); try { const result = await sendMessage({ action: 'authenticate', identifier: handle, password: password, }); if (result.success) { // Authentication successful await loadCaptureScreen(); } else { showLoginError(result.error || 'Authentication failed'); loginButton.disabled = false; loginButton.textContent = 'Sign In'; } } catch (error) { console.error('Login error:', error); showLoginError('An error occurred. Please try again.'); loginButton.disabled = false; loginButton.textContent = 'Sign In'; } } /** * Handle logout */ async function handleLogout() { try { await sendMessage({ action: 'clearSession' }); showScreen('login'); loginHandle.value = ''; loginPassword.value = ''; } catch (error) { console.error('Logout error:', error); } } /** * Handle form submission */ async function handleSubmit() { const note = noteInput.value.trim(); const selectedCollectionId = collectionSelect.value; if (!selectedCollectionId) { showStatus('Please select a collection', 'error'); return; } submitButton.disabled = true; submitButton.textContent = 'Saving...'; showStatus('Saving card to Semble...', 'loading'); try { const result = await sendMessage({ action: 'saveCard', url: currentTab.url, metadata: metadata, note: note || undefined, collectionId: selectedCollectionId, }); if (result.success) { showStatus('Card saved successfully!', 'success'); // Clear form noteInput.value = ''; collectionSelect.value = ''; submitButton.disabled = true; // Close popup after 1.5 seconds setTimeout(() => { window.close(); }, 1500); } else { showStatus(result.error || 'Failed to save card', 'error'); submitButton.disabled = false; submitButton.textContent = 'Add to Collection'; } } catch (error) { console.error('Submit error:', error); showStatus('An error occurred. Please try again.', 'error'); submitButton.disabled = false; submitButton.textContent = 'Add to Collection'; } } /** * Show a screen */ function showScreen(screen) { loginScreen.classList.add('hidden'); captureScreen.classList.add('hidden'); loadingScreen.classList.add('hidden'); switch (screen) { case 'login': loginScreen.classList.remove('hidden'); break; case 'capture': captureScreen.classList.remove('hidden'); break; case 'loading': loadingScreen.classList.remove('hidden'); break; } } /** * Show status message */ function showStatus(message, type) { statusMessage.textContent = message; statusMessage.classList.remove('hidden', 'alert-loading', 'alert-success', 'alert-error'); switch (type) { case 'loading': statusMessage.classList.add('alert-loading'); break; case 'success': statusMessage.classList.add('alert-success'); break; case 'error': statusMessage.classList.add('alert-error'); break; } } /** * Show error message */ function showError(message) { showStatus(message, 'error'); } /** * Show login error */ function showLoginError(message) { loginError.textContent = message; loginError.classList.remove('hidden'); } /** * Hide login error */ function hideLoginError() { loginError.classList.add('hidden'); } /** * Send message to background script */ function sendMessage(message) { return new Promise((resolve, reject) => { chrome.runtime.sendMessage(message, (response) => { if (chrome.runtime.lastError) { reject(chrome.runtime.lastError); } else { resolve(response); } }); }); }