diff --git a/netlify/functions/raw.ts b/netlify/functions/raw.ts index f9b651a..badc53f 100644 --- a/netlify/functions/raw.ts +++ b/netlify/functions/raw.ts @@ -1,4 +1,3 @@ -import type { Handler, HandlerEvent, HandlerContext } from "@netlify/functions"; import * as fs from "fs"; import * as path from "path"; @@ -9,8 +8,20 @@ import * as path from "path"; * Returns text/plain with minimal headers for reliable AI ingestion. */ +// Inline types for Netlify Functions (avoids external dependency) +interface HandlerEvent { + path: string; + httpMethod: string; +} + +interface HandlerResponse { + statusCode: number; + headers: Record; + body: string; +} + // Response headers optimized for AI crawlers -const AI_HEADERS = { +const AI_HEADERS: Record = { "Content-Type": "text/plain; charset=utf-8", "Access-Control-Allow-Origin": "*", "Cache-Control": "public, max-age=3600", @@ -20,10 +31,7 @@ const AI_HEADERS = { // Extract slug from path like /api/raw/my-post or /.netlify/functions/raw/my-post function extractSlug(rawPath: string): string | null { // Handle both /api/raw/:slug and /.netlify/functions/raw/:slug patterns - const patterns = [ - /^\/api\/raw\/(.+)$/, - /^\/.netlify\/functions\/raw\/(.+)$/, - ]; + const patterns = [/^\/api\/raw\/(.+)$/, /^\/.netlify\/functions\/raw\/(.+)$/]; for (const pattern of patterns) { const match = rawPath.match(pattern); @@ -59,10 +67,8 @@ function readMarkdownFile(slug: string): string | null { return null; } -const handler: Handler = async ( - event: HandlerEvent, - _context: HandlerContext, -) => { +// Netlify Function handler +const handler = async (event: HandlerEvent): Promise => { // Only allow GET requests if (event.httpMethod !== "GET") { return { @@ -103,4 +109,3 @@ const handler: Handler = async ( }; export { handler }; - diff --git a/package.json b/package.json index cbb0d69..0b903de 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,6 @@ "remark-gfm": "^4.0.0" }, "devDependencies": { - "@netlify/functions": "^2.8.2", "@types/node": "^25.0.2", "@types/react": "^18.2.56", "@types/react-dom": "^18.2.19", diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index bda19f0..db90d0b 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -98,6 +98,7 @@ export default function Home() { An open-source publishing framework for AI agents and developers. {" "} +
Write markdown, sync from the terminal.

setGithubStars(null)); }, []); - // Don't render until stats load + // Show loading spinner while stats load if (stats === undefined) { - return null; + return ( +
+
+ +

+ Loading statistics... +

+
+
+ ); } // Stats card configuration with numbered sections diff --git a/src/styles/global.css b/src/styles/global.css index a777443..63ac440 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -715,6 +715,20 @@ body { animation: dropdownFadeIn 0.15s ease; } +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +.spinner-icon { + animation: spin 1s linear infinite; + color: var(--text-secondary); +} + @keyframes dropdownFadeIn { from { opacity: 0; @@ -878,8 +892,8 @@ body { display: flex; align-items: center; justify-content: center; - width: 20px; - height: 20px; + width: 24px; + height: 24px; padding: 0; background: transparent; border: none; @@ -896,6 +910,8 @@ body { } .page-sidebar-expand svg { + width: 16px; + height: 16px; transition: transform 0.15s ease; } @@ -904,7 +920,7 @@ body { } .page-sidebar-spacer { - width: 20px; + width: 24px; flex-shrink: 0; } @@ -1012,17 +1028,17 @@ body { } .page-sidebar-expand { - width: 18px; - height: 18px; + width: 24px; + height: 24px; } .page-sidebar-expand svg { - width: 12px; - height: 12px; + width: 16px; + height: 16px; } .page-sidebar-spacer { - width: 18px; + width: 24px; } .page-sidebar-link {