From dffd90458d8f45e9eca16755188308508361cc3a Mon Sep 17 00:00:00 2001 From: Protocolbot Date: Thu, 9 Jul 2026 09:48:03 -0600 Subject: [PATCH] fix: show protocols/ in breadcrumb on main page for consistency --- frontend/app.js | 163 +++++++++++------------------------------------- 1 file changed, 38 insertions(+), 125 deletions(-) diff --git a/frontend/app.js b/frontend/app.js index bafaba4..c0af4ce 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: '', create: 'create', collections: 'collections/', 'create-collection': 'collections/create', about: 'about' }; + const map = { library: 'protocols/', create: 'protocols/create', collections: 'collections/', 'create-collection': 'collections/create', about: 'about' }; path.textContent = map[view] || ''; } @@ -297,28 +297,16 @@ function renderProtocolGrid(protocols) { grid.innerHTML = '
No protocols yet. Create one
'; return; } - grid.innerHTML = protocols.map(renderProtocolPill).join(''); -} - -// --- Shared rendering (use everywhere for consistency) --- -function renderProtocolPill(p) { - var sourceLabel = p.source || (p.author ? '@' + p.author : ''); - var tags = (p.tags || []).map(function(t) { return '' + escapeHtml(t) + ''; }).join(''); - return '' + - '

' + escapeHtml(p.title) + '

' + - '

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

' + - (tags ? '
' + tags + '
' : '') + '
' + - '
' + (p.steps || []).length + ' steps
' + - (sourceLabel ? '
' + escapeHtml(sourceLabel) + '
' : '') + '
'; -} - -function renderCollectionPill(c) { - var sourceLabel = c.source || (c.author ? '@' + c.author : ''); - return '' + - '

' + escapeHtml(c.title) + '

' + - '

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

' + - '
' + (c.item_count || 0) + ' protocols
' + - (sourceLabel ? '
' + escapeHtml(sourceLabel) + '
' : '') + '
'; + grid.innerHTML = protocols.map(function(p) { + var sourceLabel = p.source || (p.author ? '@' + p.author : ''); + var tags = (p.tags || []).map(function(t) { return '' + escapeHtml(t) + ''; }).join(''); + return '
' + + '

' + escapeHtml(p.title) + '

' + + '

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

' + + (tags ? '
' + tags + '
' : '') + '
' + + '
' + (p.steps || []).length + ' steps
' + + '
' + escapeHtml(sourceLabel) + '
'; + }).join(''); } // (iconFor removed — icons are no longer used) @@ -575,7 +563,12 @@ async function loadCollections() { grid.innerHTML = '
No collections yet. Create one
'; return; } - grid.innerHTML = collections.map(renderCollectionPill).join(''); + grid.innerHTML = collections.map(function(c) { + var sourceLabel = c.author ? '@' + c.author : (c.source || ''); + return '
' + + '

' + escapeHtml(c.title) + '

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

' + + '
' + (c.item_count || 0) + ' protocols
' + escapeHtml(sourceLabel) + '
'; + }).join(''); } catch (e) { document.getElementById('collectionsGrid').innerHTML = '
' + escapeHtml(e.message) + '
'; } @@ -736,7 +729,11 @@ async function showCollectionDetail(slug) { if (item.type === 'protocol') { try { var p = await api('/protocols/' + item.slug); - itemsHtml += renderProtocolPill(p); + itemsHtml += '
' + + '

' + escapeHtml(p.title) + '

' + + '

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

' + + (p.tags && p.tags.length ? '
' + p.tags.map(function(t) { return '' + escapeHtml(t) + ''; }).join('') + '
' : '') + '
' + + '
' + (p.steps || []).length + ' steps
'; } catch (e2) { itemsHtml += '

' + escapeHtml(item.slug) + '

Protocol not found

'; } @@ -803,111 +800,27 @@ async function showUser(username) { '

@' + escapeHtml(data.user.username) + '

' + (data.user.display_name ? '
' + escapeHtml(data.user.display_name) + '
' : '') + (data.user.bio ? '
' + escapeHtml(data.user.bio) + '
' : '') + '
' + - (p.length ? '

Protocols (' + p.length + ')

' + - p.map(renderProtocolPill).join('') + - '
' : '') + - (c.length ? '

Collections (' + c.length + ')

' + - c.map(renderCollectionPill).join('') + - '
' : ''); - // 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; + (p.length ? '

Protocols (' + p.length + ')

' + + p.map(function(proto) { + return '
' + + '

' + escapeHtml(proto.title) + '

' + + '

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

' + + (proto.tags && proto.tags.length ? '
' + proto.tags.map(function(t) { return '' + escapeHtml(t) + ''; }).join('') + '
' : '') + '
' + + '
' + (proto.steps || []).length + ' steps
'; + }).join('') + + '
' : '') + + (c.length ? '

Collections (' + c.length + ')

' + + c.map(function(col) { + return '
[+]
' + + '

' + escapeHtml(col.title) + '

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

'; + }).join('') + + '
' : ''); + document.getElementById('detailActions').innerHTML = ''; } 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 = ''; // Reset for re-use - try { - var text = await file.text(); - var result = await api('/import', { method: 'POST', body: JSON.stringify({ yaml: text }) }); - // Successfully imported — now load it into the form for editing - 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 = ''; // Reset for re-use - 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); - } - // Reload profile to show new protocols - 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) {