diff --git a/frontend/app.js b/frontend/app.js index 671c314..15b11e1 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -57,12 +57,13 @@ async function init() { renderAuth(); // Re-route after auth loads so auth-dependent UI renders correctly - if (location.hash && location.hash !== '#library') { + if (location.hash && location.hash !== '#protocols') { route(); } } function route() { + isRouting = true; const hash = location.hash.slice(1) || 'library'; if (hash.startsWith('protocols/')) { showDetail(hash.split('/')[1]); @@ -81,14 +82,16 @@ function route() { } else { navigate('library'); } + isRouting = false; } let searchTimer = null; let currentRoute = ''; +let isRouting = false; function navigate(view) { // Prevent double navigation from hashchange - if (currentRoute === view) return; + if (currentRoute === view && view !== 'library') return; currentRoute = view; document.querySelectorAll('.view').forEach(v => v.style.display = 'none'); document.getElementById('view-' + view).style.display = 'block'; @@ -96,11 +99,16 @@ function navigate(view) { document.querySelectorAll('.mobile-panel a').forEach(a => a.classList.toggle('active', a.dataset.view === view)); closeMenu(); updateBreadcrumb(view); - if (view === 'library') { location.hash = 'library'; loadProtocols(); } - if (view === 'collections') { location.hash = 'collections'; loadCollections(); } + if (!isRouting) { + if (view === 'library') { location.hash = 'library'; } + if (view === 'collections') { location.hash = 'collections'; } + if (view === 'about') { location.hash = 'about'; } + } + if (view === 'library') loadProtocols(); + if (view === 'collections') loadCollections(); if (view === 'create') { editingSlug = null; resetForm(); } if (view === 'create-collection') { editingCollectionSlug = null; resetCollectionForm(); } - if (view === 'about') { location.hash = 'about'; } + if (view === 'about') {} window.scrollTo(0, 0); } @@ -313,7 +321,7 @@ function renderProtocolGrid(protocols) { // --- Detail --- async function showDetail(slug) { - location.hash = 'protocols/' + slug; + if (!isRouting && location.hash !== '#protocols/' + slug) location.hash = 'protocols/' + slug; document.querySelectorAll('.view').forEach(v => v.style.display = 'none'); document.getElementById('view-detail').style.display = 'block'; document.querySelectorAll('header nav a').forEach(a => a.classList.remove('active')); @@ -713,7 +721,7 @@ async function saveCollection(e) { } async function showCollectionDetail(slug) { - location.hash = 'collections/' + slug; + if (!isRouting && location.hash !== '#collections/' + slug) location.hash = 'collections/' + slug; document.querySelectorAll('.view').forEach(v => v.style.display = 'none'); document.getElementById('view-collection-detail').style.display = 'block'; document.querySelectorAll('header nav a').forEach(a => a.classList.remove('active')); @@ -786,6 +794,7 @@ async function deleteCollection(slug) { // --- User Profile --- async function showUser(username) { + if (!isRouting && location.hash !== '#users/' + username) location.hash = 'users/' + username; document.querySelectorAll('.view').forEach(v => v.style.display = 'none'); document.getElementById('view-detail').style.display = 'block'; document.querySelectorAll('header nav a').forEach(a => a.classList.remove('active')); @@ -815,59 +824,12 @@ async function showUser(username) { '

' + escapeHtml(col.title) + '

' + escapeHtml(col.description||'') + '

'; }).join('') + '' : ''); - // Add actions if viewing own profile - var actionsHtml = ''; - if (currentUser && currentUser.username === username) { - actionsHtml = ''; - if (currentUser.role === 'admin') { - actionsHtml += ''; - } - } - document.getElementById('detailActions').innerHTML = actionsHtml; + document.getElementById('detailActions').innerHTML = ''; } catch (e) { document.getElementById('detailContent').innerHTML = '
User not found
'; } } -// --- Password change --- -function showChangePassword() { - document.getElementById('pwModal').style.display = 'flex'; -} -function closePwModal() { document.getElementById('pwModal').style.display = 'none'; } - -async function handleChangePassword(e) { - e.preventDefault(); - var current = document.getElementById('pwCurrent').value; - var newPassword = document.getElementById('pwNew').value; - var confirmPw = document.getElementById('pwConfirm').value; - if (newPassword !== confirmPw) { toast('Passwords do not match', true); return; } - if (newPassword.length < 8) { toast('Password must be at least 8 characters', true); return; } - try { - await api('/auth/password', { method: 'POST', body: JSON.stringify({ current_password: current, new_password: newPassword }) }); - toast('Password changed'); - closePwModal(); - document.getElementById('pwForm').reset(); - } catch (err) { - toast(err.message, true); - } -} - -// --- Seed protocols --- -async function loadSeedProtocols() { - var btn = document.getElementById('seedBtn'); - if (btn) { btn.disabled = true; btn.textContent = 'Loading...'; } - try { - var result = await api('/seed', { method: 'POST' }); - toast('Loaded ' + result.imported + ' seed protocols'); - if (btn) { btn.textContent = 'Load Seed Protocols'; btn.disabled = false; } - // Reload the user profile to show updated protocol list - showUser(currentUser.username); - } catch (e) { - toast(e.message, true); - if (btn) { btn.textContent = 'Load Seed Protocols'; btn.disabled = false; } - } -} - // --- Toast --- let toastTimer = null; function toast(msg, isError) {