diff --git a/kubernetes/gitea/landingpage.css b/kubernetes/gitea/landingpage.css index def81ee..fc462f1 100644 --- a/kubernetes/gitea/landingpage.css +++ b/kubernetes/gitea/landingpage.css @@ -84,10 +84,7 @@ margin: 0; color: var(--color-text); } -.subtitle { - font-size: 0.9rem; - color: var(--color-text-muted); -} + /* Grid */ .repo-grid { diff --git a/kubernetes/gitea/landingpage.html b/kubernetes/gitea/landingpage.html index 473b246..c354b3e 100644 --- a/kubernetes/gitea/landingpage.html +++ b/kubernetes/gitea/landingpage.html @@ -10,7 +10,6 @@

Recent Projects

- Latest commits across all repositories
@@ -25,7 +24,6 @@

Recent Activity

- What Alex has been up to View full profile →
@@ -42,6 +40,7 @@ window.GITEA_APP_URL = "{{AppUrl}}"; window.GITEA_SUB_URL = "{{AppSubUrl}}"; - - + + + {{template "base/footer" .}} diff --git a/kubernetes/gitea/landingpage.js b/kubernetes/gitea/landingpage.js index 1bee8ab..0e863d5 100644 --- a/kubernetes/gitea/landingpage.js +++ b/kubernetes/gitea/landingpage.js @@ -1,69 +1,74 @@ const httpService = { - baseUrl: window.GITEA_SUB_URL || '', + baseUrl: window.GITEA_SUB_URL || "", async fetchRss() { const resp = await fetch(`${this.baseUrl}/alex.rss`); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); const text = await resp.text(); - return new DOMParser().parseFromString(text, 'application/xml'); + return new DOMParser().parseFromString(text, "application/xml"); }, }; const dataDomain = { timeAgo(dateStr) { const diff = (Date.now() - new Date(dateStr)) / 1000; - if (diff < 60) return 'just now'; - if (diff < 3600) return Math.floor(diff / 60) + 'm ago'; - if (diff < 86400) return Math.floor(diff / 3600) + 'h ago'; - if (diff < 2592000) return Math.floor(diff / 86400) + 'd ago'; - if (diff < 31536000) return Math.floor(diff / 2592000) + 'mo ago'; - return Math.floor(diff / 31536000) + 'y ago'; + if (diff < 60) return "just now"; + if (diff < 3600) return Math.floor(diff / 60) + "m ago"; + if (diff < 86400) return Math.floor(diff / 3600) + "h ago"; + if (diff < 2592000) return Math.floor(diff / 86400) + "d ago"; + if (diff < 31536000) return Math.floor(diff / 2592000) + "mo ago"; + return Math.floor(diff / 31536000) + "y ago"; }, esc(str) { - return (str || '') - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"'); + return (str || "") + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """); }, safeTitleHtml(rawTitleText) { - const doc = new DOMParser().parseFromString(rawTitleText, 'text/html'); - doc.body.querySelectorAll('*:not(a)').forEach(el => el.replaceWith(el.textContent)); + const doc = new DOMParser().parseFromString(rawTitleText, "text/html"); + doc.body + .querySelectorAll("*:not(a)") + .forEach((el) => el.replaceWith(el.textContent)); return doc.body.innerHTML; }, titlePlainText(rawTitleText) { - const doc = new DOMParser().parseFromString(rawTitleText, 'text/html'); + const doc = new DOMParser().parseFromString(rawTitleText, "text/html"); return doc.body.textContent || rawTitleText; }, activityIcon(titleText) { const t = titleText.toLowerCase(); - if (t.includes('push') || t.includes('commit')) return '📤'; - if (t.includes('creat') && t.includes('repo')) return '📁'; - if (t.includes('fork')) return '🍴'; - if (t.includes('open') && t.includes('issue')) return '🔴'; - if (t.includes('clos') && t.includes('issue')) return '🟢'; - if (t.includes('pull request') || t.includes('merge')) return '🔀'; - if (t.includes('tag')) return '🏷️'; - if (t.includes('branch')) return '🌿'; - if (t.includes('comment')) return '💬'; - if (t.includes('release')) return '🚀'; - return '⚡'; + if (t.includes("push") || t.includes("commit")) return "📤"; + if (t.includes("creat") && t.includes("repo")) return "📁"; + if (t.includes("fork")) return "🍴"; + if (t.includes("open") && t.includes("issue")) return "🔴"; + if (t.includes("clos") && t.includes("issue")) return "🟢"; + if (t.includes("pull request") || t.includes("merge")) return "🔀"; + if (t.includes("tag")) return "🏷️"; + if (t.includes("branch")) return "🌿"; + if (t.includes("comment")) return "💬"; + if (t.includes("release")) return "🚀"; + return "⚡"; }, parseCommits(descriptionText) { - const doc = new DOMParser().parseFromString(descriptionText, 'text/html'); - return Array.from(doc.querySelectorAll('a')).map(anchor => { + const doc = new DOMParser().parseFromString(descriptionText, "text/html"); + return Array.from(doc.querySelectorAll("a")).map((anchor) => { const sha = anchor.textContent.trim().slice(0, 7); - const href = anchor.getAttribute('href') || '#'; - let msg = ''; + const href = anchor.getAttribute("href") || "#"; + let msg = ""; let node = anchor.nextSibling; while (node) { - const t = (node.textContent || '').trim(); - if (t) { msg = t; break; } + const t = (node.textContent || "").trim(); + if (t) { + msg = t; + break; + } node = node.nextSibling; } return { sha, href, msg }; @@ -71,40 +76,47 @@ const dataDomain = { }, parseRepos(xmlDoc) { - const items = Array.from(xmlDoc.querySelectorAll('channel > item')); + const items = Array.from(xmlDoc.querySelectorAll("channel > item")); 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'); + const titleHtml = item.querySelector("title")?.textContent || ""; + const titleDoc = new DOMParser().parseFromString(titleHtml, "text/html"); + const anchors = titleDoc.querySelectorAll("a"); if (anchors.length < 2) continue; 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') || '#', - shortName: repoName.includes('/') ? repoName.split('/').pop() : repoName, - pubDate: item.querySelector('pubDate')?.textContent || '', - firstCommit: this.parseCommits(item.querySelector('description')?.textContent || '')[0] || null, + repoUrl: repoAnchor.getAttribute("href") || "#", + shortName: repoName.includes("/") + ? repoName.split("/").pop() + : repoName, + pubDate: item.querySelector("pubDate")?.textContent || "", + firstCommit: + this.parseCommits( + item.querySelector("description")?.textContent || "", + )[0] || null, }); } return Array.from(seen.values()); }, parseActivity(xmlDoc, limit = 20) { - return Array.from(xmlDoc.querySelectorAll('channel > item')) + return Array.from(xmlDoc.querySelectorAll("channel > item")) .slice(0, limit) - .map(item => { - const rawTitle = item.querySelector('title')?.textContent || ''; + .map((item) => { + const rawTitle = item.querySelector("title")?.textContent || ""; const titleText = this.titlePlainText(rawTitle); return { titleHtmlSafe: this.safeTitleHtml(rawTitle), titleText, - link: item.querySelector('link')?.textContent || '#', - pubDate: item.querySelector('pubDate')?.textContent || '', + link: item.querySelector("link")?.textContent || "#", + pubDate: item.querySelector("pubDate")?.textContent || "", icon: this.activityIcon(titleText), - commits: this.parseCommits(item.querySelector('description')?.textContent || '').slice(0, 3), + commits: this.parseCommits( + item.querySelector("description")?.textContent || "", + ).slice(0, 3), }; }); }, @@ -112,23 +124,28 @@ const dataDomain = { const uiRendering = { async renderRepos(xmlDoc) { - const grid = document.getElementById('repo-grid'); + const grid = document.getElementById("repo-grid"); if (!grid) return; - const repos = dataDomain.parseRepos(xmlDoc); if (repos.length === 0) { grid.innerHTML = `
No repositories found in feed.
`; return; } - grid.innerHTML = ''; - for (const { shortName, repoName, repoUrl, pubDate, firstCommit } of repos) { + grid.innerHTML = ""; + for (const { + shortName, + repoName, + repoUrl, + pubDate, + firstCommit, + } of repos) { const when = dataDomain.timeAgo(pubDate); - const commitMsg = firstCommit?.msg || firstCommit?.sha || ''; + const commitMsg = firstCommit?.msg || firstCommit?.sha || ""; - const card = document.createElement('a'); - card.className = 'repo-card'; + const card = document.createElement("a"); + card.className = "repo-card"; card.href = dataDomain.esc(repoUrl); card.innerHTML = `
@@ -147,7 +164,7 @@ const uiRendering = { }, async renderActivity(xmlDoc) { - const feed = document.getElementById('activity-feed'); + const feed = document.getElementById("activity-feed"); if (!feed) return; const items = dataDomain.parseActivity(xmlDoc); @@ -156,21 +173,27 @@ const uiRendering = { return; } - feed.innerHTML = ''; + feed.innerHTML = ""; for (const { titleHtmlSafe, icon, pubDate, commits } of items) { const when = dataDomain.timeAgo(pubDate); - const commitsHtml = commits.length === 0 ? '' : - `
` + - commits.map(({ sha, href, msg }) => ` + const commitsHtml = + commits.length === 0 + ? "" + : `
` + + commits + .map( + ({ sha, href, msg }) => `
${dataDomain.esc(sha)} ${dataDomain.esc(msg)} -
`).join('') + - `
`; +
`, + ) + .join("") + + `
`; - const el = document.createElement('div'); - el.className = 'activity-item'; + const el = document.createElement("div"); + el.className = "activity-item"; el.innerHTML = `
${icon}
@@ -188,23 +211,23 @@ const uiRendering = { async render() { const baseUrl = httpService.baseUrl; - let xmlDoc; try { - xmlDoc = await httpService.fetchRss(); + const xmlDoc = await httpService.fetchRss(); + await Promise.all([ + this.renderRepos(xmlDoc), + this.renderActivity(xmlDoc), + ]); } catch (e) { - console.error('Gitea landing: RSS fetch failed', e); - const grid = document.getElementById('repo-grid'); - const feed = document.getElementById('activity-feed'); - if (grid) grid.innerHTML = `
Could not load feed (${e.message}). Browse manually →
`; - if (feed) feed.innerHTML = `
Could not load activity (${e.message})
`; + console.error("Gitea landing: RSS fetch failed", e); + const grid = document.getElementById("repo-grid"); + const feed = document.getElementById("activity-feed"); + if (grid) + grid.innerHTML = `
Could not load feed (${e.message}). Browse manually →
`; + if (feed) + feed.innerHTML = `
Could not load activity (${e.message})
`; return; } - - await Promise.all([ - this.renderRepos(xmlDoc), - this.renderActivity(xmlDoc), - ]); }, }; -document.addEventListener('DOMContentLoaded', () => uiRendering.render()); +document.addEventListener("DOMContentLoaded", () => uiRendering.render());