document.addEventListener('DOMContentLoaded', function() { const linksList = document.getElementById('links-list'); const modal = document.getElementById('link-modal'); const deleteModal = document.getElementById('delete-modal'); const form = document.getElementById('link-form'); const searchInput = document.getElementById('search-input'); const queryInput = document.getElementById('query-input'); const simpleBtn = document.getElementById('simple-search-btn'); const queryBtn = document.getElementById('query-search-btn'); const saveCollectionBtn = document.getElementById('save-collection-btn'); const saveCollectionModal = document.getElementById('save-collection-modal'); const saveCollectionForm = document.getElementById('save-collection-form'); let deleteTargetId = null; let searchMode = 'simple'; let lastQuery = ''; let lastResults = []; function setSearchMode(mode) { searchMode = mode; simpleBtn.classList.toggle('active', mode === 'simple'); queryBtn.classList.toggle('active', mode === 'query'); searchInput.style.display = mode === 'simple' ? '' : 'none'; queryInput.style.display = mode === 'query' ? '' : 'none'; searchInput.placeholder = mode === 'simple' ? 'Search links by title, URL, tags, or notes...' : 'e.g. personal AND lan NOT ai'; } simpleBtn.addEventListener('click', () => setSearchMode('simple')); queryBtn.addEventListener('click', () => setSearchMode('query')); async function loadLinks(search = '') { try { let links; if (searchMode === 'query' && search) { lastQuery = search; const data = await LinkSync.previewQuery(search); if (data.error) { linksList.innerHTML = `

Query error: ${escapeHtml(data.error)}

`; lastResults = []; return; } links = data.results || []; } else { lastQuery = ''; links = await LinkSync.getLinks(search ? { search } : {}); } lastResults = Array.isArray(links) ? links : []; renderLinks(lastResults); } catch (err) { linksList.innerHTML = `

Failed to load links: ${err.message}

`; lastResults = []; } } function renderLinks(links) { if (!links || links.length === 0) { linksList.innerHTML = '

No links found.

'; document.getElementById('empty-add-btn').addEventListener('click', openModal); return; } linksList.innerHTML = `
${links.map(link => ` `).join('')}
Title URL Tags Created Actions
${escapeHtml(link.title)} ${escapeHtml(link.url)}
${(link.tags || []).map(t => `${escapeHtml(t)}`).join('')}
${formatDate(link.created_at)}
`; linksList.querySelectorAll('[data-action="edit"]').forEach(btn => { btn.addEventListener('click', () => editLink(btn.dataset.id)); }); linksList.querySelectorAll('[data-action="delete"]').forEach(btn => { btn.addEventListener('click', () => confirmDelete(btn.dataset.id)); }); } function openModal(link = null) { document.getElementById('modal-title').textContent = link ? 'Edit Link' : 'Add Link'; document.getElementById('link-id').value = (link && link.id) ? link.id : ''; document.getElementById('link-url').value = link ? link.url : ''; document.getElementById('link-title').value = link ? link.title : ''; document.getElementById('link-description').value = link ? (link.description || '') : ''; document.getElementById('link-notes').value = link ? (link.notes || '') : ''; document.getElementById('link-tags').value = link ? (link.tags || []).join(', ') : ''; document.getElementById('link-favicon').value = link ? (link.favicon_url || '') : ''; document.getElementById('link-path').value = link ? (link.path || '') : ''; modal.style.display = 'flex'; } async function editLink(id) { try { const links = await LinkSync.getLinks(); const link = (Array.isArray(links) ? links : []).find(l => l.id === id); if (link) openModal(link); } catch (err) { alert('Failed to load link details'); } } function confirmDelete(id) { deleteTargetId = id; deleteModal.style.display = 'flex'; } function closeModal() { modal.style.display = 'none'; form.reset(); } function closeDeleteModal() { deleteModal.style.display = 'none'; deleteTargetId = null; } function openSaveCollectionModal() { const currentSearch = searchMode === 'query' ? queryInput.value.trim() : searchInput.value.trim(); const isQueryMode = searchMode === 'query' && currentSearch; document.getElementById('save-collection-type').value = isQueryMode ? 'dynamic' : 'static'; document.getElementById('save-collection-name').value = isQueryMode ? `Query: ${currentSearch}` : `Search: ${currentSearch}`; document.getElementById('save-collection-desc').value = ''; document.getElementById('save-collection-public').checked = false; saveCollectionModal.style.display = 'flex'; } function closeSaveCollectionModal() { saveCollectionModal.style.display = 'none'; saveCollectionForm.reset(); } document.getElementById('new-link-btn').addEventListener('click', () => openModal()); document.getElementById('modal-close').addEventListener('click', closeModal); document.getElementById('cancel-btn').addEventListener('click', closeModal); document.getElementById('delete-cancel-btn').addEventListener('click', closeDeleteModal); document.getElementById('save-collection-btn').addEventListener('click', openSaveCollectionModal); document.getElementById('save-collection-modal-close').addEventListener('click', closeSaveCollectionModal); document.getElementById('save-collection-cancel-btn').addEventListener('click', closeSaveCollectionModal); modal.querySelector('.modal-overlay').addEventListener('click', closeModal); deleteModal.querySelector('.modal-overlay').addEventListener('click', closeDeleteModal); saveCollectionModal.querySelector('.modal-overlay').addEventListener('click', closeSaveCollectionModal); form.addEventListener('submit', async function(e) { e.preventDefault(); const saveBtn = document.getElementById('save-btn'); saveBtn.disabled = true; saveBtn.textContent = 'Saving...'; const id = document.getElementById('link-id').value; const isEdit = id && id !== '' && id !== 'undefined'; const tagsRaw = document.getElementById('link-tags').value; const data = { url: document.getElementById('link-url').value, title: document.getElementById('link-title').value, description: document.getElementById('link-description').value || null, notes: document.getElementById('link-notes').value || null, tags: tagsRaw ? tagsRaw.split(',').map(t => t.trim()).filter(t => t) : [], favicon_url: document.getElementById('link-favicon').value || null, path: document.getElementById('link-path').value || null, }; try { if (isEdit) { await LinkSync.updateLink(id, data); } else { await LinkSync.createLink(data); } closeModal(); loadLinks(searchMode === 'query' ? queryInput.value : searchInput.value); } catch (err) { alert('Failed to save link: ' + err.message); } finally { saveBtn.disabled = false; saveBtn.textContent = 'Save'; } }); saveCollectionForm.addEventListener('submit', async function(e) { e.preventDefault(); const saveBtn = document.getElementById('save-collection-save-btn'); saveBtn.disabled = true; saveBtn.textContent = 'Saving...'; const name = document.getElementById('save-collection-name').value; const desc = document.getElementById('save-collection-desc').value || null; const type = document.getElementById('save-collection-type').value; const isPublic = document.getElementById('save-collection-public').checked; const data = { name, description: desc, query_type: type, is_public: isPublic, }; if (type === 'dynamic') { const expression = searchMode === 'query' ? queryInput.value.trim() : searchInput.value.trim(); data.query_expression = { expression }; } else { data.link_ids = lastResults.map(l => l.id); } try { await LinkSync.createCollection(data); closeSaveCollectionModal(); alert('Collection saved successfully'); } catch (err) { alert('Failed to save collection: ' + err.message); } finally { saveBtn.disabled = false; saveBtn.textContent = 'Save'; } }); document.getElementById('confirm-delete-btn').addEventListener('click', async function() { if (!deleteTargetId) return; try { await LinkSync.deleteLink(deleteTargetId); closeDeleteModal(); loadLinks(searchMode === 'query' ? queryInput.value : searchInput.value); } catch (err) { alert('Failed to delete link: ' + err.message); } }); function doSearch() { const search = searchMode === 'query' ? queryInput.value : searchInput.value; loadLinks(search); } document.getElementById('search-btn').addEventListener('click', doSearch); searchInput.addEventListener('keypress', function(e) { if (e.key === 'Enter') doSearch(); }); queryInput.addEventListener('keypress', function(e) { if (e.key === 'Enter') doSearch(); }); const urlParams = new URLSearchParams(window.location.search); if (urlParams.get('action') === 'new') { openModal(); } loadLinks(); function escapeHtml(str) { if (!str) return ''; const div = document.createElement('div'); div.textContent = str; return div.innerHTML; } function formatDate(dateStr) { if (!dateStr) return '-'; const d = new Date(dateStr); return d.toLocaleDateString(); } });