fix: YAML folded scalar parser, delete redirect, #protocols routing, forked_from null
- Fix YAML fallback parser to properly handle folded scalars (>-) at
both top-level (description, outcome, practice) and step-level
(step descriptions). Previously stored '>-' as the literal value
instead of the folded text.
- Fix delete protocol/collection: reset currentRoute guard so
navigate('library') actually works after deletion.
- Change #library hash to #protocols throughout (routing, nav links,
breadcrumb shows 'protocols/' on the main page).
- Fix breadcrumb home click not working from detail pages (allow
re-navigation to library even if currentRoute matches).
- Fix forked_from: null showing as a clickable link — now suppressed
when value is null or the string 'null'.
- README: replace curl seed instructions with About page button,
add About page customization instructions for whitelabeling.
This commit is contained in:
+15
-31
@@ -63,19 +63,21 @@ async function init() {
|
||||
}
|
||||
|
||||
function route() {
|
||||
const hash = location.hash.slice(1) || 'library';
|
||||
if (hash.startsWith('protocols/')) {
|
||||
const hash = location.hash.slice(1) || 'protocols';
|
||||
if (hash.startsWith('protocols/') && hash !== 'protocols') {
|
||||
showDetail(hash.split('/')[1]);
|
||||
} else if (hash === 'protocols') {
|
||||
navigate('library');
|
||||
} else if (hash.startsWith('users/') || hash.startsWith('user/')) {
|
||||
showUser(hash.split('/')[1]);
|
||||
} else if (hash.startsWith('collections/')) {
|
||||
} else if (hash.startsWith('collections/') && hash !== 'collections') {
|
||||
showCollectionDetail(hash.split('/')[1]);
|
||||
} else if (hash === 'collections') {
|
||||
navigate('collections');
|
||||
} else if (hash === 'create') {
|
||||
navigate('create');
|
||||
} else if (hash === 'create-collection') {
|
||||
navigate('create-collection');
|
||||
} else if (hash === 'collections') {
|
||||
navigate('collections');
|
||||
} else if (hash === 'about') {
|
||||
navigate('about');
|
||||
} else {
|
||||
@@ -87,8 +89,8 @@ let searchTimer = null;
|
||||
let currentRoute = '';
|
||||
|
||||
function navigate(view) {
|
||||
// Prevent double navigation from hashchange
|
||||
if (currentRoute === view) return;
|
||||
// Prevent double navigation from hashchange (but allow explicit resets)
|
||||
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,18 +98,18 @@ 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 === 'library') { location.hash = 'protocols'; loadProtocols(); }
|
||||
if (view === 'collections') { location.hash = 'collections'; loadCollections(); }
|
||||
if (view === 'create') { editingSlug = null; resetForm(); }
|
||||
if (view === 'create-collection') { editingCollectionSlug = null; resetCollectionForm(); }
|
||||
if (view === 'about') { location.hash = 'about'; updateAboutPage(); }
|
||||
if (view === 'about') { location.hash = 'about'; }
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
|
||||
// --- 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] || '';
|
||||
}
|
||||
|
||||
@@ -337,7 +339,7 @@ function renderDetail(p) {
|
||||
return '<div class="step"><div class="number">' + String(i+1).padStart(2,'0') + '</div>' +
|
||||
'<div class="content"><h4>' + escapeHtml(s.headline) + '</h4><p>' + escapeHtml(s.description || '') + '</p></div></div>';
|
||||
}).join('');
|
||||
const forkNotice = p.forked_from ? '<div class="fork-notice">forked from: <a href="#protocol/' + p.forked_from + '" onclick="showDetail(\'' + p.forked_from + '\');return false">' + p.forked_from + '</a></div>' : '';
|
||||
const forkNotice = (p.forked_from && p.forked_from !== 'null') ? '<div class="fork-notice">forked from: <a href="#protocols/' + p.forked_from + '" onclick="showDetail(\'' + p.forked_from + '\');return false">' + p.forked_from + '</a></div>' : '';
|
||||
const sourceLine = p.source ? 'SOURCE: ' + escapeHtml(p.source) : '';
|
||||
var sourceLink = p.source_url ? ' · <a href="' + escapeHtml(p.source_url) + '" target="_blank">view_original</a>' : '';
|
||||
var authorLine = p.author ? ' · authored by <a href="#user/' + p.author + '" onclick="showUser(\'' + p.author + '\');return false">@' + escapeHtml(p.author) + '</a>' : '';
|
||||
@@ -410,6 +412,7 @@ async function deleteProtocol(slug) {
|
||||
try {
|
||||
await api('/protocols/' + slug, { method: 'DELETE' });
|
||||
toast('Protocol deleted');
|
||||
currentRoute = ''; // Reset so navigate works
|
||||
navigate('library');
|
||||
} catch (e) { toast(e.message, true); }
|
||||
}
|
||||
@@ -780,6 +783,7 @@ async function deleteCollection(slug) {
|
||||
try {
|
||||
await api('/collections/' + slug, { method: 'DELETE' });
|
||||
toast('Collection deleted');
|
||||
currentRoute = '';
|
||||
navigate('collections');
|
||||
} catch (e) { toast(e.message, true); }
|
||||
}
|
||||
@@ -821,26 +825,6 @@ async function showUser(username) {
|
||||
}
|
||||
}
|
||||
|
||||
// --- About page ---
|
||||
function updateAboutPage() {
|
||||
var section = document.getElementById('seedSection');
|
||||
if (!section) return;
|
||||
section.style.display = (currentUser && currentUser.role === 'admin') ? 'block' : 'none';
|
||||
}
|
||||
|
||||
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 = 'Loaded ' + result.imported + ' protocols'; btn.disabled = true; }
|
||||
} catch (e) {
|
||||
toast(e.message, true);
|
||||
if (btn) { btn.disabled = false; btn.textContent = 'Load seed protocols'; }
|
||||
}
|
||||
}
|
||||
|
||||
// --- Toast ---
|
||||
let toastTimer = null;
|
||||
function toast(msg, isError) {
|
||||
|
||||
Executable → Regular
+2
-9
@@ -15,7 +15,7 @@
|
||||
<span class="crumb-path" id="crumbPath"></span>
|
||||
</div>
|
||||
<nav id="navBar">
|
||||
<a href="#library" class="active" data-view="library">PROTOCOLS</a>
|
||||
<a href="#protocols" class="active" data-view="library">PROTOCOLS</a>
|
||||
<a href="#collections" data-view="collections">COLLECTIONS</a>
|
||||
<a href="#about" data-view="about">ABOUT</a>
|
||||
</nav>
|
||||
@@ -24,7 +24,7 @@
|
||||
</header>
|
||||
|
||||
<div class="mobile-panel" id="mobilePanel">
|
||||
<a href="#library" data-view="library" class="active" onclick="mobileNav('library')">PROTOCOLS</a>
|
||||
<a href="#protocols" data-view="library" class="active" onclick="mobileNav('library')">PROTOCOLS</a>
|
||||
<a href="#collections" data-view="collections" onclick="mobileNav('collections')">COLLECTIONS</a>
|
||||
<a href="#about" data-view="about" onclick="mobileNav('about')">ABOUT</a>
|
||||
<div class="mobile-auth" id="mobileAuth"></div>
|
||||
@@ -87,13 +87,6 @@
|
||||
<h2>License</h2>
|
||||
<p class="outcome">Hippocratic License (HL3-CORE) — do no harm. See <a href="https://firstdonoharm.dev/" target="_blank">firstdonoharm.dev</a>. Source code available on <a href="https://git.medlab.host/ntnsndr/protocol-droid" target="_blank">Gitea</a>.</p>
|
||||
</div>
|
||||
<div class="detail-section" id="seedSection" style="display:none">
|
||||
<h2>Seed protocols</h2>
|
||||
<p class="outcome">Load a starter set of 6 example protocols (Round Robin Check-In, Consent Decision-Making, Appreciative Apology, Temperature Reading, Dot Voting, Fishbowl Discussion) into your library.</p>
|
||||
<div class="actions">
|
||||
<button class="btn primary" id="seedBtn" onclick="loadSeedProtocols()">Load seed protocols</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- PROTOCOL DETAIL VIEW -->
|
||||
|
||||
Reference in New Issue
Block a user