fix: ctrl+click, shared pill rendering, header layout — all in one
Three recurring issues fixed together: 1. Ctrl+click: All protocol and collection pills now use <a href> instead of <div onclick>, so ctrl/cmd+click opens in new tab. text-decoration:none and color:inherit prevent link styling. 2. Consistent modules: Shared renderProtocolPill() and renderCollectionPill() functions used everywhere — library, collections list, collection detail, user profile. All produce identical HTML with .protocol-grid wrapper for consistent spacing. Profile collections no longer have .icon divs. 3. Header layout: Logo (32px) on left with crumb-text wrapper. $ protocol-droid on first line, crumb path below it. Favicon links and PWA manifest restored. Relative paths (frontend/ not /frontend/) for subdirectory support.
This commit is contained in:
+24
-30
@@ -297,16 +297,28 @@ function renderProtocolGrid(protocols) {
|
|||||||
grid.innerHTML = '<div class="empty">No protocols yet. <a href="#create" onclick="navigate(\'create\');return false">Create one</a></div>';
|
grid.innerHTML = '<div class="empty">No protocols yet. <a href="#create" onclick="navigate(\'create\');return false">Create one</a></div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
grid.innerHTML = protocols.map(function(p) {
|
grid.innerHTML = protocols.map(renderProtocolPill).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Shared rendering (use everywhere for consistency) ---
|
||||||
|
function renderProtocolPill(p) {
|
||||||
var sourceLabel = p.source || (p.author ? '@' + p.author : '');
|
var sourceLabel = p.source || (p.author ? '@' + p.author : '');
|
||||||
var tags = (p.tags || []).map(function(t) { return '<span class="tag">' + escapeHtml(t) + '</span>'; }).join('');
|
var tags = (p.tags || []).map(function(t) { return '<span class="tag">' + escapeHtml(t) + '</span>'; }).join('');
|
||||||
return '<div class="protocol-pill" onclick="showDetail(\'' + p.slug + '\')">' +
|
return '<a class="protocol-pill" href="#protocols/' + p.slug + '">' +
|
||||||
'<div class="info"><h3>' + escapeHtml(p.title) + '</h3>' +
|
'<div class="info"><h3>' + escapeHtml(p.title) + '</h3>' +
|
||||||
'<p>' + escapeHtml(p.description || '') + '</p>' +
|
'<p>' + escapeHtml(p.description || '') + '</p>' +
|
||||||
(tags ? '<div class="tag-row">' + tags + '</div>' : '') + '</div>' +
|
(tags ? '<div class="tag-row">' + tags + '</div>' : '') + '</div>' +
|
||||||
'<div class="meta"><div class="steps">' + (p.steps || []).length + ' steps</div>' +
|
'<div class="meta"><div class="steps">' + (p.steps || []).length + ' steps</div>' +
|
||||||
'<div>' + escapeHtml(sourceLabel) + '</div></div></div>';
|
(sourceLabel ? '<div>' + escapeHtml(sourceLabel) + '</div>' : '') + '</div></a>';
|
||||||
}).join('');
|
}
|
||||||
|
|
||||||
|
function renderCollectionPill(c) {
|
||||||
|
var sourceLabel = c.source || (c.author ? '@' + c.author : '');
|
||||||
|
return '<a class="protocol-pill collection" href="#collections/' + c.slug + '">' +
|
||||||
|
'<div class="info"><h3>' + escapeHtml(c.title) + '</h3>' +
|
||||||
|
'<p>' + escapeHtml(c.description || '') + '</p></div>' +
|
||||||
|
'<div class="meta"><div class="steps">' + (c.item_count || 0) + ' protocols</div>' +
|
||||||
|
(sourceLabel ? '<div>' + escapeHtml(sourceLabel) + '</div>' : '') + '</div></a>';
|
||||||
}
|
}
|
||||||
|
|
||||||
// (iconFor removed — icons are no longer used)
|
// (iconFor removed — icons are no longer used)
|
||||||
@@ -563,12 +575,7 @@ async function loadCollections() {
|
|||||||
grid.innerHTML = '<div class="empty">No collections yet. <a href="#" onclick="navigate(\'create-collection\');return false">Create one</a></div>';
|
grid.innerHTML = '<div class="empty">No collections yet. <a href="#" onclick="navigate(\'create-collection\');return false">Create one</a></div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
grid.innerHTML = collections.map(function(c) {
|
grid.innerHTML = collections.map(renderCollectionPill).join('');
|
||||||
var sourceLabel = c.author ? '@' + c.author : (c.source || '');
|
|
||||||
return '<div class="protocol-pill collection" onclick="showCollectionDetail(\'' + c.slug + '\')">' +
|
|
||||||
'<div class="info"><h3>' + escapeHtml(c.title) + '</h3><p>' + escapeHtml(c.description || '') + '</p></div>' +
|
|
||||||
'<div class="meta"><div class="steps">' + (c.item_count || 0) + ' protocols</div><div>' + escapeHtml(sourceLabel) + '</div></div></div>';
|
|
||||||
}).join('');
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
document.getElementById('collectionsGrid').innerHTML = '<div class="empty">' + escapeHtml(e.message) + '</div>';
|
document.getElementById('collectionsGrid').innerHTML = '<div class="empty">' + escapeHtml(e.message) + '</div>';
|
||||||
}
|
}
|
||||||
@@ -729,11 +736,7 @@ async function showCollectionDetail(slug) {
|
|||||||
if (item.type === 'protocol') {
|
if (item.type === 'protocol') {
|
||||||
try {
|
try {
|
||||||
var p = await api('/protocols/' + item.slug);
|
var p = await api('/protocols/' + item.slug);
|
||||||
itemsHtml += '<div class="protocol-pill" onclick="showDetail(\'' + p.slug + '\')">' +
|
itemsHtml += renderProtocolPill(p);
|
||||||
'<div class="info"><h3>' + escapeHtml(p.title) + '</h3>' +
|
|
||||||
'<p>' + escapeHtml(p.description || '') + '</p>' +
|
|
||||||
(p.tags && p.tags.length ? '<div class="tag-row">' + p.tags.map(function(t) { return '<span class="tag">' + escapeHtml(t) + '</span>'; }).join('') + '</div>' : '') + '</div>' +
|
|
||||||
'<div class="meta"><div class="steps">' + (p.steps || []).length + ' steps</div></div></div>';
|
|
||||||
} catch (e2) {
|
} catch (e2) {
|
||||||
itemsHtml += '<div class="protocol-pill"><div class="info"><h3>' + escapeHtml(item.slug) + '</h3><p>Protocol not found</p></div></div>';
|
itemsHtml += '<div class="protocol-pill"><div class="info"><h3>' + escapeHtml(item.slug) + '</h3><p>Protocol not found</p></div></div>';
|
||||||
}
|
}
|
||||||
@@ -800,21 +803,12 @@ async function showUser(username) {
|
|||||||
'<div class="profile-card"><h2>@' + escapeHtml(data.user.username) + '</h2>' +
|
'<div class="profile-card"><h2>@' + escapeHtml(data.user.username) + '</h2>' +
|
||||||
(data.user.display_name ? '<div class="bio">' + escapeHtml(data.user.display_name) + '</div>' : '') +
|
(data.user.display_name ? '<div class="bio">' + escapeHtml(data.user.display_name) + '</div>' : '') +
|
||||||
(data.user.bio ? '<div class="bio">' + escapeHtml(data.user.bio) + '</div>' : '') + '</div>' +
|
(data.user.bio ? '<div class="bio">' + escapeHtml(data.user.bio) + '</div>' : '') + '</div>' +
|
||||||
(p.length ? '<div class="detail-section"><h2>Protocols (' + p.length + ')</h2>' +
|
(p.length ? '<div class="detail-section"><h2>Protocols (' + p.length + ')</h2><div class="protocol-grid">' +
|
||||||
p.map(function(proto) {
|
p.map(renderProtocolPill).join('') +
|
||||||
return '<div class="protocol-pill" onclick="showDetail(\'' + proto.slug + '\')">' +
|
'</div></div>' : '') +
|
||||||
'<div class="info"><h3>' + escapeHtml(proto.title) + '</h3>' +
|
(c.length ? '<div class="detail-section"><h2>Collections (' + c.length + ')</h2><div class="protocol-grid">' +
|
||||||
'<p>' + escapeHtml(proto.description||'') + '</p>' +
|
c.map(renderCollectionPill).join('') +
|
||||||
(proto.tags && proto.tags.length ? '<div class="tag-row">' + proto.tags.map(function(t) { return '<span class="tag">' + escapeHtml(t) + '</span>'; }).join('') + '</div>' : '') + '</div>' +
|
'</div></div>' : '');
|
||||||
'<div class="meta"><div class="steps">' + (proto.steps || []).length + ' steps</div></div></div>';
|
|
||||||
}).join('') +
|
|
||||||
'</div>' : '') +
|
|
||||||
(c.length ? '<div class="detail-section"><h2>Collections (' + c.length + ')</h2>' +
|
|
||||||
c.map(function(col) {
|
|
||||||
return '<div class="protocol-pill collection" onclick="showCollectionDetail(\'' + col.slug + '\')"><div class="icon">[+]</div>' +
|
|
||||||
'<div class="info"><h3>' + escapeHtml(col.title) + '</h3><p>' + escapeHtml(col.description||'') + '</p></div></div>';
|
|
||||||
}).join('') +
|
|
||||||
'</div>' : '');
|
|
||||||
// Add actions if viewing own profile
|
// Add actions if viewing own profile
|
||||||
var actionsHtml = '';
|
var actionsHtml = '';
|
||||||
if (currentUser && currentUser.username === username) {
|
if (currentUser && currentUser.username === username) {
|
||||||
|
|||||||
+9
-2
@@ -4,16 +4,23 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Protocol Droid</title>
|
<title>Protocol Droid</title>
|
||||||
<link rel="stylesheet" href="/frontend/style.css">
|
<link rel="icon" type="image/svg+xml" href="frontend/logo.svg">
|
||||||
|
<link rel="icon" type="image/x-icon" href="frontend/favicon.ico">
|
||||||
|
<link rel="apple-touch-icon" href="frontend/icon-192.png">
|
||||||
|
<link rel="manifest" href="manifest.json">
|
||||||
|
<link rel="stylesheet" href="frontend/style.css">
|
||||||
<meta name="theme-color" content="#0a1929">
|
<meta name="theme-color" content="#0a1929">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
<div class="breadcrumb" onclick="navigate('library')">
|
<div class="breadcrumb" onclick="navigate('library')">
|
||||||
|
<img src="frontend/logo.svg" class="header-logo" alt="">
|
||||||
|
<div class="crumb-text">
|
||||||
<span><span class="prompt">$</span> protocol-droid</span>
|
<span><span class="prompt">$</span> protocol-droid</span>
|
||||||
<span class="crumb-path" id="crumbPath"></span>
|
<span class="crumb-path" id="crumbPath"></span>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<nav id="navBar">
|
<nav id="navBar">
|
||||||
<a href="#library" class="active" data-view="library">PROTOCOLS</a>
|
<a href="#library" class="active" data-view="library">PROTOCOLS</a>
|
||||||
<a href="#collections" data-view="collections">COLLECTIONS</a>
|
<a href="#collections" data-view="collections">COLLECTIONS</a>
|
||||||
@@ -259,6 +266,6 @@
|
|||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script src="/frontend/app.js"></script>
|
<script src="frontend/app.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
+2
-1
@@ -238,8 +238,9 @@ header nav a.active { color: var(--accent-bright); border-color: rgba(0,170,255,
|
|||||||
background: var(--surface); border: 1px solid var(--border-light);
|
background: var(--surface); border: 1px solid var(--border-light);
|
||||||
border-radius: 16px; padding: 14px 20px; display: flex; align-items: flex-start;
|
border-radius: 16px; padding: 14px 20px; display: flex; align-items: flex-start;
|
||||||
gap: 14px; cursor: pointer; transition: all 0.15s; box-shadow: var(--shadow);
|
gap: 14px; cursor: pointer; transition: all 0.15s; box-shadow: var(--shadow);
|
||||||
|
text-decoration: none; color: inherit;
|
||||||
}
|
}
|
||||||
.protocol-pill:hover { border-color: var(--accent); box-shadow: var(--shadow-hover); transform: translateX(2px); }
|
.protocol-pill:hover { border-color: var(--accent); box-shadow: var(--shadow-hover); transform: translateX(2px); text-decoration: none; }
|
||||||
.protocol-pill .icon {
|
.protocol-pill .icon {
|
||||||
width: 38px; height: 38px; border-radius: 10px; background: var(--panel);
|
width: 38px; height: 38px; border-radius: 10px; background: var(--panel);
|
||||||
display: flex; align-items: center; justify-content: center;
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
|||||||
Reference in New Issue
Block a user