From 169dc7e2bf835b96c6253056ddc3c4373699c659 Mon Sep 17 00:00:00 2001 From: Alex Mickelson Date: Thu, 5 Mar 2026 10:24:54 -0700 Subject: [PATCH] landing page updates --- kubernetes/gitea/landingpage.js | 223 +++++++++++++++----------------- 1 file changed, 104 insertions(+), 119 deletions(-) diff --git a/kubernetes/gitea/landingpage.js b/kubernetes/gitea/landingpage.js index ec4d84a..f5db01b 100644 --- a/kubernetes/gitea/landingpage.js +++ b/kubernetes/gitea/landingpage.js @@ -1,4 +1,4 @@ -(async function () { +(async function loadRepos() { const grid = document.getElementById('repo-grid'); if (!grid) return; @@ -14,113 +14,90 @@ return Math.floor(diff / 31536000) + 'y ago'; } - function escapeHtml(str) { - return (str || '').replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); + function esc(str) { + return (str || '').replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); } - function langDot(lang) { - const colors = { - Go:'#00ADD8', Python:'#3572A5', JavaScript:'#f1e05a', TypeScript:'#2b7489', - Rust:'#dea584', Java:'#b07219', 'C#':'#178600', Nix:'#7e7eff', - Shell:'#89e051', HTML:'#e34c26', CSS:'#563d7c', Elixir:'#6e4a7e', - }; - return colors[lang] - ? `` - : 'πŸ“¦'; - } - - async function fetchJson(url) { - const resp = await fetch(url, { credentials: 'include' }); - if (!resp.ok) throw new Error(resp.status); - return resp.json(); - } - - async function getLatestCommit(repo) { - try { - const commits = await fetchJson( - `${baseUrl}/api/v1/repos/${encodeURIComponent(repo.full_name)}/commits?limit=1&page=1` - ); - if (commits && commits.length > 0) return commits[0]; - } catch (_) {} - return null; - } - - async function loadRepos() { - let repos; - try { - const resp = await fetch(`${baseUrl}/api/v1/repos/search?sort=updated&order=desc&limit=12`, { - credentials: 'include', - }); - if (!resp.ok) { - const msg = resp.status === 401 || resp.status === 403 - ? `Sign in to see repositories (HTTP ${resp.status})` - : `API error: HTTP ${resp.status}`; - grid.innerHTML = `
${msg}. Browse manually β†’
`; - return; - } - const json = await resp.json(); - repos = json.data || json; - } catch (e) { - console.error('Gitea landing: repo fetch failed', e); - grid.innerHTML = `
- Could not load repositories (${e.message}). Browse manually β†’ -
`; + let doc; + try { + const resp = await fetch(`${baseUrl}/alex.rss`); + if (!resp.ok) { + grid.innerHTML = `
Could not load feed (HTTP ${resp.status}). Browse manually β†’
`; return; } - - if (!repos || repos.length === 0) { - grid.innerHTML = `
No public repositories found.
`; - return; - } - - repos.sort((a, b) => new Date(b.updated) - new Date(a.updated)); - - grid.innerHTML = ''; - for (const repo of repos) { - const card = document.createElement('a'); - card.className = 'repo-card'; - card.href = `${baseUrl}/${escapeHtml(repo.full_name)}`; - card.dataset.repoName = repo.full_name; - card.innerHTML = ` -
- ${langDot(repo.language)} - ${escapeHtml(repo.name)} - ${repo.private ? 'private' : ''} -
-
${escapeHtml(repo.description) || 'No description'}
-
- ${repo.language ? `${langDot(repo.language)} ${escapeHtml(repo.language)}` : ''} - ⭐ ${repo.stars_count || 0} - 🍴 ${repo.forks_count || 0} - πŸ•’ ${timeAgo(repo.updated)} -
-
- - loading commit… -
- `.trim(); - grid.appendChild(card); - } - - await Promise.all(repos.map(async (repo) => { - const commit = await getLatestCommit(repo); - const el = document.getElementById(`commit-${CSS.escape(repo.full_name)}`); - if (!el) return; - if (commit) { - const msg = commit.commit?.message?.split('\n')[0] || ''; - const when = timeAgo(commit.commit?.committer?.date || commit.created); - el.innerHTML = ` - - ${escapeHtml(msg)} - ${when} - `; - } else { - el.innerHTML = `no commits visible`; - } - })); + const text = await resp.text(); + doc = new DOMParser().parseFromString(text, 'application/xml'); + } catch (e) { + console.error('Gitea landing: RSS fetch failed', e); + grid.innerHTML = `
Could not load repositories. Browse manually β†’
`; + return; } - loadRepos(); + const items = Array.from(doc.querySelectorAll('channel > item')); + if (items.length === 0) { + grid.innerHTML = `
No activity found.
`; + return; + } + + // Deduplicate: one card per repo (most recent entry wins) + const seen = new Map(); + for (const item of items) { + const titleHtml = item.querySelector('title')?.textContent || ''; + const titleDoc = new DOMParser().parseFromString(titleHtml, 'text/html'); + const anchors = titleDoc.querySelectorAll('a'); + if (anchors.length < 2) continue; + // last anchor in the title is the repo link + const repoAnchor = anchors[anchors.length - 1]; + const repoName = repoAnchor.textContent.trim(); + if (!repoName || seen.has(repoName)) continue; + seen.set(repoName, { repoName, repoUrl: repoAnchor.getAttribute('href') || '#', item }); + } + + if (seen.size === 0) { + grid.innerHTML = `
No repositories found in feed.
`; + return; + } + + grid.innerHTML = ''; + for (const { repoName, repoUrl, item } of seen.values()) { + const pubDate = item.querySelector('pubDate')?.textContent || ''; + const description = item.querySelector('description')?.textContent || ''; + const when = pubDate ? timeAgo(pubDate) : ''; + + // Parse first commit from description: sha\ncommit message + const descDoc = new DOMParser().parseFromString(description, 'text/html'); + const firstAnchor = descDoc.querySelector('a'); + let commitMsg = ''; + let commitUrl = '#'; + if (firstAnchor) { + commitUrl = firstAnchor.getAttribute('href') || '#'; + let node = firstAnchor.nextSibling; + while (node) { + const t = (node.textContent || '').trim(); + if (t) { commitMsg = t; break; } + node = node.nextSibling; + } + if (!commitMsg) commitMsg = firstAnchor.textContent.trim().slice(0, 7); + } + + const shortName = repoName.includes('/') ? repoName.split('/').pop() : repoName; + + const card = document.createElement('a'); + card.className = 'repo-card'; + card.href = esc(repoUrl); + card.innerHTML = ` +
+ ${esc(shortName)} +
+
${esc(repoName)}
+
+ + ${esc(commitMsg)} + ${esc(when)} +
+ `.trim(); + grid.appendChild(card); + } })(); (async function loadActivity() { @@ -170,8 +147,12 @@ const description = item.querySelector('description')?.textContent || ''; const when = pubDate ? timeAgo(pubDate) : ''; + // Strip HTML from title for plain text display + const titleDoc = new DOMParser().parseFromString(title, 'text/html'); + const titleText = titleDoc.body.textContent || title; + let icon = '⚑'; - const t = title.toLowerCase(); + const t = titleText.toLowerCase(); if (t.includes('push') || t.includes('commit')) icon = 'πŸ“€'; else if (t.includes('creat') && t.includes('repo')) icon = 'πŸ“'; else if (t.includes('fork')) icon = '🍴'; @@ -183,23 +164,27 @@ else if (t.includes('comment')) icon = 'πŸ’¬'; else if (t.includes('release')) icon = 'πŸš€'; + // Parse commits from description: sha\ncommit message\n\n... let commitsHtml = ''; const descDoc = new DOMParser().parseFromString(description, 'text/html'); - const commitEls = descDoc.querySelectorAll('li'); - if (commitEls.length > 0) { - commitsHtml = '
' + - Array.from(commitEls).slice(0, 3).map(li => { - const anchor = li.querySelector('a'); - const sha = anchor ? esc(anchor.textContent.trim().slice(0, 7)) : ''; - const shaHref = anchor ? esc(anchor.getAttribute('href') || '#') : '#'; - const msg = esc(li.textContent.replace(anchor?.textContent || '', '').trim().replace(/^[-–:]\s*/, '')); - return `
- ${sha ? `${sha}` : ''} - ${msg} -
`; - }).join('') + - (commitEls.length > 3 ? `
+${commitEls.length - 3} more
` : '') + - '
'; + const commitAnchors = Array.from(descDoc.querySelectorAll('a')).slice(0, 3); + if (commitAnchors.length > 0) { + const lines = commitAnchors.map(anchor => { + const sha = esc(anchor.textContent.trim().slice(0, 7)); + const shaHref = esc(anchor.getAttribute('href') || '#'); + let msg = ''; + let node = anchor.nextSibling; + while (node) { + const t = (node.textContent || '').trim(); + if (t) { msg = esc(t); break; } + node = node.nextSibling; + } + return `
+ ${sha} + ${msg} +
`; + }).join(''); + commitsHtml = `
${lines}
`; } const el = document.createElement('div'); @@ -207,7 +192,7 @@ el.innerHTML = `
${icon}
- + ${commitsHtml}
${when}