chore: efficiency updates b4a37564
Steve · 2026-03-21 20:04 6 file(s) · +125 −154
packages/client/src/components/layout/Footer.astro +2 −8
14 14
  <a href="https://blog.kagi.com/small-web-updates">
15 15
    <Image inferSize={true} src="https://kagifeedback.org/assets/files/2025-11-27/1764250950-635837-80x15-2.png" alt="kagi small web"/>
16 16
  </a>
17 -
	<div class="googleballs-trigger flex items-center gap-1" role="button" tabindex="0" aria-label="Easter egg">
17 +
	<a href="/googleballs" class="flex items-center gap-1" aria-label="Google Balls">
18 18
		<span style="background:#4285F4;width:6px;height:6px;border-radius:50%;display:inline-block;"></span>
19 19
		<span style="background:#EA4335;width:6px;height:6px;border-radius:50%;display:inline-block;"></span>
20 20
		<span style="background:#FBBC05;width:6px;height:6px;border-radius:50%;display:inline-block;"></span>
21 21
		<span style="background:#34A853;width:6px;height:6px;border-radius:50%;display:inline-block;"></span>
22 -
	</div>
22 +
	</a>
23 23
</footer>
24 -
25 -
<script>
26 -
	document.querySelector('.googleballs-trigger')?.addEventListener('click', function() {
27 -
		document.dispatchEvent(new CustomEvent('googleballs:open'));
28 -
	});
29 -
</script>
packages/client/src/components/now/NowUpdates.astro (added) +91 −0
1 +
---
2 +
import { createMarkdownRenderer } from "@/utils";
3 +
import { OWNER_DID, PDS_URL } from "@/data/constants";
4 +
5 +
const md = await createMarkdownRenderer();
6 +
7 +
interface Document {
8 +
	uri: string;
9 +
	value: {
10 +
		title: string;
11 +
		publishedAt: string;
12 +
		path: string;
13 +
		content?: {
14 +
			markdown?: string;
15 +
		};
16 +
		textContent?: string;
17 +
		location?: string;
18 +
	};
19 +
}
20 +
21 +
let documents: Document[] = [];
22 +
let error = false;
23 +
24 +
try {
25 +
	const res = await fetch(
26 +
		`${PDS_URL}/xrpc/com.atproto.repo.listRecords?` +
27 +
			new URLSearchParams({
28 +
				repo: OWNER_DID,
29 +
				collection: "site.standard.document",
30 +
			}),
31 +
	);
32 +
33 +
	if (res.ok) {
34 +
		const data = await res.json();
35 +
		documents = (data.records || [])
36 +
			.filter((doc: Document) => doc.value.path.includes("/now/"))
37 +
			.sort((a: Document, b: Document) => {
38 +
				return new Date(b.value.publishedAt).getTime() - new Date(a.value.publishedAt).getTime();
39 +
			});
40 +
	}
41 +
} catch (err) {
42 +
	console.error("Error fetching updates:", err);
43 +
	error = true;
44 +
}
45 +
---
46 +
47 +
{error && <p>Error loading recent updates. Make sure your PDS is accessible.</p>}
48 +
49 +
{!error && documents.length === 0 && <p>No recent updates found.</p>}
50 +
51 +
{!error && documents.length > 0 && (
52 +
	<div class="space-y-4">
53 +
		{documents.map((record) => {
54 +
			const value = record.value;
55 +
			const path = value.path.slice(1);
56 +
			const publishedAt = new Date(value.publishedAt).toLocaleDateString();
57 +
58 +
			let contentHTML = "";
59 +
			if (value.content && value.content.markdown) {
60 +
				contentHTML = md.render(value.content.markdown).trim()
61 +
					.replace(/<img\s([^>]*?)>/g, (_, attrs) => {
62 +
						const hasLoading = /loading\s*=/.test(attrs);
63 +
						const hasDecoding = /decoding\s*=/.test(attrs);
64 +
						const hasWidth = /width\s*=/.test(attrs);
65 +
						const extra = `${hasLoading ? "" : ' loading="lazy"'}${hasDecoding ? "" : ' decoding="async"'}${hasWidth ? "" : ' width="800"'}`;
66 +
						return `<img ${attrs}${extra} style="height:auto">`;
67 +
					});
68 +
			} else if (value.textContent) {
69 +
				contentHTML = `<p>${value.textContent}</p>`;
70 +
			}
71 +
72 +
			return (
73 +
				<article class="border-b pb-6 mb-6 last:border-b-0">
74 +
					<a
75 +
						href={`/${path}`}
76 +
						class="block hover:opacity-80 transition-opacity"
77 +
					>
78 +
						<h3 class="text-lg font-semibold mb-3">{value.title}</h3>
79 +
					</a>
80 +
					<div
81 +
						class="prose prose-invert max-w-none mb-3"
82 +
						set:html={contentHTML}
83 +
					/>
84 +
					<div class="flex items-center gap-2 text-sm text-gray-500">
85 +
						<time>{publishedAt}</time>
86 +
					</div>
87 +
				</article>
88 +
			);
89 +
		})}
90 +
	</div>
91 +
)}
packages/client/src/components/now/NowUpdates.tsx (deleted) +0 −115
1 -
import { useEffect, useState } from "react";
2 -
import { createMarkdownRenderer } from "@/utils";
3 -
import { OWNER_DID, PDS_URL } from "@/data/constants";
4 -
5 -
const md = await createMarkdownRenderer()
6 -
7 -
interface Document {
8 -
	uri: string;
9 -
	value: {
10 -
		title: string;
11 -
		publishedAt: string;
12 -
		path: string;
13 -
		content?: {
14 -
			markdown?: string;
15 -
		};
16 -
		textContent?: string;
17 -
		location?: string;
18 -
	};
19 -
}
20 -
21 -
export default function NowUpdates() {
22 -
	const [documents, setDocuments] = useState<Document[]>([]);
23 -
	const [loading, setLoading] = useState(true);
24 -
	const [error, setError] = useState(false);
25 -
26 -
	useEffect(() => {
27 -
		async function fetchPosts() {
28 -
			try {
29 -
				const documentsData = await fetch(
30 -
					`${PDS_URL}/xrpc/com.atproto.repo.listRecords?` +
31 -
						new URLSearchParams({
32 -
							repo: OWNER_DID,
33 -
							collection: "site.standard.document",
34 -
							//limit: "20",
35 -
						}),
36 -
				)
37 -
					.then((res) => (res.ok ? res.json() : { records: [] }))
38 -
					.catch(() => ({ records: [] }));
39 -
40 -
				const filteredDocuments = (documentsData.records || []).filter(
41 -
					(doc: Document) => doc.value.path.includes("/now/"),
42 -
				);
43 -
44 -
				const sortedDocuments: Document[] = filteredDocuments.sort(
45 -
					(a: Document, b: Document) => {
46 -
						const dateA = new Date(a.value.publishedAt);
47 -
						const dateB = new Date(b.value.publishedAt);
48 -
						return dateB.getTime() - dateA.getTime();
49 -
					},
50 -
				);
51 -
52 -
				setDocuments(sortedDocuments);
53 -
				setLoading(false);
54 -
			} catch (err) {
55 -
				console.error("Error fetching updates:", err);
56 -
				setError(true);
57 -
				setLoading(false);
58 -
			}
59 -
		}
60 -
61 -
		fetchPosts();
62 -
	}, []);
63 -
64 -
	if (loading) {
65 -
		return <p>Loading...</p>;
66 -
	}
67 -
68 -
	if (error) {
69 -
		return (
70 -
			<p>Error loading recent updates. Make sure your PDS is accessible.</p>
71 -
		);
72 -
	}
73 -
74 -
	if (documents.length === 0) {
75 -
		return <p>No recent updates found.</p>;
76 -
	}
77 -
78 -
	return (
79 -
		<div className="space-y-4">
80 -
			{documents.map((record) => {
81 -
				const value = record.value;
82 -
				const path = value.path.slice(1);
83 -
				const publishedAt = new Date(value.publishedAt).toLocaleDateString();
84 -
85 -
				let contentHTML = "";
86 -
				if (value.content && value.content.markdown) {
87 -
					contentHTML = md.render(value.content.markdown).trim();
88 -
				} else if (value.textContent) {
89 -
					contentHTML = `<p>${value.textContent}</p>`;
90 -
				}
91 -
92 -
				return (
93 -
					<article
94 -
						key={record.uri}
95 -
						className="border-b pb-6 mb-6 last:border-b-0"
96 -
					>
97 -
						<a
98 -
							href={`/${path}`}
99 -
							className="block hover:opacity-80 transition-opacity"
100 -
						>
101 -
							<h3 className="text-lg font-semibold mb-3">{value.title}</h3>
102 -
						</a>
103 -
						<div
104 -
							className="prose prose-invert max-w-none mb-3"
105 -
							dangerouslySetInnerHTML={{ __html: contentHTML }}
106 -
						/>
107 -
						<div className="flex items-center gap-2 text-sm text-gray-500">
108 -
							<time>{publishedAt}</time>
109 -
						</div>
110 -
					</article>
111 -
				);
112 -
			})}
113 -
		</div>
114 -
	);
115 -
}
packages/client/src/layouts/Base.astro +0 −29
3 3
import BaseHead from "@/components/layout/BaseHead.astro";
4 4
import Header from "@/components/layout/Header.astro";
5 5
import Footer from "@/components/layout/Footer.astro";
6 -
import GoogleBalls from "@/components/page/GoogleBalls.astro";
7 6
import siteConfig from "@/site-config";
8 7
9 8
interface Props {
36 35
			<slot />
37 36
		</main>
38 37
		<Footer />
39 -
		<div id="googleballs-modal" style="display:none;position:fixed;inset:0;z-index:9999;background:#121113;">
40 -
      <a href="https://googleballs.com" target="_blank" rel="noreferrer" style="position:absolute;top:16px;left:16px;color:#fff;font-size:16px;cursor:pointer;z-index:10000;text-decoration:underline;">Google Balls</a>
41 -
			<button id="googleballs-close" style="position:absolute;top:16px;right:16px;z-index:10000;background:none;border:none;color:#fff;font-size:32px;cursor:pointer;line-height:1;" aria-label="Close">&times;</button>
42 -
			<GoogleBalls />
43 -
		</div>
44 -
		<script>
45 -
			(function() {
46 -
				var modal = document.getElementById('googleballs-modal');
47 -
				var closeBtn = document.getElementById('googleballs-close');
48 -
49 -
				document.addEventListener('googleballs:open', function() {
50 -
					modal.style.display = 'block';
51 -
					if (window.__googleballs) window.__googleballs.init();
52 -
				});
53 -
54 -
				closeBtn.addEventListener('click', function() {
55 -
					modal.style.display = 'none';
56 -
					if (window.__googleballs) window.__googleballs.stop();
57 -
				});
58 -
59 -
				document.addEventListener('keydown', function(e) {
60 -
					if (e.key === 'Escape' && modal.style.display === 'block') {
61 -
						modal.style.display = 'none';
62 -
						if (window.__googleballs) window.__googleballs.stop();
63 -
					}
64 -
				});
65 -
			})();
66 -
		</script>
67 38
	</body>
68 39
</html>
packages/client/src/pages/googleballs.astro (added) +28 −0
1 +
---
2 +
import GoogleBalls from "@/components/page/GoogleBalls.astro";
3 +
---
4 +
5 +
<html lang="en">
6 +
	<head>
7 +
		<meta charset="utf-8" />
8 +
		<meta name="viewport" content="width=device-width, initial-scale=1" />
9 +
		<meta name="apple-mobile-web-app-capable" content="yes" />
10 +
		<meta name="theme-color" content="#121113" />
11 +
		<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
12 +
		<title>Google Balls</title>
13 +
		<style>
14 +
			* { margin: 0; padding: 0; box-sizing: border-box; }
15 +
			html, body { width: 100%; height: 100%; overflow: hidden; background: #121113; }
16 +
		</style>
17 +
	</head>
18 +
	<body>
19 +
		<div id="googleballs-modal" style="position:fixed;inset:0;background:#121113;">
20 +
			<a href="https://googleballs.com" target="_blank" rel="noreferrer" style="position:absolute;top:16px;left:16px;color:#fff;font-size:16px;cursor:pointer;z-index:10;text-decoration:underline;">Google Balls</a>
21 +
			<a href="/" style="position:absolute;top:16px;right:16px;z-index:10;color:#fff;font-size:16px;text-decoration:underline;">Back</a>
22 +
			<GoogleBalls />
23 +
		</div>
24 +
		<script>
25 +
			if (window.__googleballs) window.__googleballs.init();
26 +
		</script>
27 +
	</body>
28 +
</html>
packages/client/src/pages/now/index.astro +4 −2
1 1
---
2 +
export const prerender = false;
3 +
2 4
import PageLayout from "@/layouts/Base.astro";
3 -
import NowUpdates from "@/components/now/NowUpdates";
5 +
import NowUpdates from "@/components/now/NowUpdates.astro";
4 6
import { SOCIAL_LINKS } from "@/data/constants";
5 7
6 8
const meta = {
64 66
        </a>
65 67
    </div>
66 68
  </div>
67 -
  <NowUpdates client:load />
69 +
  <NowUpdates />
68 70
  </div>
69 71
</PageLayout>