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) {