@@ -776,6 +864,12 @@
Found in
let curVersion = null; // version selected in current detail view
let DST_PREFIX = localStorage.getItem('dstPrefix') ?? ''; // configurable destination prefix for generated manifests
let allRefsCache = []; // refs for the current detail's version table
+let CUR_FILES = null; // {path:[{name,type,sha,ignored?}]}
+let CUR_FILES_PATH = []; // current breadcrumb path stack
+let CUR_FILES_CACHED_PKG = null; // package ID for cached files
+let CUR_FILES_CACHED_VERSION = null; // version for cached files
+let MFS_IGNORES = {}; // id ā Set of ignored paths (relative to repo root)
+let GH_TOKEN = localStorage.getItem('ghToken') || ''; // GitHub personal access token
let acIdx = -1;
let _skipNav = false;
function _pushURL(p){if(_skipNav)return;const qs=new URLSearchParams(p).toString();history.pushState(p,'',qs?'?'+qs:location.pathname)}
@@ -824,6 +918,7 @@
Found in
// Call on page load
loadCatalog();
+updGhTokenStatus();
document.getElementById('hacdrop').addEventListener('mousedown',e=>{const el=e.target.closest('.acitem');if(el)selHAC(el.dataset.id);});
document.getElementById('acdrop').addEventListener('mousedown',e=>{const el=e.target.closest('.acitem');if(el)selAC(el.dataset.id);});
@@ -866,6 +961,9 @@
Found in
function showHome(){_pushURL({});showPage('page-home');document.getElementById('hInput').value='';document.getElementById('nInput').value='';document.getElementById('mobileSearch').querySelector('input').value='';curQ=''}
function showResults(){showPage('page-results');if(window.innerWidth<=600){const msi=document.getElementById('mobileSearch').querySelector('input');if(msi.value){msi.setSelectionRange(msi.value.length,msi.value.length);}}}
function _semverCmp(a,b){
+ const hasDateA=a.date&&a.date.trim(),hasDateB=b.date&&b.date.trim();
+ if(hasDateA&&hasDateB){const da=new Date(a.date),db=new Date(b.date);if(da!==db)return db.getTime()-da.getTime();}
+ else if(hasDateA)return -1;else if(hasDateB)return 1;
const p=s=>{const m=s.name.replace(/^v/i,'').match(/^(\d+)(?:\.(\d+))?(?:\.(\d+))?/);return m?[+m[1],+(m[2]||0),+(m[3]||0)]:null};
const[av,bv]=[p(a),p(b)];
if(!av&&!bv)return 0;if(!av)return 1;if(!bv)return -1;
@@ -874,15 +972,36 @@
Found in
}
async function showDetail(id){
const c=RAW[id];if(!c)return;
- _pushURL({pkg:id});curPkg=id;showPage('page-detail');window.scrollTo(0,0);
+ _pushURL({pkg:id});curPkg=id;CUR_FILES=null;CUR_FILES_CACHED_PKG=null;CUR_FILES_CACHED_VERSION=null;showPage('page-detail');window.scrollTo(0,0);
if(!c._detailLoaded){
document.getElementById('readmeBody').innerHTML='
Loadingā¦
';
document.getElementById('vtbody').innerHTML='
| Loading⦠|
';
try{const resp=await fetch(`../data/${id}.json`);if(resp.ok){const d=await resp.json();Object.assign(RAW[id],d);}}catch(e){}
RAW[id]._detailLoaded=true;
+ const isGh=c.source_type==='github'||(c.source_labels||[]).includes('github');
+ if(isGh){await fetchGhData(c);}
}
renderDetail(RAW[id]);
}
+async function fetchGhData(c){
+ let org=c.org||'';let repo=c.repo||c.name||'';
+ const m=(c.url||'').match(/github\.com[\/:]([^\/]+)\/([^\/\s]+)/);
+ if(!m||!m[1]||!m[2])return;
+ org=m[1].toLowerCase();repo=m[2].replace(/\.git$/,'').toLowerCase();
+ const opts=ghFetchOpts();
+ try{
+ const [readmeResp,tagsResp,contentsResp,repoResp]=await Promise.all([
+ fetch(`https://api.github.com/repos/${org}/${repo}/readme`,opts),
+ fetch(`https://api.github.com/repos/${org}/${repo}/tags?per_page=100`,opts),
+ fetch(`https://api.github.com/repos/${org}/${repo}/contents/`,opts),
+ fetch(`https://api.github.com/repos/${org}/${repo}`,opts)
+ ]);
+ if(readmeResp.ok){try{const d=await readmeResp.json();if(d.content){const txt=atob(d.content);c.readme=txt;}}catch(e){}}
+ if(tagsResp.ok){try{const tags=await tagsResp.json();if(Array.isArray(tags)){const tagList=tags.slice(0,50);const commitResps=await Promise.allSettled(tagList.map(t=>fetch(`https://api.github.com/repos/${org}/${repo}/commits/${t.commit.sha}`,opts).then(r=>r.ok?r.json():null)));tagList.forEach((t,i)=>{if(commitResps[i].status==='fulfilled'&&commitResps[i].value?.commit?.committer?.date)t.date=commitResps[i].value.commit.committer.date;});c.tags=[...new Map(tagList.map(t=>({name:t.name,commit_sha:t.commit?.sha||'',date:t.date||'',...(t.commit?.url?{commit_url:t.commit.url}:{})})).map(t=>[t.name,t])).values()];}}catch(e){}}
+ if(contentsResp.ok){try{const items=await contentsResp.json();if(Array.isArray(items)){const changelogFile=items.find(f=>f.type==='file'&&/^(changelog|history)/i.test(f.name));if(changelogFile&&changelogFile.download_url){const clResp=await fetch(changelogFile.download_url,opts);if(clResp.ok)c.changelog=await clResp.text()}}}catch(e){}}
+ if(repoResp.ok){try{const repo=await repoResp.json();if(repo.default_branch)c.default_branch=repo.default_branch;}catch(e){}}
+ }catch(e){}
+}
function score(c,q){
if(!q)return 1;
@@ -1109,6 +1228,9 @@
Found in
function selectVersion(i){
const r=allRefsCache[i];if(!r)return;
curVersion={name:r.name,kind:r.kind};
+ CUR_FILES=null;
+ CUR_FILES_CACHED_PKG=null;
+ CUR_FILES_CACHED_VERSION=null;
_replaceURL({pkg:curPkg,v:r.name,vk:r.kind});
document.querySelectorAll('#vtbody tr[data-vidx]').forEach(tr=>tr.classList.toggle('ver-selected',+tr.dataset.vidx===i));
const selRow=document.querySelector(`#vtbody tr[data-vidx="${i}"]`);
@@ -1172,10 +1294,146 @@
Found in
let sib=hdr.nextElementSibling;
while(sib&&sib.classList.contains('vtree-item')){sib.style.display=open?'none':'';sib=sib.nextElementSibling;}
}
+
+function iconFolder(){return'
';}
+function iconFile(){return'
';}
+function iconParent(){return'
';}
+async function loadFilesIfNeeded(){
+ const c=curPkg?RAW[curPkg]:null;if(!c)return;
+ const fb=document.getElementById('filesBody');if(!fb)return;
+ const isGh=c.source_type==='github'||(c.source_labels||[]).includes('github');
+ if(!isGh){fb.innerHTML='
File browser is only available for GitHub repositories.
';return;}
+ if(!MFS_IGNORES[curPkg])MFS_IGNORES[curPkg]=new Set();
+ const versionKey=curVersion?curVersion.name+':'+curVersion.kind:'';
+ if(CUR_FILES&&CUR_FILES_CACHED_PKG===curPkg&&CUR_FILES_CACHED_VERSION===versionKey){renderFilesList();return;}
+ fb.innerHTML='
Loading filesā¦
';
+ try{
+ const apiUrl=buildGhApiUrl(c,curVersion);
+ const opts=ghFetchOpts();
+ const resp=await fetch(apiUrl,opts);
+ if(resp.status===403||resp.status===404){fb.innerHTML='
Rate limit exceeded. Add a
GitHub token for higher limits (60ā5000/hr).
';return;}
+ if(!resp.ok)throw new Error(`GitHub API error: ${resp.status}`);
+ const data=await resp.json();
+ CUR_FILES={'':Array.isArray(data)?data:data.entries||[]};
+ CUR_FILES_PATH=[''];
+ CUR_FILES_CACHED_PKG=curPkg;
+ CUR_FILES_CACHED_VERSION=versionKey;
+ CUR_FILES['']=CUR_FILES[''].map(e=>({...e,ignored:MFS_IGNORES[curPkg].has(e.name)}));
+ renderFilesList();
+ }catch(e){console.error('Files load error:',e);fb.innerHTML='
Failed to load files. Check console for details.
';}
+}
+function buildGhApiUrl(c,version){
+ let org=c.org||'';let repo=c.repo||c.name||'';
+ const m=(c.url||'').match(/github\.com[\/:]([^\/]+)\/([^\/\s]+)/);
+ if(m){org=m[1].toLowerCase();repo=m[2].replace(/\.git$/,'').toLowerCase();}
+ const ref=version?(version.kind==='branch'?version.name:version.name):c.default_branch||'main';
+ const pth=CUR_FILES_PATH.slice(1).join('/');
+ const pathParam=pth?'/'+pth:'';
+ return `https://api.github.com/repos/${org}/${repo}/contents${pathParam}?ref=${ref}`;
+}
+function renderFilesList(){
+ const fb=document.getElementById('filesBody');if(!fb||!CUR_FILES)return;
+ const curPath=CUR_FILES_PATH.slice(1).join('/');
+ const items=CUR_FILES[curPath]||[];
+ if(!items.length){fb.innerHTML='
š Empty directory
';return;}
+ const sorted=[...items].sort((a,b)=>{if(a.type!==b.type)return a.type==='dir'?-1:1;return a.name.localeCompare(b.name);});
+ let html='
Select files to ignore
';
+ if(CUR_FILES_PATH.length>1){
+ const parentPath=CUR_FILES_PATH.slice(0,-1).join('/');
+ html+=`
${iconParent()}.. (parent directory)
`;
+ }
+ sorted.forEach(item=>{html+=renderFileRow(item);});
+ html+='
';
+ fb.innerHTML=html;
+ updFilesIgnCnt();
+}
+function renderFileRow(item){
+ const ico=item.type==='dir'?iconFolder():iconFile();
+ const curDir=CUR_FILES_PATH.slice(1).join('/');
+ const fullPath=curDir?curDir+'/'+item.name:item.name;
+ const ign=MFS_IGNORES[curPkg]?.has(fullPath)||item.ignored;
+ const folderNav=item.type==='dir'?`ondblclick="event.stopPropagation();navFilesPath('${esc(item.name)}')" title="Double-click to open"`:' title="Check to ignore"';
+ return `
${ico}${esc(item.name)}
`;
+}
+function toggleFileIgnore(fullPath,ignored){
+ if(!curPkg||!MFS_IGNORES[curPkg])return;
+ if(ignored)MFS_IGNORES[curPkg].add(fullPath);
+ else MFS_IGNORES[curPkg].delete(fullPath);
+ const parts=fullPath.split('/');
+ const fileName=parts[parts.length-1];
+ const dirPath=parts.slice(0,-1).join('/');
+ const items=CUR_FILES[dirPath]||[];
+ const it=items.find(i=>i.name===fileName);
+ if(it)it.ignored=ignored;
+ renderFilesList();
+ renderModal();
+ const c=curPkg?RAW[curPkg]:null;
+ if(c)document.getElementById('dSnip').innerHTML=buildSnipHtml(c,curVersion);
+}
+function updFilesIgnCnt(){
+ const el=document.getElementById('filesIgnCnt');if(!el)return;
+ const cnt=MFS_IGNORES[curPkg]?.size||0;
+ if(cnt){el.textContent=`${cnt} file${cnt!==1?'s':''} ignored`;el.style.display='';}
+ else{el.style.display='none';}
+}
+async function navFilesPath(subPath){
+ const c=curPkg?RAW[curPkg]:null;if(!c)return;
+ const fb=document.getElementById('filesBody');if(!fb)return;
+ const targetPath=subPath||'';
+ if(CUR_FILES[targetPath]){CUR_FILES_PATH=subPath?subPath.split('/'):[''];renderFilesList();return;}
+ fb.innerHTML='
Loadingā¦
';
+ try{
+ const apiUrl=buildGhApiUrl(c,curVersion);
+ const opts=ghFetchOpts();
+ const resp=await fetch(apiUrl,opts);if(!resp.ok)throw new Error(`API error: ${resp.status}`);
+ CUR_FILES[targetPath]=await resp.json();
+ CUR_FILES[targetPath]=CUR_FILES[targetPath].map(e=>({...e,ignored:MFS_IGNORES[curPkg].has((targetPath?targetPath+'/':'')+e.name)}));
+ CUR_FILES_PATH=targetPath?targetPath.split('/'):[''];
+ renderFilesList();
+ }catch(e){fb.innerHTML='
Failed to load directory
';}
+}
+function getIgnoredYaml(id){
+ const igns=MFS_IGNORES[id];if(!igns||!igns.size)return'';
+ const lines=[...igns].map(p=>` - ${yamlQ(p)}`).join('\n');
+ return` ignore:\n${lines}\n`;
+}
+function ghFetchOpts(){
+ if(!GH_TOKEN)return{};
+ return{headers:{Authorization:`Bearer ${GH_TOKEN}`,Accept:'application/vnd.github.v3+json'}};
+}
+function setGhToken(val){
+ GH_TOKEN=val.trim();
+ if(GH_TOKEN)localStorage.setItem('ghToken',GH_TOKEN);
+ else localStorage.removeItem('ghToken');
+ updGhTokenStatus();
+}
+function toggleGhTokenVisibility(){
+ const inp=document.getElementById('ghToken');
+ if(!inp)return;
+ inp.type=inp.type==='password'?'text':'password';
+}
+function updGhTokenStatus(){
+ const el=document.getElementById('ghTokenStatus');
+ if(!el)return;
+ if(!GH_TOKEN){el.innerHTML='
No token set (rate limits apply)';return;}
+ el.innerHTML='
ā Token activeClear';
+}
+function clearGhToken(){
+ GH_TOKEN='';
+ localStorage.removeItem('ghToken');
+ const inp=document.getElementById('ghToken');
+ if(inp)inp.value='';
+ updGhTokenStatus();
+ showToast('GitHub token cleared');
+}
+function openGhTokenPage(){
+ window.open('https://github.com/settings/tokens/new?description=dfetch-hub&scopes=','_blank','noopener');
+}
function switchTab(name){
- document.querySelectorAll('.tabbtn').forEach((b,i)=>{const ns=['readme','versions','changelog'];const on=ns[i]===name;b.classList.toggle('active',on);b.setAttribute('aria-selected',String(on))});
+ document.querySelectorAll('.tabbtn').forEach((b,i)=>{const ns=['readme','versions','changelog','files'];const on=ns[i]===name;b.classList.toggle('active',on);b.setAttribute('aria-selected',String(on))});
document.querySelectorAll('.tabcontent').forEach(el=>el.classList.remove('active'));
document.getElementById('tab-'+name).classList.add('active');
+ if(name==='files')loadFilesIfNeeded();
}
function toggleSnip(){
const panel=document.getElementById('snipPanel');const card=document.getElementById('snipCard');
@@ -1192,7 +1450,7 @@
Found in
const open=drop.style.display!=='none';
drop.style.display=open?'none':'';
if(btn)btn.classList.toggle('active',!open);
- if(!open){const inp=document.getElementById('mfPfx');if(inp){inp.value=DST_PREFIX;inp.focus();}}
+ if(!open){const inp=document.getElementById('mfPfx');if(inp){inp.value=DST_PREFIX;inp.focus();}updGhTokenStatus();}
}
function setDstPrefix(v){
DST_PREFIX=v.trim();
@@ -1235,7 +1493,10 @@
Found in
if(c.subfolder_path)e+=` src: ${yamlQ(c.subfolder_path)}\n`;
if(v){if(v.kind==='branch')e+=` branch: ${yamlQ(v.name)}\n`;else e+=` tag: ${yamlQ(v.name)}\n`;}
else{const t=ltag(c);if(t)e+=` tag: ${yamlQ(t)}\n`;else e+=` branch: ${yamlQ(br)}\n`;}
- e+=` dst: ${pfx}${sn}\n`;return e;
+ e+=` dst: ${pfx}${sn}\n`;
+ const ign=getIgnoredYaml(c.id);
+ if(ign)e+=ign;
+ return e;
}
function buildSnipHtml(c,version){
const v=version||curVersion;const br=c.default_branch||'main';const sn=(c.name||'comp').replace(/\s+/g,'-').toLowerCase();
@@ -1245,7 +1506,15 @@
Found in
if(c.subfolder_path)h+=` ${k('src')}: ${sv(esc(c.subfolder_path))}\n`;
if(v){if(v.kind==='branch')h+=` ${k('branch')}: ${sv(esc(v.name))}\n`;else h+=` ${k('tag')}: ${sv(esc(v.name))}\n`;}
else{const t=ltag(c);if(t)h+=` ${k('tag')}: ${sv(esc(t))}\n`;else h+=` ${k('branch')}: ${sv(esc(br))}\n`;}
- h+=` ${k('dst')}: ${sv(pfx+sn)}\n`;return h;
+ h+=` ${k('dst')}: ${sv(pfx+sn)}\n`;
+ const igns=MFS_IGNORES[c.id];if(igns&&igns.size){const lines=[...igns].map(p=>` ${k('-')} ${sv(yamlQ(p))}`).join('\n');h+=` ${k('ignore')}:\n${lines}\n`;}
+ return h;
+}
+function getIgnoredYamlHtml(id){
+ const igns=MFS_IGNORES[id];if(!igns||!igns.size)return'';
+ const k=s=>`
${s}`,sv=s=>`
${s}`;
+ const lines=[...igns].map(p=>` ${k('-')} ${sv(yamlQ(p))}`).join('\n');
+ return` ${k('ignore')}:\n${lines}\n`;
}
function buildEntryHtml(c,version){
const v=version||MFS_VERSIONS[c.id];const br=c.default_branch||'main';const sn=(c.name||'comp').replace(/\s+/g,'-').toLowerCase();
@@ -1255,7 +1524,9 @@
Found in
if(c.subfolder_path)h+=` ${k('src')}: ${sv(esc(c.subfolder_path))}\n`;
if(v){if(v.kind==='branch')h+=` ${k('branch')}: ${sv(esc(v.name))}\n`;else h+=` ${k('tag')}: ${sv(esc(v.name))}\n`;}
else{const t=ltag(c);if(t)h+=` ${k('tag')}: ${sv(esc(t))}\n`;else h+=` ${k('branch')}: ${sv(esc(br))}\n`;}
- h+=` ${k('dst')}: ${sv(pfx+sn)}\n`;return h;
+ h+=` ${k('dst')}: ${sv(pfx+sn)}\n`;
+ const ign=getIgnoredYamlHtml(c.id);if(ign)h+=ign;
+ return h;
}
function buildFullMfHtml(items){
const k=s=>`
${s}`,sv=s=>`
${s}`;
@@ -1274,7 +1545,7 @@
Found in
const _ESC={'&':'&','<':'<','>':'>','"':'"',"'":'''};
function esc(s){return String(s||'').replace(/[&<>"']/g,c=>_ESC[c]||c)}
-function yamlQ(s){return"'"+String(s||'').replace(/'/g,"''")+"'"}
+function yamlQ(s){const str=String(s||'');if(/^[-\w./]*$/.test(str))return str;return"'"+str.replace(/'/g,"''")+"'"}
function slug(s){return String(s||'').toLowerCase().replace(/[^a-z0-9]/g,'-').replace(/-+/g,'-').replace(/^-|-$/g,'')}
const _LBL_KNOWN={'vcpkg':['#d6eaf8','#1a5276'],'conan':['#fef9e7','#7d6608'],'npm':['#fde8e4','#922b21'],'clib':['#e8d8f0','#4a235a'],'esp':['#d5f5e3','#1e8449'],'esp-idf':['#d5f5e3','#1e8449'],'github':['#eaecee','#2c3e50'],'gitlab':['#fde8f0','#7b241c'],'svn':['#fef5e7','#784212']};
const _LBL_PAL=[['#d6eaf8','#1a5276'],['#fde8e4','#922b21'],['#d5f5e3','#1e8449'],['#fef9e7','#7d6608'],['#e8d8f0','#4a235a'],['#fde8f0','#7b241c'],['#fef5e7','#784212'],['#d5eaff','#1a4080'],['#e4f4e8','#1b5e33'],['#ffe4f0','#7b0040'],['#fff0d5','#7a4000'],['#e8e4ff','#2c007a']];
@@ -1282,7 +1553,7 @@
Found in
function makeLbl(l){const[bg,fg]=_lblColor(l);return`
${esc(l)}`}
function fmt(n){if(n>=1000)return(n/1000).toFixed(1)+'k';return String(n)}
function isSafeHttpUrl(url){try{const u=new URL(String(url||''));return u.protocol==='http:'||u.protocol==='https:';}catch(e){return false;}}
-function ltag(c){const tags=(c.tags||[]).filter(t=>t.name);if(!tags.length)return'';const vp=s=>String(s).replace(/^v/i,'').split('.').map(p=>{const n=parseInt(p,10);return isNaN(n)?p:n});return tags.reduce((b,t)=>{const bv=vp(b.name),tv=vp(t.name);for(let i=0;i
t.name);if(!tags.length)return'';const hasAnyDates=tags.some(t=>t.date&&t.date.trim());if(hasAnyDates){return tags.reduce((b,t)=>{const hasDateB=b.date&&b.date.trim(),hasDateT=t.date&&t.date.trim();if(hasDateT&&hasDateB){const db=new Date(b.date),dt=new Date(t.date);return dt>db?t:b}else if(hasDateT)return t;else if(hasDateB)return b;return b}).name}const vp=s=>String(s).replace(/^v/i,'').split('.').map(p=>{const n=parseInt(p,10);return isNaN(n)?p:n});return tags.reduce((b,t)=>{const bv=vp(b.name),tv=vp(t.name);for(let i=0;i{const ta=document.createElement('textarea');ta.value=t;document.body.appendChild(ta);ta.select();try{const ok=document.execCommand('copy');ok?res():rej(new Error('execCommand copy returned false'))}catch(e){rej(e)}finally{ta.remove()}})}
function showToast(msg){const t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');clearTimeout(t._t);t._t=setTimeout(()=>t.classList.remove('show'),2200)}