Skip to content

Commit d442662

Browse files
authored
Update index.html
1 parent f020f34 commit d442662

1 file changed

Lines changed: 135 additions & 107 deletions

File tree

index.html

Lines changed: 135 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -1,136 +1,164 @@
1-
<script>
2-
// ===== Configuration =====
3-
const ORG = 'Democracy-Lab';
4-
const ORG_PAGES_BASE = 'https://democracy-lab.github.io';
5-
const SHOW_ALL = true; // true = show all public repos immediately; false = only those with Pages
6-
const AUTO_REFRESH_MS = 120000; // 2 minutes; set to 0 to disable auto-refresh
7-
8-
// Build the expected Pages URL for a repo (works for org project pages)
9-
function pagesUrl(repoName) {
10-
if (repoName.toLowerCase() === `${ORG.toLowerCase()}.github.io`) return `${ORG_PAGES_BASE}/`;
11-
return `${ORG_PAGES_BASE}/${encodeURIComponent(repoName)}/`;
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<title>Democracy Lab — Project Sites</title>
6+
<meta name="viewport" content="width=device-width, initial-scale=1" />
7+
<style>
8+
:root { --max: 1100px; }
9+
body { font-family: Arial, Helvetica, sans-serif; margin: 0; background:#0e1116; color:#e6edf3; }
10+
header { padding: 24px; text-align:center; }
11+
header h1 { margin: 0 0 8px; font-size: 28px; }
12+
header p { margin: 0; opacity:.8 }
13+
.container { max-width: var(--max); margin: 0 auto; padding: 16px 24px 48px; }
14+
.toolbar { display:flex; gap:12px; align-items:center; margin-bottom:16px; flex-wrap:wrap; }
15+
input[type="search"] { flex:1; padding:10px 12px; border-radius:10px; border:1px solid #30363d; background:#0d1117; color:#e6edf3; }
16+
select { padding:10px 12px; border-radius:10px; border:1px solid #30363d; background:#0d1117; color:#e6edf3; }
17+
.grid { display:grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap:16px; }
18+
.card { background:#0d1117; border:1px solid #30363d; border-radius:14px; padding:16px; display:flex; flex-direction:column; gap:8px; }
19+
.card h3 { margin:0; font-size:18px; }
20+
.meta { font-size:12px; opacity:.8 }
21+
.links a { text-decoration:none; color:#67b7ff; margin-right:10px; }
22+
footer { text-align:center; padding:24px; opacity:.7; }
23+
.empty { text-align:center; padding:40px; opacity:.8 }
24+
</style>
25+
</head>
26+
<body>
27+
<header>
28+
<h1>Democracy Lab — Project Sites</h1>
29+
<p>Auto-listing of organization repositories that publish with GitHub Pages.</p>
30+
</header>
31+
32+
<div class="container">
33+
<div class="toolbar">
34+
<input id="q" type="search" placeholder="Search repositories… (name or description)" />
35+
<select id="sort">
36+
<option value="updated">Sort: Recently updated</option>
37+
<option value="name">Sort: Name (A→Z)</option>
38+
</select>
39+
<!-- Refresh button will be appended here by the script -->
40+
</div>
41+
42+
<div id="grid" class="grid"></div>
43+
<div id="empty" class="empty" style="display:none;">No matches.</div>
44+
</div>
45+
46+
<footer>Built from the public GitHub API • Last refreshed client-side</footer>
47+
48+
<!-- Put the script here, AFTER the HTML -->
49+
<script>
50+
/* === robust script (guards + no-cors caching fixes) === */
51+
const ORG='Democracy-Lab';
52+
const ORG_PAGES_BASE='https://democracy-lab.github.io';
53+
const SHOW_ALL=true;
54+
const AUTO_REFRESH_MS=120000;
55+
56+
function pagesUrl(name){
57+
return name.toLowerCase()===`${ORG.toLowerCase()}.github.io`
58+
? `${ORG_PAGES_BASE}/`
59+
: `${ORG_PAGES_BASE}/${encodeURIComponent(name)}/`;
1260
}
1361

14-
// Fetch all public repos with pagination; bust caches aggressively
15-
async function fetchAllRepos(url = `https://api.github.com/orgs/${ORG}/repos?per_page=100&type=public&ts=${Date.now()}`) {
16-
const all = [];
17-
while (url) {
18-
const res = await fetch(url, {
19-
cache: 'no-store',
20-
headers: { 'Accept': 'application/vnd.github+json' }
21-
});
22-
if (!res.ok) throw new Error(`GitHub API: ${res.status} ${res.statusText}`);
23-
const page = await res.json();
24-
all.push(...page);
25-
26-
const link = res.headers.get('Link');
27-
const next = link && link.split(',').find(s => s.includes('rel="next"'));
28-
url = next ? next.split(';')[0].trim().slice(1, -1) + `&ts=${Date.now()}` : null;
62+
async function fetchAllRepos(url=`https://api.github.com/orgs/${ORG}/repos?per_page=100&type=public&ts=${Date.now()}`){
63+
const all=[];
64+
while(url){
65+
const res=await fetch(url,{cache:'no-store',headers:{Accept:'application/vnd.github+json'}});
66+
if(!res.ok) throw new Error(`GitHub API: ${res.status} ${res.statusText}`);
67+
all.push(...await res.json());
68+
const link=res.headers.get('Link');
69+
const next=link && link.split(',').find(s=>s.includes('rel="next"'));
70+
url=next? next.split(';')[0].trim().slice(1,-1)+`&ts=${Date.now()}` : null;
2971
}
3072
return all;
3173
}
3274

33-
function fmtDate(iso) { try { return new Date(iso).toLocaleString(); } catch { return iso; } }
34-
35-
function render(list) {
36-
const grid = document.getElementById('grid');
37-
const empty = document.getElementById('empty');
38-
grid.innerHTML = '';
39-
if (!list.length) { empty.style.display = ''; return; }
40-
empty.style.display = 'none';
41-
42-
for (const r of list) {
43-
const live = !!r.has_pages || r.name.toLowerCase() === `${ORG.toLowerCase()}.github.io`;
44-
const site = pagesUrl(r.name);
45-
46-
const card = document.createElement('div');
47-
card.className = 'card';
48-
if (!live) card.style.opacity = 0.6;
49-
50-
card.innerHTML = `
51-
<h3>
52-
<a href="${live ? site : r.html_url}" target="_blank" rel="noopener"
53-
style="color:#67b7ff;text-decoration:none;">
54-
${r.name}
55-
</a>
56-
</h3>
57-
<div class="meta">${r.description ? r.description : ''}</div>
58-
<div class="meta">Updated: ${fmtDate(r.updated_at)}</div>
75+
const el=(id)=>document.getElementById(id);
76+
const fmt=(iso)=>{try{return new Date(iso).toLocaleString();}catch{return iso;}};
77+
78+
function render(list){
79+
const grid=el('grid'), empty=el('empty');
80+
if(!grid){ console.warn('#grid missing'); return; }
81+
grid.innerHTML='';
82+
if(!list.length){ if(empty) empty.style.display=''; return; }
83+
if(empty) empty.style.display='none';
84+
85+
list.forEach(r=>{
86+
const live=!!r.has_pages || r.name.toLowerCase()===`${ORG.toLowerCase()}.github.io`;
87+
const site=pagesUrl(r.name);
88+
const card=document.createElement('div');
89+
card.className='card';
90+
if(!live) card.style.opacity=0.6;
91+
card.innerHTML=`
92+
<h3><a href="${live?site:r.html_url}" target="_blank" rel="noopener" style="color:#67b7ff;text-decoration:none;">${r.name}</a></h3>
93+
<div class="meta">${r.description??''}</div>
94+
<div class="meta">Updated: ${fmt(r.updated_at)}</div>
5995
<div class="links">
60-
${live
61-
? `<a href="${site}" target="_blank" rel="noopener">View site</a>`
62-
: `<span class="meta">Not deployed yet</span>`}
96+
${live?`<a href="${site}" target="_blank" rel="noopener">View site</a>`:`<span class="meta">Not deployed yet</span>`}
6397
<a href="${r.html_url}" target="_blank" rel="noopener">Repo</a>
64-
</div>
65-
`;
66-
67-
// Make the whole card open (site if live, else repo) unless a link was clicked
68-
card.style.cursor = 'pointer';
69-
card.addEventListener('click', (e) => {
70-
if (!(e.target instanceof HTMLAnchorElement)) {
71-
window.open(live ? site : r.html_url, '_blank', 'noopener');
98+
</div>`;
99+
card.style.cursor='pointer';
100+
card.addEventListener('click',e=>{
101+
if(!(e.target instanceof HTMLAnchorElement)){
102+
window.open(live?site:r.html_url,'_blank','noopener');
72103
}
73104
});
74-
75105
grid.appendChild(card);
76-
}
106+
});
77107
}
78108

79-
function filterSort(repos) {
80-
const q = document.getElementById('q').value.toLowerCase().trim();
81-
const sort = document.getElementById('sort').value;
82-
83-
let list = repos.slice();
84-
if (!SHOW_ALL) {
85-
list = list.filter(r => r.has_pages || r.name.toLowerCase() === `${ORG.toLowerCase()}.github.io`);
86-
}
87-
if (q) {
88-
list = list.filter(r =>
109+
function filterSort(repos){
110+
const qEl=el('q'), sortEl=el('sort');
111+
let list=repos.slice();
112+
if(!SHOW_ALL) list=list.filter(r=>r.has_pages||r.name.toLowerCase()===`${ORG.toLowerCase()}.github.io`);
113+
const q=(qEl?.value||'').toLowerCase().trim();
114+
if(q){
115+
list=list.filter(r=>
89116
(r.name && r.name.toLowerCase().includes(q)) ||
90-
(r.description && r.description.toLowerCase().includes(q))
91-
);
117+
(r.description && r.description.toLowerCase().includes(q)));
92118
}
93-
if (sort === 'name') list.sort((a,b) => a.name.localeCompare(b.name));
94-
else list.sort((a,b) => new Date(b.updated_at) - new Date(a.updated_at));
95-
119+
const sort=sortEl?.value || 'updated';
120+
if(sort==='name') list.sort((a,b)=>a.name.localeCompare(b.name));
121+
else list.sort((a,b)=>new Date(b.updated_at)-new Date(a.updated_at));
96122
render(list);
97123
}
98124

99-
async function loadAndRender() {
100-
const btn = document.getElementById('refreshBtn');
101-
if (btn) btn.disabled = true;
102-
103-
try {
104-
const repos = await fetchAllRepos();
105-
window.__REPOS__ = repos;
125+
async function loadAndRender(){
126+
const btn=document.getElementById('refreshBtn');
127+
if(btn) btn.disabled=true;
128+
try{
129+
const repos=await fetchAllRepos();
130+
window.__REPOS__=repos;
106131
filterSort(repos);
107-
} catch (err) {
108-
document.getElementById('grid').innerHTML =
109-
`<div class="empty">Error loading list: ${err.message}</div>`;
110-
} finally {
111-
if (btn) btn.disabled = false;
132+
}catch(err){
133+
console.error(err);
134+
const grid=el('grid');
135+
if(grid) grid.innerHTML=`<div class="empty">Error loading list: ${err.message}</div>`;
136+
}finally{
137+
if(btn) btn.disabled=false;
112138
}
113139
}
114140

115-
function addRefreshButtonOnce() {
116-
if (document.getElementById('refreshBtn')) return;
117-
const toolbar = document.querySelector('.toolbar');
118-
const btn = document.createElement('button');
119-
btn.id = 'refreshBtn';
120-
btn.textContent = 'Refresh';
121-
btn.style.cssText =
122-
'padding:10px 12px;border-radius:10px;border:1px solid #30363d;background:#0d1117;color:#e6edf3;cursor:pointer;';
141+
function addRefreshButtonOnce(){
142+
if(document.getElementById('refreshBtn')) return;
143+
const toolbar=document.querySelector('.toolbar');
144+
if(!toolbar) return; // guard if markup changed
145+
const btn=document.createElement('button');
146+
btn.id='refreshBtn';
147+
btn.textContent='Refresh';
148+
btn.style.cssText='padding:10px 12px;border-radius:10px;border:1px solid #30363d;background:#0d1117;color:#e6edf3;cursor:pointer;';
123149
btn.addEventListener('click', loadAndRender);
124150
toolbar.appendChild(btn);
125151
}
126152

127-
(function start() {
153+
// Start once DOM is ready
154+
document.addEventListener('DOMContentLoaded', ()=>{
128155
addRefreshButtonOnce();
129156
loadAndRender();
130-
document.getElementById('q').addEventListener('input', () => filterSort(window.__REPOS__ || []));
131-
document.getElementById('sort').addEventListener('change', () => filterSort(window.__REPOS__ || []));
132-
if (AUTO_REFRESH_MS > 0) setInterval(loadAndRender, AUTO_REFRESH_MS);
133-
})();
134-
</script>
135-
157+
el('q')?.addEventListener('input', ()=>filterSort(window.__REPOS__||[]));
158+
el('sort')?.addEventListener('change', ()=>filterSort(window.__REPOS__||[]));
159+
if(AUTO_REFRESH_MS>0) setInterval(loadAndRender, AUTO_REFRESH_MS);
160+
});
161+
</script>
162+
</body>
163+
</html>
136164

0 commit comments

Comments
 (0)