diff --git a/frontend/app.js b/frontend/app.js index c0af4ce..23c2546 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -107,7 +107,7 @@ function navigate(view) { // --- Mobile menu --- function updateBreadcrumb(view) { const path = document.getElementById('crumbPath'); - const map = { library: 'protocols/', create: 'protocols/create', collections: 'collections/', 'create-collection': 'collections/create', about: 'about' }; + const map = { library: '', create: 'create', collections: 'collections/', 'create-collection': 'collections/create', about: 'about' }; path.textContent = map[view] || ''; } @@ -815,12 +815,103 @@ async function showUser(username) { '

' + escapeHtml(col.title) + '

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

'; }).join('') + '' : ''); - document.getElementById('detailActions').innerHTML = ''; + // Add actions if viewing own profile + var actionsHtml = ''; + if (currentUser && currentUser.username === username) { + actionsHtml = ''; + if (currentUser.role === 'admin') { + actionsHtml += ''; + actionsHtml += ''; + actionsHtml += ''; + } + } + document.getElementById('detailActions').innerHTML = actionsHtml; } catch (e) { document.getElementById('detailContent').innerHTML = '
User not found
'; } } +// --- Import YAML to form --- +async function importYamlToForm(event) { + var file = event.target.files[0]; + if (!file) return; + event.target.value = ''; + try { + var text = await file.text(); + var result = await api('/import', { method: 'POST', body: JSON.stringify({ yaml: text }) }); + toast('Imported: ' + result.slug + ' — review and save'); + editProtocol(result.slug); + } catch (err) { + toast('Import failed: ' + err.message, true); + } +} + +// --- Bulk import YAML files (admin) --- +async function bulkImportYaml(event) { + var files = event.target.files; + if (!files || !files.length) return; + event.target.value = ''; + var imported = 0; + var failed = 0; + var errors = []; + for (var i = 0; i < files.length; i++) { + try { + var text = await files[i].text(); + await api('/import', { method: 'POST', body: JSON.stringify({ yaml: text }) }); + imported++; + } catch (err) { + failed++; + errors.push(files[i].name + ': ' + err.message); + } + } + var msg = 'Imported ' + imported + ' protocol' + (imported !== 1 ? 's' : ''); + if (failed > 0) { + msg += ', ' + failed + ' failed: ' + errors.join('; '); + toast(msg, true); + } else { + toast(msg); + } + if (currentUser) showUser(currentUser.username); +} + +// --- 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; } + 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) {