Skip to content

Commit 7e4bd84

Browse files
committed
feat: fold hierarchical tags with '/' in Versions tab
When any tag name contains a forward slash (as commonly seen in monorepo setups, e.g. component/v1.0.0), the Versions tab now renders a collapsible tree instead of a flat list. Tags are grouped by the prefix before the first '/', and each group starts collapsed. Groups auto-expand when the currently selected version lives inside them. - Added CSS for .vtree-group-hdr, .vtree-item, .vtree-chevron, etc. - Added _renderVerTree() to build the grouped HTML - Added toggleTreeGroup() to expand/collapse a group - Updated selectVersion() to use data-vidx attribute for highlight (works in both flat and tree modes) Closes #31
1 parent a7949da commit 7e4bd84

1 file changed

Lines changed: 48 additions & 4 deletions

File tree

dfetch_hub/site/index.html

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,13 @@
290290
.vbadge-branch{background:#e8f4f8;color:#1a5276;border:1px solid #aed6f1}
291291
.vtable tbody tr{cursor:pointer}
292292
.vtable tbody tr.ver-selected td{background:rgba(0,113,188,.1)!important}
293+
/* Hierarchical tag tree */
294+
.vtree-group-hdr td{background:var(--bg)!important;cursor:pointer;font-size:.8rem;font-weight:700;padding:.45rem .85rem;color:var(--muted);user-select:none;border-bottom:1px solid var(--border)}
295+
.vtree-group-hdr:hover td{background:var(--border)!important}
296+
.vtree-chevron{display:inline-block;width:1rem;font-size:.7rem}
297+
.vtree-prefix{color:var(--text)}
298+
.vtree-count{margin-left:.35rem;font-size:.72rem;font-weight:400;color:var(--subtle)}
299+
.vtree-item td:first-child{padding-left:1.8rem}
293300
/* Version pill */
294301
.action-card{margin-bottom:.65rem}
295302
.ver-pill{display:flex;align-items:center;gap:.4rem;margin-top:.45rem;padding:.3rem .7rem;background:var(--bg);border:1px solid var(--border);border-radius:var(--rm)}
@@ -848,14 +855,15 @@
848855
allRefsCache=allRefs.slice(0,50);
849856
const firstTag=sortedTags.length?{name:sortedTags[0].name,kind:'tag'}:null;
850857
curVersion=firstTag||{name:c.default_branch||'main',kind:'branch'};
851-
document.getElementById('vtbody').innerHTML=allRefsCache.length?allRefsCache.map((r,i)=>{
858+
const hasHierTags=allRefsCache.some(r=>r.kind==='tag'&&r.name.includes('/'));
859+
document.getElementById('vtbody').innerHTML=allRefsCache.length?(hasHierTags?_renderVerTree(allRefsCache):allRefsCache.map((r,i)=>{
852860
const sel=curVersion&&curVersion.name===r.name&&curVersion.kind===r.kind;
853-
return `<tr class="${sel?'ver-selected':''}" onclick="selectVersion(${i})">
861+
return `<tr class="${sel?'ver-selected':''}" data-vidx="${i}" onclick="selectVersion(${i})">
854862
<td class="vtag-name">${esc(r.name)}${i===0&&r.kind==='tag'?'<span class="vtag-latest">latest</span>':''}</td>
855863
<td><span class="vbadge vbadge-${r.kind}">${r.kind}</span></td>
856864
<td class="vsha">${r.commit_sha?r.commit_sha.slice(0,10):'—'}</td>
857865
<td style="font-size:.79rem;color:var(--subtle)">${r.date?r.date.slice(0,10):'—'}</td>
858-
</tr>`;}).join(''):'<tr><td colspan="4" style="text-align:center;color:var(--subtle)">No version info</td></tr>';
866+
</tr>`;}).join('')):'<tr><td colspan="4" style="text-align:center;color:var(--subtle)">No version info</td></tr>';
859867
// Snippet
860868
document.getElementById('dSnip').innerHTML=buildSnipHtml(c,curVersion);
861869
// Aside
@@ -884,7 +892,7 @@
884892
const r=allRefsCache[i];if(!r)return;
885893
curVersion={name:r.name,kind:r.kind};
886894
_replaceURL({pkg:curPkg,v:r.name,vk:r.kind});
887-
document.querySelectorAll('#vtbody tr').forEach((tr,idx)=>tr.classList.toggle('ver-selected',idx===i));
895+
document.querySelectorAll('#vtbody tr[data-vidx]').forEach(tr=>tr.classList.toggle('ver-selected',+tr.dataset.vidx===i));
888896
const c=curPkg?RAW[curPkg]:null;
889897
if(c)document.getElementById('dSnip').innerHTML=buildSnipHtml(c,curVersion);
890898
updSelVerInfo();
@@ -896,6 +904,42 @@
896904
document.getElementById('selVerKind').textContent=curVersion.kind;
897905
el.style.display='';
898906
}
907+
function _renderVerTree(refs){
908+
// Group tags whose names contain '/' by their prefix (part before first /)
909+
const groups=new Map();
910+
refs.forEach((r,i)=>{
911+
if(r.kind!=='tag'||!r.name.includes('/'))return;
912+
const pfx=r.name.slice(0,r.name.indexOf('/'));
913+
if(!groups.has(pfx))groups.set(pfx,[]);
914+
groups.get(pfx).push(i);
915+
});
916+
let html='';
917+
// Render grouped tags — collapsed by default, open if selected version is inside
918+
groups.forEach((idxs,pfx)=>{
919+
const open=idxs.some(i=>curVersion&&refs[i].name===curVersion.name&&refs[i].kind===curVersion.kind);
920+
html+=`<tr class="vtree-group-hdr" onclick="toggleTreeGroup(this)"><td colspan="4"><span class="vtree-chevron">${open?'▾':'▸'}</span><span class="vtree-prefix">${esc(pfx)}/</span><span class="vtree-count">(${idxs.length})</span></td></tr>`;
921+
idxs.forEach(i=>{
922+
const r=refs[i];const sel=curVersion&&r.name===curVersion.name&&r.kind===curVersion.kind;
923+
const isLatest=i===0;
924+
html+=`<tr class="vtree-item${sel?' ver-selected':''}" data-vidx="${i}" onclick="selectVersion(${i})"${open?'':' style="display:none"'}><td class="vtag-name">${esc(r.name.slice(pfx.length+1))}${isLatest?'<span class="vtag-latest">latest</span>':''}</td><td><span class="vbadge vbadge-tag">tag</span></td><td class="vsha">${r.commit_sha?r.commit_sha.slice(0,10):'—'}</td><td style="font-size:.79rem;color:var(--subtle)">${r.date?r.date.slice(0,10):'—'}</td></tr>`;
925+
});
926+
});
927+
// Render ungrouped tags and branches
928+
refs.forEach((r,i)=>{
929+
if(r.kind==='tag'&&r.name.includes('/'))return;
930+
const sel=curVersion&&r.name===curVersion.name&&r.kind===curVersion.kind;
931+
const isLatest=i===0&&r.kind==='tag';
932+
html+=`<tr class="${sel?'ver-selected':''}" data-vidx="${i}" onclick="selectVersion(${i})"><td class="vtag-name">${esc(r.name)}${isLatest?'<span class="vtag-latest">latest</span>':''}</td><td><span class="vbadge vbadge-${r.kind}">${r.kind}</span></td><td class="vsha">${r.commit_sha?r.commit_sha.slice(0,10):'—'}</td><td style="font-size:.79rem;color:var(--subtle)">${r.date?r.date.slice(0,10):'—'}</td></tr>`;
933+
});
934+
return html||'<tr><td colspan="4" style="text-align:center;color:var(--subtle)">No version info</td></tr>';
935+
}
936+
function toggleTreeGroup(hdr){
937+
const chevron=hdr.querySelector('.vtree-chevron');
938+
const open=chevron&&chevron.textContent==='▾';
939+
if(chevron)chevron.textContent=open?'▸':'▾';
940+
let sib=hdr.nextElementSibling;
941+
while(sib&&sib.classList.contains('vtree-item')){sib.style.display=open?'none':'';sib=sib.nextElementSibling;}
942+
}
899943
function switchTab(name){
900944
document.querySelectorAll('.tabbtn').forEach((b,i)=>{const ns=['readme','versions'];b.classList.toggle('active',ns[i]===name)});
901945
document.querySelectorAll('.tabcontent').forEach(el=>el.classList.remove('active'));

0 commit comments

Comments
 (0)