diff --git a/dfetch_hub/site/index.html b/dfetch_hub/site/index.html
index deef0a6..d5e1f82 100644
--- a/dfetch_hub/site/index.html
+++ b/dfetch_hub/site/index.html
@@ -290,6 +290,22 @@
.vbadge-branch{background:#e8f4f8;color:#1a5276;border:1px solid #aed6f1}
.vtable tbody tr{cursor:pointer}
.vtable tbody tr.ver-selected td{background:rgba(0,113,188,.1)!important}
+ /* Hierarchical tag tree */
+ .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)
+ }
+ .vtree-group-hdr:hover td{background:var(--border)!important}
+ .vtree-chevron{display:inline-block;width:1rem;font-size:.7rem}
+ .vtree-prefix{color:var(--text)}
+ .vtree-count{margin-left:.35rem;font-size:.72rem;font-weight:400;color:var(--subtle)}
+ .vtree-item td:first-child{padding-left:1.8rem}
/* Version pill */
.action-card{margin-bottom:.65rem}
.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 +864,15 @@
allRefsCache=allRefs.slice(0,50);
const firstTag=sortedTags.length?{name:sortedTags[0].name,kind:'tag'}:null;
curVersion=firstTag||{name:c.default_branch||'main',kind:'branch'};
- document.getElementById('vtbody').innerHTML=allRefsCache.length?allRefsCache.map((r,i)=>{
+ const hasHierTags=allRefsCache.some(r=>r.kind==='tag'&&r.name.includes('/'));
+ document.getElementById('vtbody').innerHTML=allRefsCache.length?(hasHierTags?_renderVerTree(allRefsCache):allRefsCache.map((r,i)=>{
const sel=curVersion&&curVersion.name===r.name&&curVersion.kind===r.kind;
- return `
+ return `
| ${esc(r.name)}${i===0&&r.kind==='tag'?'latest':''} |
${r.kind} |
${r.commit_sha?r.commit_sha.slice(0,10):'—'} |
${r.date?r.date.slice(0,10):'—'} |
-
`;}).join(''):'| No version info |
';
+ `;}).join('')):'| No version info |
';
// Snippet
document.getElementById('dSnip').innerHTML=buildSnipHtml(c,curVersion);
// Aside
@@ -884,7 +901,7 @@
const r=allRefsCache[i];if(!r)return;
curVersion={name:r.name,kind:r.kind};
_replaceURL({pkg:curPkg,v:r.name,vk:r.kind});
- document.querySelectorAll('#vtbody tr').forEach((tr,idx)=>tr.classList.toggle('ver-selected',idx===i));
+ document.querySelectorAll('#vtbody tr[data-vidx]').forEach(tr=>tr.classList.toggle('ver-selected',+tr.dataset.vidx===i));
const c=curPkg?RAW[curPkg]:null;
if(c)document.getElementById('dSnip').innerHTML=buildSnipHtml(c,curVersion);
updSelVerInfo();
@@ -896,6 +913,42 @@
document.getElementById('selVerKind').textContent=curVersion.kind;
el.style.display='';
}
+function _renderVerTree(refs){
+ // Group tags whose names contain '/' by their prefix (part before first /)
+ const groups=new Map();
+ refs.forEach((r,i)=>{
+ if(r.kind!=='tag'||!r.name.includes('/'))return;
+ const pfx=r.name.slice(0,r.name.indexOf('/'));
+ if(!groups.has(pfx))groups.set(pfx,[]);
+ groups.get(pfx).push(i);
+ });
+ let html='';
+ // Render grouped tags — collapsed by default, open if selected version is inside
+ groups.forEach((idxs,pfx)=>{
+ const open=idxs.some(i=>curVersion&&refs[i].name===curVersion.name&&refs[i].kind===curVersion.kind);
+ html+=`| ${open?'▾':'▸'}${esc(pfx)}/(${idxs.length}) |
`;
+ idxs.forEach(i=>{
+ const r=refs[i];const sel=curVersion&&r.name===curVersion.name&&r.kind===curVersion.kind;
+ const isLatest=i===0;
+ html+=`| ${esc(r.name.slice(pfx.length+1))}${isLatest?'latest':''} | tag | ${r.commit_sha?r.commit_sha.slice(0,10):'—'} | ${r.date?r.date.slice(0,10):'—'} |
`;
+ });
+ });
+ // Render ungrouped tags and branches
+ refs.forEach((r,i)=>{
+ if(r.kind==='tag'&&r.name.includes('/'))return;
+ const sel=curVersion&&r.name===curVersion.name&&r.kind===curVersion.kind;
+ const isLatest=i===0&&r.kind==='tag';
+ html+=`| ${esc(r.name)}${isLatest?'latest':''} | ${r.kind} | ${r.commit_sha?r.commit_sha.slice(0,10):'—'} | ${r.date?r.date.slice(0,10):'—'} |
`;
+ });
+ return html||'| No version info |
';
+}
+function toggleTreeGroup(hdr){
+ const chevron=hdr.querySelector('.vtree-chevron');
+ const open=chevron&&chevron.textContent==='▾';
+ if(chevron)chevron.textContent=open?'▸':'▾';
+ let sib=hdr.nextElementSibling;
+ while(sib&&sib.classList.contains('vtree-item')){sib.style.display=open?'none':'';sib=sib.nextElementSibling;}
+}
function switchTab(name){
document.querySelectorAll('.tabbtn').forEach((b,i)=>{const ns=['readme','versions'];b.classList.toggle('active',ns[i]===name)});
document.querySelectorAll('.tabcontent').forEach(el=>el.classList.remove('active'));