A fullstack app for indexing standard.site documents
8
fork

Configure Feed

Select the types of activity you want to include in your feed.

chore: added xp styles

Steve d9fbf7b1 f09f6b37

+134 -176
+10 -23
bun.lock
··· 6 6 "name": "atfeeds", 7 7 }, 8 8 "packages/client": { 9 - "name": "@atfeeds/client", 9 + "name": "@document-feeds/client", 10 10 "version": "1.0.0", 11 11 "dependencies": { 12 12 "react": "^18.2.0", 13 13 "react-dom": "^18.2.0", 14 + "xp.css": "^0.2.6", 14 15 }, 15 16 "devDependencies": { 16 17 "@types/react": "^18.2.0", ··· 20 21 "vite": "^5.0.0", 21 22 }, 22 23 }, 23 - "packages/cloudflare": { 24 - "name": "atfeeds-cloudflare", 24 + "packages/server": { 25 + "name": "@document-feeds/server", 25 26 "version": "1.0.0", 26 27 "dependencies": { 27 28 "hono": "^4.0.0", ··· 29 30 "devDependencies": { 30 31 "@cloudflare/workers-types": "^4.20240117.0", 31 32 "wrangler": "^3.0.0", 32 - }, 33 - }, 34 - "packages/server": { 35 - "name": "@atfeeds/server", 36 - "version": "1.0.0", 37 - "dependencies": { 38 - "hono": "^4.0.0", 39 - }, 40 - "devDependencies": { 41 - "@types/bun": "^1.0.0", 42 33 }, 43 34 }, 44 35 }, 45 36 "packages": { 46 - "@atfeeds/client": ["@atfeeds/client@workspace:packages/client"], 47 - 48 - "@atfeeds/server": ["@atfeeds/server@workspace:packages/server"], 49 - 50 37 "@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], 51 38 52 39 "@babel/compat-data": ["@babel/compat-data@7.28.6", "", {}, "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg=="], ··· 102 89 "@cloudflare/workers-types": ["@cloudflare/workers-types@4.20260111.0", "", {}, "sha512-NFA2U+AqEWHkAmw6oRzNWJyc14rIvBlF/OlK3lixokunRKwyziuON07nWUZ0w0kKWlW4fJ/muA09tEUaQY07tA=="], 103 90 104 91 "@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="], 92 + 93 + "@document-feeds/client": ["@document-feeds/client@workspace:packages/client"], 94 + 95 + "@document-feeds/server": ["@document-feeds/server@workspace:packages/server"], 105 96 106 97 "@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="], 107 98 ··· 265 256 266 257 "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], 267 258 268 - "@types/bun": ["@types/bun@1.3.5", "", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="], 269 - 270 259 "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], 271 260 272 261 "@types/node": ["@types/node@25.0.6", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-NNu0sjyNxpoiW3YuVFfNz7mxSQ+S4X2G28uqg2s+CzoqoQjLPsWSbsFFyztIAqt2vb8kfEAsJNepMGPTxFDx3Q=="], ··· 285 274 286 275 "as-table": ["as-table@1.0.55", "", { "dependencies": { "printable-characters": "^1.0.42" } }, "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ=="], 287 276 288 - "atfeeds-cloudflare": ["atfeeds-cloudflare@workspace:packages/cloudflare"], 289 - 290 277 "baseline-browser-mapping": ["baseline-browser-mapping@2.9.14", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg=="], 291 278 292 279 "blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="], 293 280 294 281 "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], 295 - 296 - "bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="], 297 282 298 283 "caniuse-lite": ["caniuse-lite@1.0.30001764", "", {}, "sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g=="], 299 284 ··· 434 419 "wrangler": ["wrangler@3.114.16", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.3.4", "@cloudflare/unenv-preset": "2.0.2", "@esbuild-plugins/node-globals-polyfill": "0.2.3", "@esbuild-plugins/node-modules-polyfill": "0.2.2", "blake3-wasm": "2.1.5", "esbuild": "0.17.19", "miniflare": "3.20250718.3", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.14", "workerd": "1.20250718.0" }, "optionalDependencies": { "fsevents": "~2.3.2", "sharp": "^0.33.5" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20250408.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-ve/ULRjrquu5BHNJ+1T0ipJJlJ6pD7qLmhwRkk0BsUIxatNe4HP4odX/R4Mq/RHG6LOnVAFs7SMeSHlz/1mNlQ=="], 435 420 436 421 "ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="], 422 + 423 + "xp.css": ["xp.css@0.2.6", "", {}, "sha512-v0qUB4wOvyXIMLNid5bIpm+0n5aL4i8wE5dqJH8LNVxE04PxUUxjwf2rNzengegUIK03XC4vbfLVCPAfMLtuqA=="], 437 424 438 425 "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], 439 426
+1 -1
packages/client/index.html
··· 3 3 <head> 4 4 <meta charset="UTF-8" /> 5 5 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 - <title>AT Feeds - Standard.site Documents</title> 6 + <title>docs.surf</title> 7 7 </head> 8 8 <body> 9 9 <div id="root"></div>
+2 -1
packages/client/package.json
··· 10 10 }, 11 11 "dependencies": { 12 12 "react": "^18.2.0", 13 - "react-dom": "^18.2.0" 13 + "react-dom": "^18.2.0", 14 + "xp.css": "^0.2.6" 14 15 }, 15 16 "devDependencies": { 16 17 "@types/react": "^18.2.0",
+109 -64
packages/client/src/App.tsx
··· 31 31 const [loading, setLoading] = useState(true); 32 32 const [error, setError] = useState<string | null>(null); 33 33 34 - useEffect(() => { 35 - async function fetchFeed() { 36 - try { 37 - const response = await fetch(`${API_URL}/feed`); 38 - if (!response.ok) { 39 - throw new Error("Failed to fetch feed"); 40 - } 41 - const data: FeedResponse = await response.json(); 42 - setDocuments(data.documents); 43 - } catch (err) { 44 - setError(err instanceof Error ? err.message : "Unknown error"); 45 - } finally { 46 - setLoading(false); 34 + const fetchFeed = async () => { 35 + setLoading(true); 36 + setError(null); 37 + try { 38 + const response = await fetch(`${API_URL}/feed`); 39 + if (!response.ok) { 40 + throw new Error("Failed to fetch feed"); 47 41 } 42 + const data: FeedResponse = await response.json(); 43 + setDocuments(data.documents); 44 + } catch (err) { 45 + setError(err instanceof Error ? err.message : "Unknown error"); 46 + } finally { 47 + setLoading(false); 48 48 } 49 + }; 49 50 51 + useEffect(() => { 50 52 fetchFeed(); 51 53 }, []); 52 54 ··· 65 67 return text.slice(0, maxLength) + "..."; 66 68 }; 67 69 68 - if (loading) { 69 - return ( 70 - <div className="container"> 71 - <h1>Standard.site Documents</h1> 72 - <p className="loading">Loading documents...</p> 70 + 71 + return ( 72 + <div className="window" style={{ width: "100%", maxWidth: "800px" }}> 73 + <div className="title-bar"> 74 + <div className="title-bar-text">docs.surf - Internet Explorer 6</div> 75 + <div className="title-bar-controls"> 76 + <button aria-label="Minimize" /> 77 + <button aria-label="Maximize" /> 78 + <button aria-label="Close" /> 79 + </div> 73 80 </div> 74 - ); 75 - } 81 + <div className="window-body"> 82 + <div 83 + style={{ 84 + display: "flex", 85 + gap: "8px", 86 + marginBottom: "16px", 87 + alignItems: "center", 88 + }} 89 + > 90 + <button onClick={fetchFeed}>Refresh</button> 91 + <button 92 + onClick={() => window.open("https://standard.site", "_blank")} 93 + > 94 + Standard.site 95 + </button> 96 + <div style={{ flex: 1 }} /> 97 + <span>{documents.length} documents</span> 98 + </div> 76 99 77 - if (error) { 78 - return ( 79 - <div className="container"> 80 - <h1>Standard.site Documents</h1> 81 - <p className="error">Error: {error}</p> 82 - </div> 83 - ); 84 - } 100 + {loading && <p style={{ textAlign: "center" }}>Searching...</p>} 85 101 86 - return ( 87 - <div className="container"> 88 - <h1>Standard.site Documents</h1> 89 - <p className="subtitle">{documents.length} documents found</p> 102 + {error && ( 103 + <div 104 + style={{ 105 + padding: "10px", 106 + background: "#ffefef", 107 + border: "1px solid #ff0000", 108 + }} 109 + > 110 + <p>Error: {error}</p> 111 + </div> 112 + )} 90 113 91 - <div className="feed"> 92 - {documents.map((doc) => ( 93 - <article key={doc.uri} className="document-card"> 94 - <h2> 95 - {doc.viewUrl ? ( 96 - <a href={doc.viewUrl} target="_blank" rel="noopener noreferrer"> 97 - {doc.title} 98 - </a> 99 - ) : ( 100 - doc.title 101 - )} 102 - </h2> 103 - <time dateTime={doc.publishedAt || undefined}> 104 - {formatDate(doc.publishedAt)} 105 - </time> 106 - {doc.textContent && ( 107 - <p className="excerpt">{truncateText(doc.textContent)}</p> 108 - )} 109 - {doc.viewUrl && ( 110 - <a 111 - href={doc.viewUrl} 112 - target="_blank" 113 - rel="noopener noreferrer" 114 - className="read-more" 115 - > 116 - Read on author's site 117 - </a> 118 - )} 119 - </article> 120 - ))} 114 + {!loading && !error && ( 115 + <div className="feed" style={{ maxHeight: "70vh", overflowY: "auto", paddingRight: "5px" }}> 116 + {documents.map((doc) => ( 117 + <fieldset key={doc.uri} style={{ marginBottom: "16px" }}> 118 + <legend style={{ fontWeight: "bold" }}> 119 + {doc.viewUrl ? ( 120 + <a 121 + href={doc.viewUrl} 122 + target="_blank" 123 + rel="noopener noreferrer" 124 + style={{ color: "inherit", textDecoration: "none" }} 125 + > 126 + {doc.title} 127 + </a> 128 + ) : ( 129 + doc.title 130 + )} 131 + </legend> 132 + <div style={{ padding: "8px" }}> 133 + <div 134 + style={{ 135 + marginBottom: "8px", 136 + fontSize: "0.85em", 137 + color: "#666", 138 + }} 139 + > 140 + Published: {formatDate(doc.publishedAt)} 141 + </div> 142 + {doc.textContent && ( 143 + <p style={{ marginBottom: "12px" }}> 144 + {truncateText(doc.textContent)} 145 + </p> 146 + )} 147 + {doc.viewUrl && ( 148 + <div style={{ textAlign: "right" }}> 149 + <button 150 + onClick={() => 151 + window.open(doc.viewUrl || "", "_blank") 152 + } 153 + > 154 + Read More 155 + </button> 156 + </div> 157 + )} 158 + </div> 159 + </fieldset> 160 + ))} 161 + {documents.length === 0 && <p>No documents found.</p>} 162 + </div> 163 + )} 121 164 </div> 122 - 123 - {documents.length === 0 && <p className="empty">No documents found.</p>} 165 + <div className="status-bar"> 166 + <p className="status-bar-field">Done</p> 167 + <p className="status-bar-field">Internet</p> 168 + </div> 124 169 </div> 125 170 ); 126 171 }
+11 -87
packages/client/src/index.css
··· 1 - * { 2 - box-sizing: border-box; 3 - margin: 0; 4 - padding: 0; 5 - } 6 - 7 1 body { 8 - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, 9 - Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 10 - line-height: 1.6; 11 - color: #333; 12 - background: #f5f5f5; 13 - } 14 - 15 - .container { 16 - max-width: 800px; 17 - margin: 0 auto; 18 - padding: 2rem 1rem; 19 - } 20 - 21 - h1 { 22 - font-size: 2rem; 23 - margin-bottom: 0.5rem; 24 - } 25 - 26 - .subtitle { 27 - color: #666; 28 - margin-bottom: 2rem; 29 - } 30 - 31 - .loading, 32 - .error, 33 - .empty { 34 - text-align: center; 35 - padding: 2rem; 36 - color: #666; 37 - } 38 - 39 - .error { 40 - color: #c00; 41 - } 42 - 43 - .feed { 2 + background: url("./xp.jpg") no-repeat center center fixed; 3 + background-size: cover; 4 + min-height: 100vh; 44 5 display: flex; 45 - flex-direction: column; 46 - gap: 1.5rem; 47 - } 48 - 49 - .document-card { 50 - background: white; 51 - border-radius: 8px; 52 - padding: 1.5rem; 53 - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); 6 + align-items: center; 7 + justify-content: center; 8 + margin: 0; 9 + padding: 20px; 54 10 } 55 11 56 - .document-card h2 { 57 - font-size: 1.25rem; 58 - margin-bottom: 0.5rem; 59 - } 60 - 61 - .document-card h2 a { 62 - color: #1a1a1a; 63 - text-decoration: none; 64 - } 65 - 66 - .document-card h2 a:hover { 67 - color: #0066cc; 68 - text-decoration: underline; 69 - } 70 - 71 - .document-card time { 72 - display: block; 73 - font-size: 0.875rem; 74 - color: #666; 75 - margin-bottom: 0.75rem; 76 - } 77 - 78 - .document-card .excerpt { 79 - color: #444; 80 - margin-bottom: 1rem; 81 - } 82 - 83 - .document-card .read-more { 84 - display: inline-block; 85 - color: #0066cc; 86 - text-decoration: none; 87 - font-size: 0.875rem; 88 - } 89 - 90 - .document-card .read-more:hover { 91 - text-decoration: underline; 12 + #root { 13 + width: 100%; 14 + display: flex; 15 + justify-content: center; 92 16 }
+1
packages/client/src/main.tsx
··· 2 2 import ReactDOM from "react-dom/client"; 3 3 import App from "./App"; 4 4 import "./index.css"; 5 + import "xp.css/dist/XP.css"; 5 6 6 7 ReactDOM.createRoot(document.getElementById("root")!).render( 7 8 <React.StrictMode>
packages/client/src/xp.jpg

This is a binary file and will not be displayed.