astro-awesomeness 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["ButtonPrimitive"],"sources":["../../src/components/ui/button-variants.ts","../../src/components/ui/button.tsx","../../src/components/theme-toggle.tsx"],"sourcesContent":["import { cva } from \"class-variance-authority\";\n\nconst buttonVariants = cva(\n \"inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n {\n defaultVariants: {\n size: \"default\",\n variant: \"default\",\n },\n variants: {\n size: {\n default: \"h-9 px-4 py-2 has-[>svg]:px-3\",\n icon: \"size-9\",\n \"icon-lg\": \"size-10\",\n \"icon-sm\": \"size-8\",\n \"icon-xs\": \"size-6 rounded-md [&_svg:not([class*='size-'])]:size-3\",\n lg: \"h-10 rounded-md px-6 has-[>svg]:px-4\",\n sm: \"h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5\",\n xs: \"h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3\",\n },\n variant: {\n default: \"bg-primary text-primary-foreground hover:bg-primary/90\",\n destructive:\n \"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40\",\n ghost: \"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50\",\n link: \"text-primary underline-offset-4 hover:underline\",\n outline:\n \"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50\",\n secondary: \"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n },\n },\n },\n);\n\nexport { buttonVariants };\n","import { Button as ButtonPrimitive } from \"@base-ui/react/button\";\nimport type { VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\n\nimport { cn } from \"../../lib/cn\";\nimport { buttonVariants } from \"./button-variants\";\n\ntype ButtonProps = Omit<React.ComponentProps<typeof ButtonPrimitive>, \"className\"> &\n VariantProps<typeof buttonVariants> & {\n className?: string;\n };\n\n// React 19 passes `ref` as a regular prop, so no forwardRef wrapper is needed.\nconst Button = ({ className, size = \"default\", variant = \"default\", ...props }: ButtonProps) => {\n return (\n <ButtonPrimitive\n className={cn(buttonVariants({ className, size, variant }))}\n data-size={size}\n data-slot=\"button\"\n data-variant={variant}\n {...props}\n />\n );\n};\n\nexport { Button };\n","import { Moon, Sun } from \"lucide-react\";\nimport { useSyncExternalStore } from \"react\";\n\nimport { Button } from \"./ui/button\";\n\ntype Theme = \"light\" | \"dark\";\n\nconst getStoredTheme = (): Theme | null => {\n if (typeof window === \"undefined\") {\n return null;\n }\n const stored = window.localStorage.getItem(\"theme\");\n if (stored === \"light\" || stored === \"dark\") {\n return stored;\n }\n return null;\n};\n\nconst storedThemeListeners = new Set<() => void>();\n\nconst subscribeStoredTheme = (callback: () => void) => {\n if (typeof window === \"undefined\") {\n return () => {};\n }\n storedThemeListeners.add(callback);\n // \"storage\" only fires in other tabs; same-tab writes notify via setStoredTheme.\n window.addEventListener(\"storage\", callback);\n return () => {\n storedThemeListeners.delete(callback);\n window.removeEventListener(\"storage\", callback);\n };\n};\n\nconst setStoredTheme = (next: Theme) => {\n window.localStorage.setItem(\"theme\", next);\n for (const listener of storedThemeListeners) {\n listener();\n }\n};\n\nconst getPrefersDark = (): boolean => {\n if (typeof window === \"undefined\") {\n return false;\n }\n return window.matchMedia(\"(prefers-color-scheme: dark)\").matches;\n};\n\nconst subscribePrefersDark = (callback: () => void) => {\n if (typeof window === \"undefined\") {\n return () => {};\n }\n const media = window.matchMedia(\"(prefers-color-scheme: dark)\");\n media.addEventListener(\"change\", callback);\n return () => {\n media.removeEventListener(\"change\", callback);\n };\n};\n\ntype Props = {\n ariaLabel?: string;\n};\n\nconst ThemeToggle = ({ ariaLabel = \"Toggle theme\" }: Props) => {\n // useSyncExternalStore avoids hydration mismatch on both reads: the server\n // snapshots (false / null) match the server HTML, then React re-syncs to the\n // real values right after hydration. Reading localStorage during the initial\n // render instead (e.g. in a useState initializer) makes React 19 log a\n // recoverable hydration error for every visitor with a stored theme.\n const prefersDark = useSyncExternalStore(subscribePrefersDark, getPrefersDark, () => false);\n const override = useSyncExternalStore(subscribeStoredTheme, getStoredTheme, () => null);\n const theme: Theme = override ?? (prefersDark ? \"dark\" : \"light\");\n\n const handleToggleTheme = () => {\n const next: Theme = theme === \"dark\" ? \"light\" : \"dark\";\n document.documentElement.classList.toggle(\"dark\", next === \"dark\");\n setStoredTheme(next);\n };\n\n return (\n <Button aria-label={ariaLabel} onClick={handleToggleTheme} size=\"icon\" variant=\"ghost\">\n {theme === \"dark\" ? <Sun /> : <Moon />}\n </Button>\n );\n};\n\nexport { ThemeToggle };\n"],"mappings":";;;;;;;;AAEA,MAAM,iBAAiB,IACrB,+bACA;CACE,iBAAiB;EACf,MAAM;EACN,SAAS;CACX;CACA,UAAU;EACR,MAAM;GACJ,SAAS;GACT,MAAM;GACN,WAAW;GACX,WAAW;GACX,WAAW;GACX,IAAI;GACJ,IAAI;GACJ,IAAI;EACN;EACA,SAAS;GACP,SAAS;GACT,aACE;GACF,OAAO;GACP,MAAM;GACN,SACE;GACF,WAAW;EACb;CACF;AACF,CACF;;;;ACnBA,MAAM,UAAU,EAAE,WAAW,OAAO,WAAW,UAAU,WAAW,GAAG,YAAyB;CAC9F,OACE,oBAACA,UAAD;EACE,WAAW,GAAG,eAAe;GAAE;GAAW;GAAM;EAAQ,CAAC,CAAC;EAC1D,aAAW;EACX,aAAU;EACV,gBAAc;EACd,GAAI;CACL;AAEL;;;;AChBA,MAAM,uBAAqC;CACzC,IAAI,OAAO,WAAW,aACpB,OAAO;CAET,MAAM,SAAS,OAAO,aAAa,QAAQ,OAAO;CAClD,IAAI,WAAW,WAAW,WAAW,QACnC,OAAO;CAET,OAAO;AACT;AAEA,MAAM,uCAAuB,IAAI,IAAgB;AAEjD,MAAM,wBAAwB,aAAyB;CACrD,IAAI,OAAO,WAAW,aACpB,aAAa,CAAC;CAEhB,qBAAqB,IAAI,QAAQ;CAEjC,OAAO,iBAAiB,WAAW,QAAQ;CAC3C,aAAa;EACX,qBAAqB,OAAO,QAAQ;EACpC,OAAO,oBAAoB,WAAW,QAAQ;CAChD;AACF;AAEA,MAAM,kBAAkB,SAAgB;CACtC,OAAO,aAAa,QAAQ,SAAS,IAAI;CACzC,KAAK,MAAM,YAAY,sBACrB,SAAS;AAEb;AAEA,MAAM,uBAAgC;CACpC,IAAI,OAAO,WAAW,aACpB,OAAO;CAET,OAAO,OAAO,WAAW,8BAA8B,CAAC,CAAC;AAC3D;AAEA,MAAM,wBAAwB,aAAyB;CACrD,IAAI,OAAO,WAAW,aACpB,aAAa,CAAC;CAEhB,MAAM,QAAQ,OAAO,WAAW,8BAA8B;CAC9D,MAAM,iBAAiB,UAAU,QAAQ;CACzC,aAAa;EACX,MAAM,oBAAoB,UAAU,QAAQ;CAC9C;AACF;AAMA,MAAM,eAAe,EAAE,YAAY,qBAA4B;CAM7D,MAAM,cAAc,qBAAqB,sBAAsB,sBAAsB,KAAK;CAE1F,MAAM,QADW,qBAAqB,sBAAsB,sBAAsB,IACtD,MAAM,cAAc,SAAS;CAEzD,MAAM,0BAA0B;EAC9B,MAAM,OAAc,UAAU,SAAS,UAAU;EACjD,SAAS,gBAAgB,UAAU,OAAO,QAAQ,SAAS,MAAM;EACjE,eAAe,IAAI;CACrB;CAEA,OACE,oBAAC,QAAD;EAAQ,cAAY;EAAW,SAAS;EAAmB,MAAK;EAAO,SAAQ;YAC5E,UAAU,SAAS,oBAAC,KAAD,CAAM,KAAI,oBAAC,MAAD,CAAO;CAC/B;AAEZ"}
1
+ {"version":3,"file":"index.js","names":["ButtonPrimitive"],"sources":["../../src/components/ui/button-variants.ts","../../src/components/ui/button.tsx","../../src/components/theme-toggle.tsx"],"sourcesContent":["import { cva } from \"class-variance-authority\";\n\nconst buttonVariants = cva(\n \"inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n {\n defaultVariants: {\n size: \"default\",\n variant: \"default\",\n },\n variants: {\n size: {\n default: \"h-9 px-4 py-2 has-[>svg]:px-3\",\n icon: \"size-9\",\n \"icon-lg\": \"size-10\",\n \"icon-sm\": \"size-8\",\n \"icon-xs\": \"size-6 rounded-md [&_svg:not([class*='size-'])]:size-3\",\n lg: \"h-10 rounded-md px-6 has-[>svg]:px-4\",\n sm: \"h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5\",\n xs: \"h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3\",\n },\n variant: {\n default: \"bg-primary text-primary-foreground hover:bg-primary/90\",\n destructive:\n \"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40\",\n ghost: \"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50\",\n link: \"text-primary underline-offset-4 hover:underline\",\n outline:\n \"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50\",\n secondary: \"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n },\n },\n },\n);\n\nexport { buttonVariants };\n","import { Button as ButtonPrimitive } from \"@base-ui/react/button\";\nimport type { VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\n\nimport { cn } from \"../../lib/cn\";\nimport { buttonVariants } from \"./button-variants\";\n\ntype ButtonProps = Omit<React.ComponentProps<typeof ButtonPrimitive>, \"className\"> &\n VariantProps<typeof buttonVariants> & {\n className?: string;\n };\n\nconst Button = ({ className, size = \"default\", variant = \"default\", ...props }: ButtonProps) => {\n return (\n <ButtonPrimitive\n className={cn(buttonVariants({ className, size, variant }))}\n data-size={size}\n data-slot=\"button\"\n data-variant={variant}\n {...props}\n />\n );\n};\n\nexport { Button };\n","import { Moon, Sun } from \"lucide-react\";\nimport { useSyncExternalStore } from \"react\";\n\nimport { Button } from \"./ui/button\";\n\ntype Theme = \"light\" | \"dark\";\n\nconst getStoredTheme = (): Theme | null => {\n if (typeof window === \"undefined\") {\n return null;\n }\n const stored = window.localStorage.getItem(\"theme\");\n if (stored === \"light\" || stored === \"dark\") {\n return stored;\n }\n return null;\n};\n\nconst storedThemeListeners = new Set<() => void>();\n\nconst subscribeStoredTheme = (callback: () => void) => {\n if (typeof window === \"undefined\") {\n return () => {};\n }\n storedThemeListeners.add(callback);\n window.addEventListener(\"storage\", callback);\n return () => {\n storedThemeListeners.delete(callback);\n window.removeEventListener(\"storage\", callback);\n };\n};\n\nconst setStoredTheme = (next: Theme) => {\n window.localStorage.setItem(\"theme\", next);\n for (const listener of storedThemeListeners) {\n listener();\n }\n};\n\nconst getPrefersDark = (): boolean => {\n if (typeof window === \"undefined\") {\n return false;\n }\n return window.matchMedia(\"(prefers-color-scheme: dark)\").matches;\n};\n\nconst subscribePrefersDark = (callback: () => void) => {\n if (typeof window === \"undefined\") {\n return () => {};\n }\n const media = window.matchMedia(\"(prefers-color-scheme: dark)\");\n media.addEventListener(\"change\", callback);\n return () => {\n media.removeEventListener(\"change\", callback);\n };\n};\n\ntype Props = {\n ariaLabel?: string;\n};\n\nconst ThemeToggle = ({ ariaLabel = \"Toggle theme\" }: Props) => {\n const prefersDark = useSyncExternalStore(subscribePrefersDark, getPrefersDark, () => false);\n const override = useSyncExternalStore(subscribeStoredTheme, getStoredTheme, () => null);\n const theme: Theme = override ?? (prefersDark ? \"dark\" : \"light\");\n\n const handleToggleTheme = () => {\n const next: Theme = theme === \"dark\" ? \"light\" : \"dark\";\n document.documentElement.classList.toggle(\"dark\", next === \"dark\");\n setStoredTheme(next);\n };\n\n return (\n <Button aria-label={ariaLabel} onClick={handleToggleTheme} size=\"icon\" variant=\"ghost\">\n {theme === \"dark\" ? <Sun /> : <Moon />}\n </Button>\n );\n};\n\nexport { ThemeToggle };\n"],"mappings":";;;;;;;;AAEA,MAAM,iBAAiB,IACrB,+bACA;CACE,iBAAiB;EACf,MAAM;EACN,SAAS;CACX;CACA,UAAU;EACR,MAAM;GACJ,SAAS;GACT,MAAM;GACN,WAAW;GACX,WAAW;GACX,WAAW;GACX,IAAI;GACJ,IAAI;GACJ,IAAI;EACN;EACA,SAAS;GACP,SAAS;GACT,aACE;GACF,OAAO;GACP,MAAM;GACN,SACE;GACF,WAAW;EACb;CACF;AACF,CACF;;;;ACpBA,MAAM,UAAU,EAAE,WAAW,OAAO,WAAW,UAAU,WAAW,GAAG,YAAyB;CAC9F,OACE,oBAACA,UAAD;EACE,WAAW,GAAG,eAAe;GAAE;GAAW;GAAM;EAAQ,CAAC,CAAC;EAC1D,aAAW;EACX,aAAU;EACV,gBAAc;EACd,GAAI;CACL;AAEL;;;;ACfA,MAAM,uBAAqC;CACzC,IAAI,OAAO,WAAW,aACpB,OAAO;CAET,MAAM,SAAS,OAAO,aAAa,QAAQ,OAAO;CAClD,IAAI,WAAW,WAAW,WAAW,QACnC,OAAO;CAET,OAAO;AACT;AAEA,MAAM,uCAAuB,IAAI,IAAgB;AAEjD,MAAM,wBAAwB,aAAyB;CACrD,IAAI,OAAO,WAAW,aACpB,aAAa,CAAC;CAEhB,qBAAqB,IAAI,QAAQ;CACjC,OAAO,iBAAiB,WAAW,QAAQ;CAC3C,aAAa;EACX,qBAAqB,OAAO,QAAQ;EACpC,OAAO,oBAAoB,WAAW,QAAQ;CAChD;AACF;AAEA,MAAM,kBAAkB,SAAgB;CACtC,OAAO,aAAa,QAAQ,SAAS,IAAI;CACzC,KAAK,MAAM,YAAY,sBACrB,SAAS;AAEb;AAEA,MAAM,uBAAgC;CACpC,IAAI,OAAO,WAAW,aACpB,OAAO;CAET,OAAO,OAAO,WAAW,8BAA8B,CAAC,CAAC;AAC3D;AAEA,MAAM,wBAAwB,aAAyB;CACrD,IAAI,OAAO,WAAW,aACpB,aAAa,CAAC;CAEhB,MAAM,QAAQ,OAAO,WAAW,8BAA8B;CAC9D,MAAM,iBAAiB,UAAU,QAAQ;CACzC,aAAa;EACX,MAAM,oBAAoB,UAAU,QAAQ;CAC9C;AACF;AAMA,MAAM,eAAe,EAAE,YAAY,qBAA4B;CAC7D,MAAM,cAAc,qBAAqB,sBAAsB,sBAAsB,KAAK;CAE1F,MAAM,QADW,qBAAqB,sBAAsB,sBAAsB,IACtD,MAAM,cAAc,SAAS;CAEzD,MAAM,0BAA0B;EAC9B,MAAM,OAAc,UAAU,SAAS,UAAU;EACjD,SAAS,gBAAgB,UAAU,OAAO,QAAQ,SAAS,MAAM;EACjE,eAAe,IAAI;CACrB;CAEA,OACE,oBAAC,QAAD;EAAQ,cAAY;EAAW,SAAS;EAAmB,MAAK;EAAO,SAAQ;YAC5E,UAAU,SAAS,oBAAC,KAAD,CAAM,KAAI,oBAAC,MAAD,CAAO;CAC/B;AAEZ"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/content/index.ts"],"sourcesContent":["import { z } from \"zod\";\n\nconst postSchema = z.object({\n author: z.string().optional(),\n categories: z.array(z.string()).default([]),\n cover: z.string().optional(),\n coverAlt: z.string().optional(),\n description: z.string().default(\"\"),\n draft: z.boolean().default(false),\n heroImageHeight: z.number().int().optional(),\n heroImageUrl: z.url().optional(),\n heroImageWidth: z.number().int().optional(),\n pubDate: z.coerce.date(),\n seo: z\n .object({\n canonical_url: z.string().optional(),\n })\n .optional(),\n series: z.string().optional(),\n slug: z.string().optional(),\n status: z.string().optional(),\n tags: z.array(z.string()).default([]),\n title: z.string().min(1),\n updatedDate: z.coerce.date().optional(),\n});\n\nconst tagSchema = z.object({\n description: z.string().optional(),\n name: z.string().min(1),\n});\n\nconst authorSchema = z.object({\n avatar: z.string().optional(),\n bio: z.string().optional(),\n name: z.string().min(1),\n url: z.url().optional(),\n});\n\ntype Post = z.infer<typeof postSchema>;\ntype Tag = z.infer<typeof tagSchema>;\ntype Author = z.infer<typeof authorSchema>;\n\n// One source of truth for the draft guard so the loader contract stays in sync\n// with the consumer-side check (`data.status !== \"DRAFT\" && !data.draft`).\nconst notDraft = (post: { data: { draft?: boolean; status?: string } }): boolean =>\n post.data.status !== \"DRAFT\" && post.data.draft !== true;\n\n// Typed structurally instead of against `CollectionEntry` so this module never\n// imports `astro:content`: that is a virtual module tsdown cannot resolve.\nconst byPubDateDesc = (a: { data: { pubDate: Date } }, b: { data: { pubDate: Date } }): number =>\n b.data.pubDate.valueOf() - a.data.pubDate.valueOf();\n\nexport { authorSchema, byPubDateDesc, notDraft, postSchema, tagSchema };\nexport type { Author, Post, Tag };\n"],"mappings":";;;;;;;;;;;AAEA,MAAM,aAAa,EAAE,OAAO;CAC1B,QAAQ,EAAE,OAAO,CAAC,CAAC,SAAS;CAC5B,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;CAC1C,OAAO,EAAE,OAAO,CAAC,CAAC,SAAS;CAC3B,UAAU,EAAE,OAAO,CAAC,CAAC,SAAS;CAC9B,aAAa,EAAE,OAAO,CAAC,CAAC,QAAQ,EAAE;CAClC,OAAO,EAAE,QAAQ,CAAC,CAAC,QAAQ,KAAK;CAChC,iBAAiB,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS;CAC3C,cAAc,EAAE,IAAI,CAAC,CAAC,SAAS;CAC/B,gBAAgB,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS;CAC1C,SAAS,EAAE,OAAO,KAAK;CACvB,KAAK,EACF,OAAO,EACN,eAAe,EAAE,OAAO,CAAC,CAAC,SAAS,EACrC,CAAC,CAAC,CACD,SAAS;CACZ,QAAQ,EAAE,OAAO,CAAC,CAAC,SAAS;CAC5B,MAAM,EAAE,OAAO,CAAC,CAAC,SAAS;CAC1B,QAAQ,EAAE,OAAO,CAAC,CAAC,SAAS;CAC5B,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;CACpC,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CACvB,aAAa,EAAE,OAAO,KAAK,CAAC,CAAC,SAAS;AACxC,CAAC;AAED,MAAM,YAAY,EAAE,OAAO;CACzB,aAAa,EAAE,OAAO,CAAC,CAAC,SAAS;CACjC,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;AACxB,CAAC;AAED,MAAM,eAAe,EAAE,OAAO;CAC5B,QAAQ,EAAE,OAAO,CAAC,CAAC,SAAS;CAC5B,KAAK,EAAE,OAAO,CAAC,CAAC,SAAS;CACzB,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CACtB,KAAK,EAAE,IAAI,CAAC,CAAC,SAAS;AACxB,CAAC;AAQD,MAAM,YAAY,SAChB,KAAK,KAAK,WAAW,WAAW,KAAK,KAAK,UAAU;AAItD,MAAM,iBAAiB,GAAgC,MACrD,EAAE,KAAK,QAAQ,QAAQ,IAAI,EAAE,KAAK,QAAQ,QAAQ"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/content/index.ts"],"sourcesContent":["import { z } from \"zod\";\n\nconst postSchema = z.object({\n author: z.string().optional(),\n categories: z.array(z.string()).default([]),\n cover: z.string().optional(),\n coverAlt: z.string().optional(),\n description: z.string().default(\"\"),\n draft: z.boolean().default(false),\n heroImageHeight: z.number().int().optional(),\n heroImageUrl: z.url().optional(),\n heroImageWidth: z.number().int().optional(),\n pubDate: z.coerce.date(),\n seo: z\n .object({\n canonical_url: z.string().optional(),\n })\n .optional(),\n series: z.string().optional(),\n slug: z.string().optional(),\n status: z.string().optional(),\n tags: z.array(z.string()).default([]),\n title: z.string().min(1),\n updatedDate: z.coerce.date().optional(),\n});\n\nconst tagSchema = z.object({\n description: z.string().optional(),\n name: z.string().min(1),\n});\n\nconst authorSchema = z.object({\n avatar: z.string().optional(),\n bio: z.string().optional(),\n name: z.string().min(1),\n url: z.url().optional(),\n});\n\ntype Post = z.infer<typeof postSchema>;\ntype Tag = z.infer<typeof tagSchema>;\ntype Author = z.infer<typeof authorSchema>;\n\nconst notDraft = (post: { data: { draft?: boolean; status?: string } }): boolean =>\n post.data.status !== \"DRAFT\" && post.data.draft !== true;\n\nconst byPubDateDesc = (a: { data: { pubDate: Date } }, b: { data: { pubDate: Date } }): number =>\n b.data.pubDate.valueOf() - a.data.pubDate.valueOf();\n\nexport { authorSchema, byPubDateDesc, notDraft, postSchema, tagSchema };\nexport type { Author, Post, Tag };\n"],"mappings":";;;;;;;;;;;AAEA,MAAM,aAAa,EAAE,OAAO;CAC1B,QAAQ,EAAE,OAAO,CAAC,CAAC,SAAS;CAC5B,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;CAC1C,OAAO,EAAE,OAAO,CAAC,CAAC,SAAS;CAC3B,UAAU,EAAE,OAAO,CAAC,CAAC,SAAS;CAC9B,aAAa,EAAE,OAAO,CAAC,CAAC,QAAQ,EAAE;CAClC,OAAO,EAAE,QAAQ,CAAC,CAAC,QAAQ,KAAK;CAChC,iBAAiB,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS;CAC3C,cAAc,EAAE,IAAI,CAAC,CAAC,SAAS;CAC/B,gBAAgB,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS;CAC1C,SAAS,EAAE,OAAO,KAAK;CACvB,KAAK,EACF,OAAO,EACN,eAAe,EAAE,OAAO,CAAC,CAAC,SAAS,EACrC,CAAC,CAAC,CACD,SAAS;CACZ,QAAQ,EAAE,OAAO,CAAC,CAAC,SAAS;CAC5B,MAAM,EAAE,OAAO,CAAC,CAAC,SAAS;CAC1B,QAAQ,EAAE,OAAO,CAAC,CAAC,SAAS;CAC5B,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;CACpC,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CACvB,aAAa,EAAE,OAAO,KAAK,CAAC,CAAC,SAAS;AACxC,CAAC;AAED,MAAM,YAAY,EAAE,OAAO;CACzB,aAAa,EAAE,OAAO,CAAC,CAAC,SAAS;CACjC,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;AACxB,CAAC;AAED,MAAM,eAAe,EAAE,OAAO;CAC5B,QAAQ,EAAE,OAAO,CAAC,CAAC,SAAS;CAC5B,KAAK,EAAE,OAAO,CAAC,CAAC,SAAS;CACzB,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CACtB,KAAK,EAAE,IAAI,CAAC,CAAC,SAAS;AACxB,CAAC;AAMD,MAAM,YAAY,SAChB,KAAK,KAAK,WAAW,WAAW,KAAK,KAAK,UAAU;AAEtD,MAAM,iBAAiB,GAAgC,MACrD,EAAE,KAAK,QAAQ,QAAQ,IAAI,EAAE,KAAK,QAAQ,QAAQ"}
@@ -30,6 +30,7 @@ type PostUrlBuilder = {
30
30
  postUrl: (post: PostLike) => string;
31
31
  };
32
32
  declare const createPostUrl: (categorySlugMap: Record<string, string>, defaultCategory: string) => PostUrlBuilder;
33
+ declare const categoryLabel: (name: string) => string;
33
34
  //#endregion
34
35
  //#region src/lib/reading-time.d.ts
35
36
  declare const readingTime: (input: string) => {
@@ -47,8 +48,8 @@ declare const slugify: (input: string) => string;
47
48
  declare const DEFAULT_TAG_BASE = "/tag";
48
49
  declare const tagUrl: (tag: string, base?: string) => string;
49
50
  declare namespace index_d_exports {
50
- export { DEFAULT_TAG_BASE, PostLike, PostParams, PostUrlBuilder, cn, createPostUrl, formatDate, getRelatedPosts, readingTime, requireEnv, slugify, tagUrl };
51
+ export { DEFAULT_TAG_BASE, PostLike, PostParams, PostUrlBuilder, categoryLabel, cn, createPostUrl, formatDate, getRelatedPosts, readingTime, requireEnv, slugify, tagUrl };
51
52
  }
52
53
  //#endregion
53
- export { requireEnv as a, PostParams as c, getRelatedPosts as d, formatDate as f, slugify as i, PostUrlBuilder as l, DEFAULT_TAG_BASE as n, readingTime as o, cn as p, tagUrl as r, PostLike as s, index_d_exports as t, createPostUrl as u };
54
- //# sourceMappingURL=index-BspVzulR.d.ts.map
54
+ export { requireEnv as a, PostParams as c, createPostUrl as d, getRelatedPosts as f, slugify as i, PostUrlBuilder as l, cn as m, DEFAULT_TAG_BASE as n, readingTime as o, formatDate as p, tagUrl as r, PostLike as s, index_d_exports as t, categoryLabel as u };
55
+ //# sourceMappingURL=index-BHnvZ16j.d.ts.map
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  import { Author, Post, Tag, t as index_d_exports } from "./content/index.js";
2
- import { t as index_d_exports$1 } from "./index-BspVzulR.js";
2
+ import { t as index_d_exports$1 } from "./index-BHnvZ16j.js";
3
3
  export { type Author, type Post, type Tag, index_d_exports as content, index_d_exports$1 as lib };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  import { t as content_exports } from "./content/index.js";
2
- import { t as lib_exports } from "./lib-V-jasC53.js";
2
+ import { t as lib_exports } from "./lib-CBjp3x7a.js";
3
3
 
4
4
  export { content_exports as content, lib_exports as lib };
@@ -1,2 +1,2 @@
1
- import { a as requireEnv, c as PostParams, d as getRelatedPosts, f as formatDate, i as slugify, l as PostUrlBuilder, n as DEFAULT_TAG_BASE, o as readingTime, p as cn, r as tagUrl, s as PostLike, u as createPostUrl } from "../index-BspVzulR.js";
2
- export { DEFAULT_TAG_BASE, type PostLike, type PostParams, type PostUrlBuilder, cn, createPostUrl, formatDate, getRelatedPosts, readingTime, requireEnv, slugify, tagUrl };
1
+ import { a as requireEnv, c as PostParams, d as createPostUrl, f as getRelatedPosts, i as slugify, l as PostUrlBuilder, m as cn, n as DEFAULT_TAG_BASE, o as readingTime, p as formatDate, r as tagUrl, s as PostLike, u as categoryLabel } from "../index-BHnvZ16j.js";
2
+ export { DEFAULT_TAG_BASE, type PostLike, type PostParams, type PostUrlBuilder, categoryLabel, cn, createPostUrl, formatDate, getRelatedPosts, readingTime, requireEnv, slugify, tagUrl };
package/dist/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  import { t as cn } from "../cn-DmzdyV07.js";
2
- import { a as readingTime, c as getRelatedPosts, i as requireEnv, l as formatDate, n as DEFAULT_TAG_BASE, o as createPostUrl, r as tagUrl, s as slugify } from "../lib-V-jasC53.js";
2
+ import { a as readingTime, c as slugify, i as requireEnv, l as getRelatedPosts, n as DEFAULT_TAG_BASE, o as categoryLabel, r as tagUrl, s as createPostUrl, u as formatDate } from "../lib-CBjp3x7a.js";
3
3
 
4
- export { DEFAULT_TAG_BASE, cn, createPostUrl, formatDate, getRelatedPosts, readingTime, requireEnv, slugify, tagUrl };
4
+ export { DEFAULT_TAG_BASE, categoryLabel, cn, createPostUrl, formatDate, getRelatedPosts, readingTime, requireEnv, slugify, tagUrl };
@@ -45,7 +45,7 @@ const getRelatedPosts = (current, all, n) => {
45
45
 
46
46
  //#endregion
47
47
  //#region src/lib/slugify.ts
48
- const slugify = (input) => input.normalize("NFD").replaceAll(/* @__PURE__ */ new RegExp("[̀-ͯ]", "gv"), "").toLowerCase().replaceAll(/* @__PURE__ */ new RegExp("[^a-z0-9\\s\\-]", "gv"), "").trim().replaceAll(/* @__PURE__ */ new RegExp("[\\s\\-]+", "gv"), "-").replaceAll(/* @__PURE__ */ new RegExp("^-+|-+$", "gv"), "");
48
+ const slugify = (input) => input.normalize("NFD").replaceAll(new RegExp("[̀-ͯ]", "gv"), "").toLowerCase().replaceAll(new RegExp("[^a-z0-9\\s\\-]", "gv"), "").trim().replaceAll(new RegExp("[\\s\\-]+", "gv"), "-").replaceAll(new RegExp("^-+|-+$", "gv"), "");
49
49
 
50
50
  //#endregion
51
51
  //#region src/lib/post-url.ts
@@ -76,12 +76,39 @@ const createPostUrl = (categorySlugMap, defaultCategory) => {
76
76
  postUrl
77
77
  };
78
78
  };
79
+ const NAMED_ENTITIES = {
80
+ amp: "&",
81
+ apos: "'",
82
+ gt: ">",
83
+ hellip: "…",
84
+ ldquo: "“",
85
+ lsquo: "‘",
86
+ lt: "<",
87
+ nbsp: "\xA0",
88
+ ndash: "–",
89
+ quot: "\"",
90
+ rdquo: "”",
91
+ rsquo: "’"
92
+ };
93
+ const MAX_CODE_POINT = 1114111;
94
+ const EM_DASH = 8212;
95
+ const HEX_RADIX = 16;
96
+ const ENTITY_PATTERN = new RegExp("&(?:#(?<decimal>\\d+)|#x(?<hex>[\\dA-F]+)|(?<named>[A-Z]+));", "giv");
97
+ const fromCodePoint = (codePoint, entity) => Number.isInteger(codePoint) && codePoint <= MAX_CODE_POINT && codePoint !== EM_DASH ? String.fromCodePoint(codePoint) : entity;
98
+ const decodeEntity = (entity, decimal, hex, named) => {
99
+ if (decimal !== void 0) return fromCodePoint(Number(decimal), entity);
100
+ if (hex !== void 0) return fromCodePoint(Number.parseInt(hex, HEX_RADIX), entity);
101
+ if (named === void 0) return entity;
102
+ const key = named.toLowerCase();
103
+ return Object.hasOwn(NAMED_ENTITIES, key) ? NAMED_ENTITIES[key] ?? entity : entity;
104
+ };
105
+ const categoryLabel = (name) => name.replaceAll(ENTITY_PATTERN, decodeEntity);
79
106
 
80
107
  //#endregion
81
108
  //#region src/lib/reading-time.ts
82
109
  const WORDS_PER_MINUTE = 200;
83
110
  const readingTime = (input) => {
84
- const stripped = input.replaceAll(/* @__PURE__ */ new RegExp("<[^>]*>", "gv"), "").replaceAll(/* @__PURE__ */ new RegExp("[#*_`>\\-]", "gv"), " ").replaceAll(/* @__PURE__ */ new RegExp("\\s+", "gv"), " ").trim();
111
+ const stripped = input.replaceAll(new RegExp("<[^>]*>", "gv"), "").replaceAll(new RegExp("[#*_`>\\-]", "gv"), " ").replaceAll(new RegExp("\\s+", "gv"), " ").trim();
85
112
  const words = stripped.length === 0 ? 0 : stripped.split(" ").length;
86
113
  return {
87
114
  minutes: Math.max(1, Math.ceil(words / WORDS_PER_MINUTE)),
@@ -100,12 +127,13 @@ const requireEnv = (name) => {
100
127
  //#endregion
101
128
  //#region src/lib/tag-url.ts
102
129
  const DEFAULT_TAG_BASE = "/tag";
103
- const tagUrl = (tag, base = DEFAULT_TAG_BASE) => `${base.replace(/* @__PURE__ */ new RegExp("\\/+$", "v"), "")}/${slugify(tag)}/`;
130
+ const tagUrl = (tag, base = DEFAULT_TAG_BASE) => `${base.replace(new RegExp("\\/+$", "v"), "")}/${slugify(tag)}/`;
104
131
 
105
132
  //#endregion
106
133
  //#region src/lib/index.ts
107
134
  var lib_exports = /* @__PURE__ */ __exportAll({
108
135
  DEFAULT_TAG_BASE: () => DEFAULT_TAG_BASE,
136
+ categoryLabel: () => categoryLabel,
109
137
  cn: () => cn,
110
138
  createPostUrl: () => createPostUrl,
111
139
  formatDate: () => formatDate,
@@ -117,5 +145,5 @@ var lib_exports = /* @__PURE__ */ __exportAll({
117
145
  });
118
146
 
119
147
  //#endregion
120
- export { readingTime as a, getRelatedPosts as c, requireEnv as i, formatDate as l, DEFAULT_TAG_BASE as n, createPostUrl as o, tagUrl as r, slugify as s, lib_exports as t };
121
- //# sourceMappingURL=lib-V-jasC53.js.map
148
+ export { readingTime as a, slugify as c, requireEnv as i, getRelatedPosts as l, DEFAULT_TAG_BASE as n, categoryLabel as o, tagUrl as r, createPostUrl as s, lib_exports as t, formatDate as u };
149
+ //# sourceMappingURL=lib-CBjp3x7a.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lib-CBjp3x7a.js","names":[],"sources":["../src/lib/format-date.ts","../src/lib/get-related-posts.ts","../src/lib/slugify.ts","../src/lib/post-url.ts","../src/lib/reading-time.ts","../src/lib/require-env.ts","../src/lib/tag-url.ts","../src/lib/index.ts"],"sourcesContent":["const defaultOptions: Intl.DateTimeFormatOptions = {\n day: \"numeric\",\n month: \"long\",\n timeZone: \"UTC\",\n year: \"numeric\",\n};\n\nconst formatterCache = new Map<string, Intl.DateTimeFormat>();\n\nconst getFormatter = (key: string, locale: string, options: Intl.DateTimeFormatOptions) => {\n let formatter = formatterCache.get(key);\n if (!formatter) {\n formatter = new Intl.DateTimeFormat(locale, options);\n formatterCache.set(key, formatter);\n }\n return formatter;\n};\n\nconst formatDate = (date: Date, locale: string = \"en-US\", options?: Intl.DateTimeFormatOptions) => {\n if (!options) {\n return getFormatter(locale, locale, defaultOptions).format(date);\n }\n const merged = { ...defaultOptions, ...options };\n return getFormatter(`${locale}:${JSON.stringify(merged)}`, locale, merged).format(date);\n};\n\nexport { formatDate };\n","type HasTags = { data: { tags: Array<string> }; id: string };\n\nconst getRelatedPosts = <T extends HasTags>(current: T, all: Array<T>, n: number) => {\n const currentTagSet = new Set(current.data.tags);\n const scored: Array<{ post: T; score: number }> = [];\n\n for (const post of all) {\n if (post.id === current.id) {\n continue;\n }\n let score = 0;\n for (const tag of post.data.tags) {\n if (currentTagSet.has(tag)) {\n score++;\n }\n }\n scored.push({ post, score });\n }\n\n return scored\n .toSorted((x, y) => y.score - x.score)\n .slice(0, n)\n .map(({ post }) => post);\n};\n\nexport { getRelatedPosts };\n","const slugify = (input: string) =>\n input\n .normalize(\"NFD\")\n .replaceAll(/[̀-ͯ]/gv, \"\")\n .toLowerCase()\n .replaceAll(/[^a-z0-9\\s\\-]/gv, \"\")\n .trim()\n .replaceAll(/[\\s\\-]+/gv, \"-\")\n .replaceAll(/^-+|-+$/gv, \"\");\n\nexport { slugify };\n","import { slugify } from \"./slugify\";\n\ntype PostLike = {\n data: {\n categories?: Array<string>;\n seo?: { canonical_url?: string };\n };\n id: string;\n};\n\ntype PostParams = { category: string; slug: string };\n\ntype PostUrlBuilder = {\n postParams: (post: PostLike) => PostParams;\n postUrl: (post: PostLike) => string;\n};\n\nconst createPostUrl = (\n categorySlugMap: Record<string, string>,\n defaultCategory: string,\n): PostUrlBuilder => {\n const postParams = (post: PostLike): PostParams => {\n const canonical = post.data.seo?.canonical_url;\n if (canonical !== undefined && canonical !== \"\") {\n try {\n const segments = new URL(canonical).pathname.split(\"/\").filter(Boolean);\n const category = segments.at(-2);\n const slug = segments.at(-1);\n if (category !== undefined && slug !== undefined) {\n return { category, slug };\n }\n } catch {\n // Malformed canonical_url; fall through to category-derived logic.\n }\n }\n const first = post.data.categories?.at(0);\n const category =\n first !== undefined && first !== \"\"\n ? (categorySlugMap[first] ?? slugify(first))\n : defaultCategory;\n return { category, slug: post.id };\n };\n\n const postUrl = (post: PostLike): string => {\n const { category, slug } = postParams(post);\n return `/${category}/${slug}/`;\n };\n\n return { postParams, postUrl };\n};\n\nconst NAMED_ENTITIES: Record<string, string> = {\n amp: \"&\",\n apos: \"'\",\n gt: \">\",\n hellip: \"\\u2026\",\n ldquo: \"\\u201c\",\n lsquo: \"\\u2018\",\n lt: \"<\",\n nbsp: \"\\u00a0\",\n ndash: \"\\u2013\",\n quot: '\"',\n rdquo: \"\\u201d\",\n rsquo: \"\\u2019\",\n};\n\nconst MAX_CODE_POINT = 1_114_111;\nconst EM_DASH = 8212;\nconst HEX_RADIX = 16;\nconst ENTITY_PATTERN = /&(?:#(?<decimal>\\d+)|#x(?<hex>[\\dA-F]+)|(?<named>[A-Z]+));/giv;\n\nconst fromCodePoint = (codePoint: number, entity: string): string =>\n Number.isInteger(codePoint) && codePoint <= MAX_CODE_POINT && codePoint !== EM_DASH\n ? String.fromCodePoint(codePoint)\n : entity;\n\nconst decodeEntity = (entity: string, decimal?: string, hex?: string, named?: string): string => {\n if (decimal !== undefined) {\n return fromCodePoint(Number(decimal), entity);\n }\n if (hex !== undefined) {\n return fromCodePoint(Number.parseInt(hex, HEX_RADIX), entity);\n }\n if (named === undefined) {\n return entity;\n }\n const key = named.toLowerCase();\n return Object.hasOwn(NAMED_ENTITIES, key) ? (NAMED_ENTITIES[key] ?? entity) : entity;\n};\n\nconst categoryLabel = (name: string): string => name.replaceAll(ENTITY_PATTERN, decodeEntity);\n\nexport type { PostLike, PostParams, PostUrlBuilder };\nexport { categoryLabel, createPostUrl };\n","const WORDS_PER_MINUTE = 200;\n\nconst readingTime = (input: string) => {\n const stripped = input\n .replaceAll(/<[^>]*>/gv, \"\")\n .replaceAll(/[#*_`>\\-]/gv, \" \")\n .replaceAll(/\\s+/gv, \" \")\n .trim();\n const words = stripped.length === 0 ? 0 : stripped.split(\" \").length;\n const minutes = Math.max(1, Math.ceil(words / WORDS_PER_MINUTE));\n return { minutes, words };\n};\n\nexport { readingTime };\n","const requireEnv = (name: string): string => {\n const value = process.env[name];\n if (value === undefined || value === \"\") {\n throw new Error(`Missing required env var: ${name}`);\n }\n return value;\n};\n\nexport { requireEnv };\n","import { slugify } from \"./slugify\";\n\nconst DEFAULT_TAG_BASE = \"/tag\";\n\nconst tagUrl = (tag: string, base: string = DEFAULT_TAG_BASE): string =>\n `${base.replace(/\\/+$/v, \"\")}/${slugify(tag)}/`;\n\nexport { DEFAULT_TAG_BASE, tagUrl };\n","export { cn } from \"./cn\";\nexport { formatDate } from \"./format-date\";\nexport { getRelatedPosts } from \"./get-related-posts\";\nexport type { PostLike, PostParams, PostUrlBuilder } from \"./post-url\";\nexport { categoryLabel, createPostUrl } from \"./post-url\";\nexport { readingTime } from \"./reading-time\";\nexport { requireEnv } from \"./require-env\";\nexport { slugify } from \"./slugify\";\nexport { DEFAULT_TAG_BASE, tagUrl } from \"./tag-url\";\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro-awesomeness",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "Astro 6/7 blog theme with shadcn primitives and a Tailwind v4 preset.",
5
5
  "keywords": [
6
6
  "astro",
@@ -54,14 +54,14 @@
54
54
  "dependencies": {
55
55
  "@base-ui/react": "^1.6.0",
56
56
  "class-variance-authority": "^0.7.1",
57
- "cnfast": "^0.0.8",
58
- "lucide-react": "^1.24.0",
57
+ "cnfast": "^0.1.0",
58
+ "lucide-react": "^1.31.0",
59
59
  "tw-animate-css": "^1.4.0",
60
60
  "zod": "^4.4.3"
61
61
  },
62
62
  "devDependencies": {
63
63
  "@tailwindcss/postcss": "^4.3.2",
64
- "@types/node": "^26.1.1",
64
+ "@types/node": "^26.2.0",
65
65
  "@types/react": "^19.2.17",
66
66
  "@types/react-dom": "^19.2.3",
67
67
  "@vitest/coverage-v8": "^4.1.10",
@@ -23,7 +23,6 @@ const subscribeStoredTheme = (callback: () => void) => {
23
23
  return () => {};
24
24
  }
25
25
  storedThemeListeners.add(callback);
26
- // "storage" only fires in other tabs; same-tab writes notify via setStoredTheme.
27
26
  window.addEventListener("storage", callback);
28
27
  return () => {
29
28
  storedThemeListeners.delete(callback);
@@ -61,11 +60,6 @@ type Props = {
61
60
  };
62
61
 
63
62
  const ThemeToggle = ({ ariaLabel = "Toggle theme" }: Props) => {
64
- // useSyncExternalStore avoids hydration mismatch on both reads: the server
65
- // snapshots (false / null) match the server HTML, then React re-syncs to the
66
- // real values right after hydration. Reading localStorage during the initial
67
- // render instead (e.g. in a useState initializer) makes React 19 log a
68
- // recoverable hydration error for every visitor with a stored theme.
69
63
  const prefersDark = useSyncExternalStore(subscribePrefersDark, getPrefersDark, () => false);
70
64
  const override = useSyncExternalStore(subscribeStoredTheme, getStoredTheme, () => null);
71
65
  const theme: Theme = override ?? (prefersDark ? "dark" : "light");
@@ -10,7 +10,6 @@ type ButtonProps = Omit<React.ComponentProps<typeof ButtonPrimitive>, "className
10
10
  className?: string;
11
11
  };
12
12
 
13
- // React 19 passes `ref` as a regular prop, so no forwardRef wrapper is needed.
14
13
  const Button = ({ className, size = "default", variant = "default", ...props }: ButtonProps) => {
15
14
  return (
16
15
  <ButtonPrimitive
@@ -40,13 +40,9 @@ type Post = z.infer<typeof postSchema>;
40
40
  type Tag = z.infer<typeof tagSchema>;
41
41
  type Author = z.infer<typeof authorSchema>;
42
42
 
43
- // One source of truth for the draft guard so the loader contract stays in sync
44
- // with the consumer-side check (`data.status !== "DRAFT" && !data.draft`).
45
43
  const notDraft = (post: { data: { draft?: boolean; status?: string } }): boolean =>
46
44
  post.data.status !== "DRAFT" && post.data.draft !== true;
47
45
 
48
- // Typed structurally instead of against `CollectionEntry` so this module never
49
- // imports `astro:content`: that is a virtual module tsdown cannot resolve.
50
46
  const byPubDateDesc = (a: { data: { pubDate: Date } }, b: { data: { pubDate: Date } }): number =>
51
47
  b.data.pubDate.valueOf() - a.data.pubDate.valueOf();
52
48
 
@@ -33,9 +33,6 @@ const {
33
33
  colorScheme = "auto",
34
34
  } = Astro.props;
35
35
 
36
- // Emitted through set:html rather than as literal script children so the two
37
- // branches stay one <script> tag; Astro does not evaluate expressions inside
38
- // script contents.
39
36
  const colorSchemeScript = colorScheme === "auto"
40
37
  ? `(() => {
41
38
  const stored = localStorage.getItem("theme");
@@ -17,8 +17,6 @@ const getFormatter = (key: string, locale: string, options: Intl.DateTimeFormatO
17
17
  };
18
18
 
19
19
  const formatDate = (date: Date, locale: string = "en-US", options?: Intl.DateTimeFormatOptions) => {
20
- // Default-option calls (every blog passes only (date, locale)) key on locale
21
- // alone, skipping the JSON.stringify the custom-option path needs.
22
20
  if (!options) {
23
21
  return getFormatter(locale, locale, defaultOptions).format(date);
24
22
  }
package/src/lib/index.ts CHANGED
@@ -2,7 +2,7 @@ export { cn } from "./cn";
2
2
  export { formatDate } from "./format-date";
3
3
  export { getRelatedPosts } from "./get-related-posts";
4
4
  export type { PostLike, PostParams, PostUrlBuilder } from "./post-url";
5
- export { createPostUrl } from "./post-url";
5
+ export { categoryLabel, createPostUrl } from "./post-url";
6
6
  export { readingTime } from "./reading-time";
7
7
  export { requireEnv } from "./require-env";
8
8
  export { slugify } from "./slugify";
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, it } from "vitest";
2
2
 
3
- import { createPostUrl } from "./post-url";
3
+ import { categoryLabel, createPostUrl } from "./post-url";
4
4
 
5
5
  const map = { "Aluguel de Espaços": "aluguel-de-espacos", Arquitetura: "arquitetura" };
6
6
  const DEFAULT = "sem-categoria";
@@ -48,3 +48,53 @@ describe("createPostUrl", () => {
48
48
  expect(postParams(post)).toEqual({ category: "arquitetura", slug: "x" });
49
49
  });
50
50
  });
51
+
52
+ describe("categoryLabel", () => {
53
+ it("decodes the named entities the WordPress migrations emit", () => {
54
+ expect(categoryLabel("Arte &amp; Design")).toBe("Arte & Design");
55
+ expect(categoryLabel("Casas &hellip;")).toBe("Casas \u2026");
56
+ expect(categoryLabel("&lt;tag&gt; &quot;x&quot; &rsquo;")).toBe('<tag> "x" \u2019');
57
+ expect(categoryLabel("Ano &ndash; Fim")).toBe("Ano \u2013 Fim");
58
+ });
59
+
60
+ it("matches named entities case-insensitively", () => {
61
+ expect(categoryLabel("A &AMP; B")).toBe("A & B");
62
+ });
63
+
64
+ it("decodes decimal and hexadecimal numeric entities", () => {
65
+ expect(categoryLabel("&#65;")).toBe("A");
66
+ expect(categoryLabel("&#x2013;")).toBe("\u2013");
67
+ expect(categoryLabel("&#X2013;")).toBe("\u2013");
68
+ });
69
+
70
+ it("leaves an encoded em-dash encoded in both numeric forms", () => {
71
+ expect(categoryLabel("A &#8212; B")).toBe("A &#8212; B");
72
+ expect(categoryLabel("A &#x2014; B")).toBe("A &#x2014; B");
73
+ });
74
+
75
+ it("leaves out-of-range code points alone instead of throwing", () => {
76
+ expect(() => categoryLabel("&#99999999999;")).not.toThrow();
77
+ expect(categoryLabel("&#99999999999;")).toBe("&#99999999999;");
78
+ expect(categoryLabel("&#x110000;")).toBe("&#x110000;");
79
+ });
80
+
81
+ it("does not resolve inherited Object.prototype keys", () => {
82
+ expect(categoryLabel("&constructor;")).toBe("&constructor;");
83
+ expect(categoryLabel("&toString;")).toBe("&toString;");
84
+ expect(categoryLabel("&hasOwnProperty;")).toBe("&hasOwnProperty;");
85
+ });
86
+
87
+ it("passes unknown entities and plain text through unchanged", () => {
88
+ expect(categoryLabel("&bogus;")).toBe("&bogus;");
89
+ expect(categoryLabel("Arquitetura")).toBe("Arquitetura");
90
+ expect(categoryLabel("")).toBe("");
91
+ });
92
+
93
+ it("stays display-only: decoding a name must not change the URL it resolves to", () => {
94
+ const encodedMap = { "Arte &amp; Design": "arte-e-design" };
95
+ const { postUrl } = createPostUrl(encodedMap, DEFAULT);
96
+ const post = { data: { categories: ["Arte &amp; Design"] }, id: "casa" };
97
+ expect(postUrl(post)).toBe("/arte-e-design/casa/");
98
+ expect(Object.hasOwn(encodedMap, categoryLabel("Arte &amp; Design"))).toBe(false);
99
+ });
100
+ });
@@ -15,11 +15,6 @@ type PostUrlBuilder = {
15
15
  postUrl: (post: PostLike) => string;
16
16
  };
17
17
 
18
- // Per-blog URL builder. Each blog supplies its own pt-BR -> slug map
19
- // (`category-slugs.json`) and the catch-all category used when the post has
20
- // no categories. The canonical_url short-circuit lets WordPress migrations
21
- // keep their original URL pair while everything else falls back to a slugified
22
- // derived category + Astro entry id.
23
18
  const createPostUrl = (
24
19
  categorySlugMap: Record<string, string>,
25
20
  defaultCategory: string,
@@ -54,5 +49,46 @@ const createPostUrl = (
54
49
  return { postParams, postUrl };
55
50
  };
56
51
 
52
+ const NAMED_ENTITIES: Record<string, string> = {
53
+ amp: "&",
54
+ apos: "'",
55
+ gt: ">",
56
+ hellip: "\u2026",
57
+ ldquo: "\u201c",
58
+ lsquo: "\u2018",
59
+ lt: "<",
60
+ nbsp: "\u00a0",
61
+ ndash: "\u2013",
62
+ quot: '"',
63
+ rdquo: "\u201d",
64
+ rsquo: "\u2019",
65
+ };
66
+
67
+ const MAX_CODE_POINT = 1_114_111;
68
+ const EM_DASH = 8212;
69
+ const HEX_RADIX = 16;
70
+ const ENTITY_PATTERN = /&(?:#(?<decimal>\d+)|#x(?<hex>[\dA-F]+)|(?<named>[A-Z]+));/giv;
71
+
72
+ const fromCodePoint = (codePoint: number, entity: string): string =>
73
+ Number.isInteger(codePoint) && codePoint <= MAX_CODE_POINT && codePoint !== EM_DASH
74
+ ? String.fromCodePoint(codePoint)
75
+ : entity;
76
+
77
+ const decodeEntity = (entity: string, decimal?: string, hex?: string, named?: string): string => {
78
+ if (decimal !== undefined) {
79
+ return fromCodePoint(Number(decimal), entity);
80
+ }
81
+ if (hex !== undefined) {
82
+ return fromCodePoint(Number.parseInt(hex, HEX_RADIX), entity);
83
+ }
84
+ if (named === undefined) {
85
+ return entity;
86
+ }
87
+ const key = named.toLowerCase();
88
+ return Object.hasOwn(NAMED_ENTITIES, key) ? (NAMED_ENTITIES[key] ?? entity) : entity;
89
+ };
90
+
91
+ const categoryLabel = (name: string): string => name.replaceAll(ENTITY_PATTERN, decodeEntity);
92
+
57
93
  export type { PostLike, PostParams, PostUrlBuilder };
58
- export { createPostUrl };
94
+ export { categoryLabel, createPostUrl };
@@ -1,6 +1,3 @@
1
- // An unset shell variable reaches a CI build as "" rather than undefined, so an
2
- // empty value has to count as missing or the failure surfaces much later, inside
3
- // the content loader's HTTP call.
4
1
  const requireEnv = (name: string): string => {
5
2
  const value = process.env[name];
6
3
  if (value === undefined || value === "") {
@@ -2,9 +2,6 @@ import { slugify } from "./slugify";
2
2
 
3
3
  const DEFAULT_TAG_BASE = "/tag";
4
4
 
5
- // The trailing slash is not cosmetic. @astrojs/sitemap emits directory-style
6
- // URLs, so a chip pointing at /tag/foo names a URL that is absent from the
7
- // sitemap and only reachable through a redirect.
8
5
  const tagUrl = (tag: string, base: string = DEFAULT_TAG_BASE): string =>
9
6
  `${base.replace(/\/+$/v, "")}/${slugify(tag)}/`;
10
7
 
@@ -1 +0,0 @@
1
- {"version":3,"file":"lib-V-jasC53.js","names":[],"sources":["../src/lib/format-date.ts","../src/lib/get-related-posts.ts","../src/lib/slugify.ts","../src/lib/post-url.ts","../src/lib/reading-time.ts","../src/lib/require-env.ts","../src/lib/tag-url.ts","../src/lib/index.ts"],"sourcesContent":["const defaultOptions: Intl.DateTimeFormatOptions = {\n day: \"numeric\",\n month: \"long\",\n timeZone: \"UTC\",\n year: \"numeric\",\n};\n\nconst formatterCache = new Map<string, Intl.DateTimeFormat>();\n\nconst getFormatter = (key: string, locale: string, options: Intl.DateTimeFormatOptions) => {\n let formatter = formatterCache.get(key);\n if (!formatter) {\n formatter = new Intl.DateTimeFormat(locale, options);\n formatterCache.set(key, formatter);\n }\n return formatter;\n};\n\nconst formatDate = (date: Date, locale: string = \"en-US\", options?: Intl.DateTimeFormatOptions) => {\n // Default-option calls (every blog passes only (date, locale)) key on locale\n // alone, skipping the JSON.stringify the custom-option path needs.\n if (!options) {\n return getFormatter(locale, locale, defaultOptions).format(date);\n }\n const merged = { ...defaultOptions, ...options };\n return getFormatter(`${locale}:${JSON.stringify(merged)}`, locale, merged).format(date);\n};\n\nexport { formatDate };\n","type HasTags = { data: { tags: Array<string> }; id: string };\n\nconst getRelatedPosts = <T extends HasTags>(current: T, all: Array<T>, n: number) => {\n const currentTagSet = new Set(current.data.tags);\n const scored: Array<{ post: T; score: number }> = [];\n\n for (const post of all) {\n if (post.id === current.id) {\n continue;\n }\n let score = 0;\n for (const tag of post.data.tags) {\n if (currentTagSet.has(tag)) {\n score++;\n }\n }\n scored.push({ post, score });\n }\n\n return scored\n .toSorted((x, y) => y.score - x.score)\n .slice(0, n)\n .map(({ post }) => post);\n};\n\nexport { getRelatedPosts };\n","const slugify = (input: string) =>\n input\n .normalize(\"NFD\")\n .replaceAll(/[̀-ͯ]/gv, \"\")\n .toLowerCase()\n .replaceAll(/[^a-z0-9\\s\\-]/gv, \"\")\n .trim()\n .replaceAll(/[\\s\\-]+/gv, \"-\")\n .replaceAll(/^-+|-+$/gv, \"\");\n\nexport { slugify };\n","import { slugify } from \"./slugify\";\n\ntype PostLike = {\n data: {\n categories?: Array<string>;\n seo?: { canonical_url?: string };\n };\n id: string;\n};\n\ntype PostParams = { category: string; slug: string };\n\ntype PostUrlBuilder = {\n postParams: (post: PostLike) => PostParams;\n postUrl: (post: PostLike) => string;\n};\n\n// Per-blog URL builder. Each blog supplies its own pt-BR -> slug map\n// (`category-slugs.json`) and the catch-all category used when the post has\n// no categories. The canonical_url short-circuit lets WordPress migrations\n// keep their original URL pair while everything else falls back to a slugified\n// derived category + Astro entry id.\nconst createPostUrl = (\n categorySlugMap: Record<string, string>,\n defaultCategory: string,\n): PostUrlBuilder => {\n const postParams = (post: PostLike): PostParams => {\n const canonical = post.data.seo?.canonical_url;\n if (canonical !== undefined && canonical !== \"\") {\n try {\n const segments = new URL(canonical).pathname.split(\"/\").filter(Boolean);\n const category = segments.at(-2);\n const slug = segments.at(-1);\n if (category !== undefined && slug !== undefined) {\n return { category, slug };\n }\n } catch {\n // Malformed canonical_url; fall through to category-derived logic.\n }\n }\n const first = post.data.categories?.at(0);\n const category =\n first !== undefined && first !== \"\"\n ? (categorySlugMap[first] ?? slugify(first))\n : defaultCategory;\n return { category, slug: post.id };\n };\n\n const postUrl = (post: PostLike): string => {\n const { category, slug } = postParams(post);\n return `/${category}/${slug}/`;\n };\n\n return { postParams, postUrl };\n};\n\nexport type { PostLike, PostParams, PostUrlBuilder };\nexport { createPostUrl };\n","const WORDS_PER_MINUTE = 200;\n\nconst readingTime = (input: string) => {\n const stripped = input\n .replaceAll(/<[^>]*>/gv, \"\")\n .replaceAll(/[#*_`>\\-]/gv, \" \")\n .replaceAll(/\\s+/gv, \" \")\n .trim();\n const words = stripped.length === 0 ? 0 : stripped.split(\" \").length;\n const minutes = Math.max(1, Math.ceil(words / WORDS_PER_MINUTE));\n return { minutes, words };\n};\n\nexport { readingTime };\n","// An unset shell variable reaches a CI build as \"\" rather than undefined, so an\n// empty value has to count as missing or the failure surfaces much later, inside\n// the content loader's HTTP call.\nconst requireEnv = (name: string): string => {\n const value = process.env[name];\n if (value === undefined || value === \"\") {\n throw new Error(`Missing required env var: ${name}`);\n }\n return value;\n};\n\nexport { requireEnv };\n","import { slugify } from \"./slugify\";\n\nconst DEFAULT_TAG_BASE = \"/tag\";\n\n// The trailing slash is not cosmetic. @astrojs/sitemap emits directory-style\n// URLs, so a chip pointing at /tag/foo names a URL that is absent from the\n// sitemap and only reachable through a redirect.\nconst tagUrl = (tag: string, base: string = DEFAULT_TAG_BASE): string =>\n `${base.replace(/\\/+$/v, \"\")}/${slugify(tag)}/`;\n\nexport { DEFAULT_TAG_BASE, tagUrl };\n","export { cn } from \"./cn\";\nexport { formatDate } from \"./format-date\";\nexport { getRelatedPosts } from \"./get-related-posts\";\nexport type { PostLike, PostParams, PostUrlBuilder } from \"./post-url\";\nexport { createPostUrl } from \"./post-url\";\nexport { readingTime } from \"./reading-time\";\nexport { requireEnv } from \"./require-env\";\nexport { slugify } from \"./slugify\";\nexport { DEFAULT_TAG_BASE, tagUrl } from \"./tag-url\";\n"],"mappings":"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"}