chore: efficiency updates
b4a37564
6 file(s) · +125 −154
| 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> |
| 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 | + | )} |
| 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 | - | } |
| 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">×</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> |
|
| 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> |
| 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> |
|