astro-awesomeness 1.2.2 → 2.0.0
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.
- package/dist/components/index.d.ts +3 -9
- package/dist/components/index.js +23 -23
- package/dist/components/index.js.map +1 -1
- package/dist/index-B8l1pSeN.d.ts +84 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/index.d.ts +2 -2
- package/dist/lib/index.js +2 -3
- package/dist/{lib-D7HdNeEs.js → lib-Edmq5pOZ.js} +116 -39
- package/dist/lib-Edmq5pOZ.js.map +1 -0
- package/dist/styles/globals.css +64 -1
- package/package.json +12 -11
- package/src/astro/header.astro +1 -1
- package/src/astro/post-card.astro +0 -8
- package/src/astro/seo.astro +7 -2
- package/src/astro/tag-list.astro +3 -3
- package/src/components/button.tsx +56 -0
- package/src/components/index.ts +2 -3
- package/src/{components → compositions}/theme-toggle.tsx +17 -2
- package/src/layouts/base-layout.astro +8 -1
- package/src/layouts/list-layout.astro +4 -42
- package/src/lib/category-index.test.ts +99 -0
- package/src/lib/category-index.ts +97 -0
- package/src/lib/cn.test.ts +2 -2
- package/src/lib/index.ts +3 -1
- package/src/lib/tag-url.test.ts +32 -1
- package/src/lib/tag-url.ts +15 -1
- package/src/styles/globals.css +64 -1
- package/dist/cn-B2A3VjBM.js +0 -3
- package/dist/index-iwkIePdz.d.ts +0 -57
- package/dist/lib-D7HdNeEs.js.map +0 -1
- package/src/components/ui/button-variants.ts +0 -35
- package/src/components/ui/button.tsx +0 -25
|
@@ -1,19 +1,13 @@
|
|
|
1
1
|
import { Button as Button$1 } from "@base-ui/react/button";
|
|
2
|
-
import * as React from "react";
|
|
3
2
|
import { VariantProps } from "class-variance-authority";
|
|
4
|
-
//#region src/components/
|
|
3
|
+
//#region src/components/button.d.ts
|
|
5
4
|
declare const buttonVariants: (props?: ({
|
|
6
5
|
size?: "default" | "icon" | "icon-lg" | "icon-sm" | "icon-xs" | "lg" | "sm" | "xs" | null | undefined;
|
|
7
6
|
variant?: "default" | "destructive" | "ghost" | "link" | "outline" | "secondary" | null | undefined;
|
|
8
7
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
8
|
+
declare const Button: ({ className, size, variant, ...props }: Button$1.Props & VariantProps<typeof buttonVariants>) => import("react").JSX.Element;
|
|
9
9
|
//#endregion
|
|
10
|
-
//#region src/
|
|
11
|
-
type ButtonProps = Omit<React.ComponentProps<typeof Button$1>, "className"> & VariantProps<typeof buttonVariants> & {
|
|
12
|
-
className?: string;
|
|
13
|
-
};
|
|
14
|
-
declare const Button: ({ className, size, variant, ...props }: ButtonProps) => React.JSX.Element;
|
|
15
|
-
//#endregion
|
|
16
|
-
//#region src/components/theme-toggle.d.ts
|
|
10
|
+
//#region src/compositions/theme-toggle.d.ts
|
|
17
11
|
type Props = {
|
|
18
12
|
ariaLabel?: string;
|
|
19
13
|
};
|
package/dist/components/index.js
CHANGED
|
@@ -1,40 +1,37 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { cn } from "cn";
|
|
2
2
|
import { Button as Button$1 } from "@base-ui/react/button";
|
|
3
|
-
import { useSyncExternalStore } from "react";
|
|
4
3
|
import { cva } from "class-variance-authority";
|
|
5
4
|
import { jsx } from "react/jsx-runtime";
|
|
6
5
|
import { Moon, Sun } from "lucide-react";
|
|
6
|
+
import { useSyncExternalStore } from "react";
|
|
7
7
|
|
|
8
|
-
//#region src/components/
|
|
9
|
-
const buttonVariants = cva("inline-flex shrink-0 items-center justify-center
|
|
8
|
+
//#region src/components/button.tsx
|
|
9
|
+
const buttonVariants = cva("group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", {
|
|
10
10
|
defaultVariants: {
|
|
11
11
|
size: "default",
|
|
12
12
|
variant: "default"
|
|
13
13
|
},
|
|
14
14
|
variants: {
|
|
15
15
|
size: {
|
|
16
|
-
default: "h-
|
|
17
|
-
icon: "size-
|
|
18
|
-
"icon-lg": "size-
|
|
19
|
-
"icon-sm": "size-
|
|
20
|
-
"icon-xs": "size-6 rounded-
|
|
21
|
-
lg: "h-
|
|
22
|
-
sm: "h-
|
|
23
|
-
xs: "h-6 gap-1 rounded-
|
|
16
|
+
default: "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
|
17
|
+
icon: "size-8",
|
|
18
|
+
"icon-lg": "size-9",
|
|
19
|
+
"icon-sm": "size-7 rounded-control in-data-[slot=button-group]:rounded-lg",
|
|
20
|
+
"icon-xs": "size-6 rounded-control-sm in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
|
|
21
|
+
lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
|
22
|
+
sm: "h-7 gap-1 rounded-control px-2.5 text-(length:--text-control) in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
|
|
23
|
+
xs: "h-6 gap-1 rounded-control-sm px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3"
|
|
24
24
|
},
|
|
25
25
|
variant: {
|
|
26
|
-
default: "bg-primary text-primary-foreground hover:bg-primary/
|
|
27
|
-
destructive: "bg-destructive text-
|
|
28
|
-
ghost: "hover:bg-
|
|
26
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/80",
|
|
27
|
+
destructive: "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
|
|
28
|
+
ghost: "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
|
|
29
29
|
link: "text-primary underline-offset-4 hover:underline",
|
|
30
|
-
outline: "border bg-background
|
|
31
|
-
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary
|
|
30
|
+
outline: "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
|
|
31
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary-hover aria-expanded:bg-secondary aria-expanded:text-secondary-foreground"
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
});
|
|
35
|
-
|
|
36
|
-
//#endregion
|
|
37
|
-
//#region src/components/ui/button.tsx
|
|
38
35
|
const Button = ({ className, size = "default", variant = "default", ...props }) => {
|
|
39
36
|
return /* @__PURE__ */ jsx(Button$1, {
|
|
40
37
|
className: cn(buttonVariants({
|
|
@@ -42,15 +39,13 @@ const Button = ({ className, size = "default", variant = "default", ...props })
|
|
|
42
39
|
size,
|
|
43
40
|
variant
|
|
44
41
|
})),
|
|
45
|
-
"data-size": size,
|
|
46
42
|
"data-slot": "button",
|
|
47
|
-
"data-variant": variant,
|
|
48
43
|
...props
|
|
49
44
|
});
|
|
50
45
|
};
|
|
51
46
|
|
|
52
47
|
//#endregion
|
|
53
|
-
//#region src/
|
|
48
|
+
//#region src/compositions/theme-toggle.tsx
|
|
54
49
|
const getStoredTheme = () => {
|
|
55
50
|
if (typeof window === "undefined") return null;
|
|
56
51
|
const stored = window.localStorage.getItem("theme");
|
|
@@ -83,7 +78,11 @@ const subscribePrefersDark = (callback) => {
|
|
|
83
78
|
media.removeEventListener("change", callback);
|
|
84
79
|
};
|
|
85
80
|
};
|
|
81
|
+
const subscribeHydration = () => () => {};
|
|
82
|
+
const getHydratedSnapshot = () => true;
|
|
83
|
+
const getServerHydratedSnapshot = () => false;
|
|
86
84
|
const ThemeToggle = ({ ariaLabel = "Toggle theme" }) => {
|
|
85
|
+
const isHydrated = useSyncExternalStore(subscribeHydration, getHydratedSnapshot, getServerHydratedSnapshot);
|
|
87
86
|
const prefersDark = useSyncExternalStore(subscribePrefersDark, getPrefersDark, () => false);
|
|
88
87
|
const theme = useSyncExternalStore(subscribeStoredTheme, getStoredTheme, () => null) ?? (prefersDark ? "dark" : "light");
|
|
89
88
|
const handleToggleTheme = () => {
|
|
@@ -93,6 +92,7 @@ const ThemeToggle = ({ ariaLabel = "Toggle theme" }) => {
|
|
|
93
92
|
};
|
|
94
93
|
return /* @__PURE__ */ jsx(Button, {
|
|
95
94
|
"aria-label": ariaLabel,
|
|
95
|
+
disabled: !isHydrated,
|
|
96
96
|
onClick: handleToggleTheme,
|
|
97
97
|
size: "icon",
|
|
98
98
|
variant: "ghost",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["ButtonPrimitive"],"sources":["../../src/components/
|
|
1
|
+
{"version":3,"file":"index.js","names":["ButtonPrimitive"],"sources":["../../src/components/button.tsx","../../src/compositions/theme-toggle.tsx"],"sourcesContent":["import { Button as ButtonPrimitive } from \"@base-ui/react/button\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"cn\";\n\nconst buttonVariants = cva(\n \"group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 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:\n \"h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2\",\n icon: \"size-8\",\n \"icon-lg\": \"size-9\",\n \"icon-sm\": \"size-7 rounded-control in-data-[slot=button-group]:rounded-lg\",\n \"icon-xs\":\n \"size-6 rounded-control-sm in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3\",\n lg: \"h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2\",\n sm: \"h-7 gap-1 rounded-control px-2.5 text-(length:--text-control) in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n xs: \"h-6 gap-1 rounded-control-sm px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n },\n variant: {\n default: \"bg-primary text-primary-foreground hover:bg-primary/80\",\n destructive:\n \"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40\",\n ghost:\n \"hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50\",\n link: \"text-primary underline-offset-4 hover:underline\",\n outline:\n \"border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50\",\n secondary:\n \"bg-secondary text-secondary-foreground hover:bg-secondary-hover aria-expanded:bg-secondary aria-expanded:text-secondary-foreground\",\n },\n },\n },\n);\n\nconst Button = ({\n className,\n size = \"default\",\n variant = \"default\",\n ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) => {\n return (\n <ButtonPrimitive\n className={cn(buttonVariants({ className, size, variant }))}\n data-slot=\"button\"\n {...props}\n />\n );\n};\n\nexport { Button, buttonVariants };\n","import { Moon, Sun } from \"lucide-react\";\nimport { useSyncExternalStore } from \"react\";\n\nimport { Button } from \"../components/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 subscribeHydration = () => () => {};\nconst getHydratedSnapshot = () => true;\nconst getServerHydratedSnapshot = () => false;\n\nconst ThemeToggle = ({ ariaLabel = \"Toggle theme\" }: Props) => {\n const isHydrated = useSyncExternalStore(\n subscribeHydration,\n getHydratedSnapshot,\n getServerHydratedSnapshot,\n );\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\n aria-label={ariaLabel}\n disabled={!isHydrated}\n onClick={handleToggleTheme}\n size=\"icon\"\n variant=\"ghost\"\n >\n {theme === \"dark\" ? <Sun /> : <Moon />}\n </Button>\n );\n};\n\nexport { ThemeToggle };\n"],"mappings":";;;;;;;;AAIA,MAAM,iBAAiB,IACrB,8lBACA;CACE,iBAAiB;EACf,MAAM;EACN,SAAS;CACX;CACA,UAAU;EACR,MAAM;GACJ,SACE;GACF,MAAM;GACN,WAAW;GACX,WAAW;GACX,WACE;GACF,IAAI;GACJ,IAAI;GACJ,IAAI;EACN;EACA,SAAS;GACP,SAAS;GACT,aACE;GACF,OACE;GACF,MAAM;GACN,SACE;GACF,WACE;EACJ;CACF;AACF,CACF;AAEA,MAAM,UAAU,EACd,WACA,OAAO,WACP,UAAU,WACV,GAAG,YAC8D;CACjE,OACE,oBAACA,UAAD;EACE,WAAW,GAAG,eAAe;GAAE;GAAW;GAAM;EAAQ,CAAC,CAAC;EAC1D,aAAU;EACV,GAAI;CACL;AAEL;;;;AC9CA,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,iCAAiC,CAAC;AACxC,MAAM,4BAA4B;AAClC,MAAM,kCAAkC;AAExC,MAAM,eAAe,EAAE,YAAY,qBAA4B;CAC7D,MAAM,aAAa,qBACjB,oBACA,qBACA,yBACF;CACA,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;EACE,cAAY;EACZ,UAAU,CAAC;EACX,SAAS;EACT,MAAK;EACL,SAAQ;YAEP,UAAU,SAAS,oBAAC,KAAD,CAAM,KAAI,oBAAC,MAAD,CAAO;CAC/B;AAEZ"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { cn as cn$1 } from "cn";
|
|
2
|
+
//#region src/lib/post-url.d.ts
|
|
3
|
+
type PostLike = {
|
|
4
|
+
data: {
|
|
5
|
+
categories?: Array<string>;
|
|
6
|
+
seo?: {
|
|
7
|
+
canonical_url?: string;
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
id: string;
|
|
11
|
+
};
|
|
12
|
+
type PostParams = {
|
|
13
|
+
category: string;
|
|
14
|
+
slug: string;
|
|
15
|
+
};
|
|
16
|
+
type PostUrlBuilder = {
|
|
17
|
+
postParams: (post: PostLike) => PostParams;
|
|
18
|
+
postUrl: (post: PostLike) => string;
|
|
19
|
+
};
|
|
20
|
+
declare const createPostUrl: (categorySlugMap: Record<string, string>, defaultCategory: string) => PostUrlBuilder;
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/lib/category-index.d.ts
|
|
23
|
+
type CategoryPostLike = PostLike & {
|
|
24
|
+
data: {
|
|
25
|
+
categories: Array<string>;
|
|
26
|
+
pubDate: Date;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
type CategoryBucket<P> = {
|
|
30
|
+
name: string;
|
|
31
|
+
posts: Array<P>;
|
|
32
|
+
slug: string;
|
|
33
|
+
};
|
|
34
|
+
type PostCategory = {
|
|
35
|
+
name: string;
|
|
36
|
+
slug: string;
|
|
37
|
+
};
|
|
38
|
+
type GroupOptions = {
|
|
39
|
+
includeSecondaryCategories?: boolean;
|
|
40
|
+
};
|
|
41
|
+
declare const createCategoryIndex: <P extends CategoryPostLike>(categorySlugMap: Record<string, string>, defaultCategory: string, posts: Array<P>) => {
|
|
42
|
+
categoryName: (slug: string) => string;
|
|
43
|
+
groupByCategory: ({ includeSecondaryCategories }?: GroupOptions) => Array<CategoryBucket<P>>;
|
|
44
|
+
postCategory: (post: PostLike) => PostCategory;
|
|
45
|
+
};
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/lib/category-label.d.ts
|
|
48
|
+
declare const categoryLabel: (name: string) => string;
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/lib/format-date.d.ts
|
|
51
|
+
declare const formatDate: (date: Date, locale?: string, options?: Intl.DateTimeFormatOptions) => string;
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/lib/get-related-posts.d.ts
|
|
54
|
+
type HasTags = {
|
|
55
|
+
data: {
|
|
56
|
+
tags: Array<string>;
|
|
57
|
+
};
|
|
58
|
+
id: string;
|
|
59
|
+
};
|
|
60
|
+
declare const getRelatedPosts: <T extends HasTags>(current: T, all: Array<T>, n: number) => T[];
|
|
61
|
+
//#endregion
|
|
62
|
+
//#region src/lib/reading-time.d.ts
|
|
63
|
+
declare const readingTime: (input: string) => {
|
|
64
|
+
minutes: number;
|
|
65
|
+
words: number;
|
|
66
|
+
};
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/lib/require-env.d.ts
|
|
69
|
+
declare const requireEnv: (name: string) => string;
|
|
70
|
+
//#endregion
|
|
71
|
+
//#region src/lib/slugify.d.ts
|
|
72
|
+
declare const slugify: (input: string) => string;
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region src/lib/tag-url.d.ts
|
|
75
|
+
declare const DEFAULT_TAG_BASE = "/tag";
|
|
76
|
+
declare const tagUrl: (tag: string, base?: string) => string;
|
|
77
|
+
declare const tagLabel: (tag: string) => string;
|
|
78
|
+
declare const uniqueTags: (tags: Array<string>) => Array<string>;
|
|
79
|
+
declare namespace index_d_exports {
|
|
80
|
+
export { CategoryBucket, CategoryPostLike, DEFAULT_TAG_BASE, PostCategory, PostLike, PostParams, PostUrlBuilder, categoryLabel, cn$1 as cn, createCategoryIndex, createPostUrl, formatDate, getRelatedPosts, readingTime, requireEnv, slugify, tagLabel, tagUrl, uniqueTags };
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
export { PostParams as _, uniqueTags as a, cn$1 as b, readingTime as c, categoryLabel as d, CategoryBucket as f, PostLike as g, createCategoryIndex as h, tagUrl as i, getRelatedPosts as l, PostCategory as m, DEFAULT_TAG_BASE as n, slugify as o, CategoryPostLike as p, tagLabel as r, requireEnv as s, index_d_exports as t, formatDate as u, PostUrlBuilder as v, createPostUrl as y };
|
|
84
|
+
//# sourceMappingURL=index-B8l1pSeN.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-
|
|
2
|
+
import { t as index_d_exports$1 } from "./index-B8l1pSeN.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
package/dist/lib/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
export { DEFAULT_TAG_BASE, type PostLike, type PostParams, type PostUrlBuilder, categoryLabel, cn, createPostUrl, formatDate, getRelatedPosts, readingTime, requireEnv, slugify, tagUrl };
|
|
1
|
+
import { _ as PostParams, a as uniqueTags, b as cn, c as readingTime, d as categoryLabel, f as CategoryBucket, g as PostLike, h as createCategoryIndex, i as tagUrl, l as getRelatedPosts, m as PostCategory, n as DEFAULT_TAG_BASE, o as slugify, p as CategoryPostLike, r as tagLabel, s as requireEnv, u as formatDate, v as PostUrlBuilder, y as createPostUrl } from "../index-B8l1pSeN.js";
|
|
2
|
+
export { type CategoryBucket, type CategoryPostLike, DEFAULT_TAG_BASE, type PostCategory, type PostLike, type PostParams, type PostUrlBuilder, categoryLabel, cn, createCategoryIndex, createPostUrl, formatDate, getRelatedPosts, readingTime, requireEnv, slugify, tagLabel, tagUrl, uniqueTags };
|
package/dist/lib/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import {
|
|
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, u as categoryLabel } from "../lib-D7HdNeEs.js";
|
|
1
|
+
import { a as uniqueTags, c as getRelatedPosts, d as createPostUrl, f as slugify, i as tagUrl, l as formatDate, m as cn, n as DEFAULT_TAG_BASE, o as requireEnv, p as categoryLabel, r as tagLabel, s as readingTime, u as createCategoryIndex } from "../lib-Edmq5pOZ.js";
|
|
3
2
|
|
|
4
|
-
export { DEFAULT_TAG_BASE, categoryLabel, cn, createPostUrl, formatDate, getRelatedPosts, readingTime, requireEnv, slugify, tagUrl };
|
|
3
|
+
export { DEFAULT_TAG_BASE, categoryLabel, cn, createCategoryIndex, createPostUrl, formatDate, getRelatedPosts, readingTime, requireEnv, slugify, tagLabel, tagUrl, uniqueTags };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-w6R9maHv.js";
|
|
2
|
-
import {
|
|
2
|
+
import { byPubDateDesc } from "./content/index.js";
|
|
3
|
+
import { cn as cn$1 } from "cn";
|
|
3
4
|
|
|
4
5
|
//#region src/lib/category-label.ts
|
|
5
6
|
const NAMED_ENTITIES = {
|
|
@@ -32,6 +33,103 @@ const decodeEntity = (entity, decimal, hex, named) => {
|
|
|
32
33
|
};
|
|
33
34
|
const categoryLabel = (name) => name.replaceAll(ENTITY_PATTERN, decodeEntity);
|
|
34
35
|
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/lib/slugify.ts
|
|
38
|
+
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"), "");
|
|
39
|
+
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/lib/post-url.ts
|
|
42
|
+
const createPostUrl = (categorySlugMap, defaultCategory) => {
|
|
43
|
+
const postParams = (post) => {
|
|
44
|
+
const canonical = post.data.seo?.canonical_url;
|
|
45
|
+
if (canonical !== void 0 && canonical !== "") try {
|
|
46
|
+
const segments = new URL(canonical).pathname.split("/").filter(Boolean);
|
|
47
|
+
const category = segments.at(-2);
|
|
48
|
+
const slug = segments.at(-1);
|
|
49
|
+
if (category !== void 0 && slug !== void 0) return {
|
|
50
|
+
category,
|
|
51
|
+
slug
|
|
52
|
+
};
|
|
53
|
+
} catch {}
|
|
54
|
+
const first = post.data.categories?.at(0);
|
|
55
|
+
return {
|
|
56
|
+
category: first !== void 0 && first !== "" ? categorySlugMap[first] ?? slugify(first) : defaultCategory,
|
|
57
|
+
slug: post.id
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
const postUrl = (post) => {
|
|
61
|
+
const { category, slug } = postParams(post);
|
|
62
|
+
return `/${category}/${slug}/`;
|
|
63
|
+
};
|
|
64
|
+
return {
|
|
65
|
+
postParams,
|
|
66
|
+
postUrl
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
//#endregion
|
|
71
|
+
//#region src/lib/category-index.ts
|
|
72
|
+
const humanize = (slug) => {
|
|
73
|
+
const words = slug.replaceAll("-", " ");
|
|
74
|
+
return words.charAt(0).toUpperCase() + words.slice(1);
|
|
75
|
+
};
|
|
76
|
+
const createCategoryIndex = (categorySlugMap, defaultCategory, posts) => {
|
|
77
|
+
const { postParams } = createPostUrl(categorySlugMap, defaultCategory);
|
|
78
|
+
const categorySlug = (name) => categorySlugMap[name] ?? slugify(name);
|
|
79
|
+
const namesBySlug = /* @__PURE__ */ new Map();
|
|
80
|
+
const register = (slug, name) => {
|
|
81
|
+
namesBySlug.set(slug, (namesBySlug.get(slug) ?? /* @__PURE__ */ new Set()).add(name));
|
|
82
|
+
};
|
|
83
|
+
for (const post of posts) {
|
|
84
|
+
const { category } = postParams(post);
|
|
85
|
+
const names = post.data.categories;
|
|
86
|
+
for (const name of names) register(categorySlug(name), name);
|
|
87
|
+
const first = names[0];
|
|
88
|
+
if (first !== void 0 && !names.some((name) => categorySlug(name) === category)) register(category, first);
|
|
89
|
+
}
|
|
90
|
+
const categoryName = (slug) => {
|
|
91
|
+
const names = [...namesBySlug.get(slug) ?? []];
|
|
92
|
+
const native = names.find((name) => slugify(categoryLabel(name)) === slug);
|
|
93
|
+
const mapped = names.find((name) => categorySlug(name) === slug);
|
|
94
|
+
const name = native ?? mapped ?? names[0];
|
|
95
|
+
return name === void 0 ? humanize(slug) : categoryLabel(name);
|
|
96
|
+
};
|
|
97
|
+
const postCategory = (post) => {
|
|
98
|
+
const { category } = postParams(post);
|
|
99
|
+
return {
|
|
100
|
+
name: categoryName(category),
|
|
101
|
+
slug: category
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
const groupByCategory = ({ includeSecondaryCategories = false } = {}) => {
|
|
105
|
+
const postsBySlug = /* @__PURE__ */ new Map();
|
|
106
|
+
const add = (slug, post) => {
|
|
107
|
+
const bucket = postsBySlug.get(slug);
|
|
108
|
+
if (bucket) bucket.push(post);
|
|
109
|
+
else postsBySlug.set(slug, [post]);
|
|
110
|
+
};
|
|
111
|
+
for (const post of posts) {
|
|
112
|
+
const { category } = postParams(post);
|
|
113
|
+
add(category, post);
|
|
114
|
+
if (!includeSecondaryCategories) continue;
|
|
115
|
+
for (const name of post.data.categories) {
|
|
116
|
+
const slug = categorySlug(name);
|
|
117
|
+
if (slug !== category) add(slug, post);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return [...postsBySlug].map(([slug, bucketPosts]) => ({
|
|
121
|
+
name: categoryName(slug),
|
|
122
|
+
posts: bucketPosts.toSorted(byPubDateDesc),
|
|
123
|
+
slug
|
|
124
|
+
}));
|
|
125
|
+
};
|
|
126
|
+
return {
|
|
127
|
+
categoryName,
|
|
128
|
+
groupByCategory,
|
|
129
|
+
postCategory
|
|
130
|
+
};
|
|
131
|
+
};
|
|
132
|
+
|
|
35
133
|
//#endregion
|
|
36
134
|
//#region src/lib/format-date.ts
|
|
37
135
|
const defaultOptions = {
|
|
@@ -75,40 +173,6 @@ const getRelatedPosts = (current, all, n) => {
|
|
|
75
173
|
return scored.toSorted((x, y) => y.score - x.score).slice(0, n).map(({ post }) => post);
|
|
76
174
|
};
|
|
77
175
|
|
|
78
|
-
//#endregion
|
|
79
|
-
//#region src/lib/slugify.ts
|
|
80
|
-
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"), "");
|
|
81
|
-
|
|
82
|
-
//#endregion
|
|
83
|
-
//#region src/lib/post-url.ts
|
|
84
|
-
const createPostUrl = (categorySlugMap, defaultCategory) => {
|
|
85
|
-
const postParams = (post) => {
|
|
86
|
-
const canonical = post.data.seo?.canonical_url;
|
|
87
|
-
if (canonical !== void 0 && canonical !== "") try {
|
|
88
|
-
const segments = new URL(canonical).pathname.split("/").filter(Boolean);
|
|
89
|
-
const category = segments.at(-2);
|
|
90
|
-
const slug = segments.at(-1);
|
|
91
|
-
if (category !== void 0 && slug !== void 0) return {
|
|
92
|
-
category,
|
|
93
|
-
slug
|
|
94
|
-
};
|
|
95
|
-
} catch {}
|
|
96
|
-
const first = post.data.categories?.at(0);
|
|
97
|
-
return {
|
|
98
|
-
category: first !== void 0 && first !== "" ? categorySlugMap[first] ?? slugify(first) : defaultCategory,
|
|
99
|
-
slug: post.id
|
|
100
|
-
};
|
|
101
|
-
};
|
|
102
|
-
const postUrl = (post) => {
|
|
103
|
-
const { category, slug } = postParams(post);
|
|
104
|
-
return `/${category}/${slug}/`;
|
|
105
|
-
};
|
|
106
|
-
return {
|
|
107
|
-
postParams,
|
|
108
|
-
postUrl
|
|
109
|
-
};
|
|
110
|
-
};
|
|
111
|
-
|
|
112
176
|
//#endregion
|
|
113
177
|
//#region src/lib/reading-time.ts
|
|
114
178
|
const WORDS_PER_MINUTE = 200;
|
|
@@ -133,22 +197,35 @@ const requireEnv = (name) => {
|
|
|
133
197
|
//#region src/lib/tag-url.ts
|
|
134
198
|
const DEFAULT_TAG_BASE = "/tag";
|
|
135
199
|
const tagUrl = (tag, base = DEFAULT_TAG_BASE) => `${base.replace(new RegExp("\\/+$", "v"), "")}/${slugify(tag)}/`;
|
|
200
|
+
const tagLabel = (tag) => tag.replace(new RegExp("^#", "v"), "");
|
|
201
|
+
const uniqueTags = (tags) => {
|
|
202
|
+
const seen = /* @__PURE__ */ new Set();
|
|
203
|
+
return tags.filter((tag) => {
|
|
204
|
+
const slug = slugify(tag);
|
|
205
|
+
if (seen.has(slug)) return false;
|
|
206
|
+
seen.add(slug);
|
|
207
|
+
return true;
|
|
208
|
+
});
|
|
209
|
+
};
|
|
136
210
|
|
|
137
211
|
//#endregion
|
|
138
212
|
//#region src/lib/index.ts
|
|
139
213
|
var lib_exports = /* @__PURE__ */ __exportAll({
|
|
140
214
|
DEFAULT_TAG_BASE: () => DEFAULT_TAG_BASE,
|
|
141
215
|
categoryLabel: () => categoryLabel,
|
|
142
|
-
cn: () => cn,
|
|
216
|
+
cn: () => cn$1,
|
|
217
|
+
createCategoryIndex: () => createCategoryIndex,
|
|
143
218
|
createPostUrl: () => createPostUrl,
|
|
144
219
|
formatDate: () => formatDate,
|
|
145
220
|
getRelatedPosts: () => getRelatedPosts,
|
|
146
221
|
readingTime: () => readingTime,
|
|
147
222
|
requireEnv: () => requireEnv,
|
|
148
223
|
slugify: () => slugify,
|
|
149
|
-
|
|
224
|
+
tagLabel: () => tagLabel,
|
|
225
|
+
tagUrl: () => tagUrl,
|
|
226
|
+
uniqueTags: () => uniqueTags
|
|
150
227
|
});
|
|
151
228
|
|
|
152
229
|
//#endregion
|
|
153
|
-
export {
|
|
154
|
-
//# sourceMappingURL=lib-
|
|
230
|
+
export { uniqueTags as a, getRelatedPosts as c, createPostUrl as d, slugify as f, tagUrl as i, formatDate as l, cn$1 as m, DEFAULT_TAG_BASE as n, requireEnv as o, categoryLabel as p, tagLabel as r, readingTime as s, lib_exports as t, createCategoryIndex as u };
|
|
231
|
+
//# sourceMappingURL=lib-Edmq5pOZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lib-Edmq5pOZ.js","names":[],"sources":["../src/lib/category-label.ts","../src/lib/slugify.ts","../src/lib/post-url.ts","../src/lib/category-index.ts","../src/lib/format-date.ts","../src/lib/get-related-posts.ts","../src/lib/reading-time.ts","../src/lib/require-env.ts","../src/lib/tag-url.ts","../src/lib/index.ts"],"sourcesContent":["const NAMED_ENTITIES = {\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} as const;\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 isNamedEntity = (key: string): key is keyof typeof NAMED_ENTITIES =>\n Object.hasOwn(NAMED_ENTITIES, key);\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 if (!isNamedEntity(key)) {\n return entity;\n }\n return NAMED_ENTITIES[key];\n};\n\nconst categoryLabel = (name: string): string => name.replaceAll(ENTITY_PATTERN, decodeEntity);\n\nexport { categoryLabel };\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\nexport type { PostLike, PostParams, PostUrlBuilder };\nexport { createPostUrl };\n","import { byPubDateDesc } from \"../content/index\";\nimport { categoryLabel } from \"./category-label\";\nimport type { PostLike } from \"./post-url\";\nimport { createPostUrl } from \"./post-url\";\nimport { slugify } from \"./slugify\";\n\ntype CategoryPostLike = PostLike & { data: { categories: Array<string>; pubDate: Date } };\n\ntype CategoryBucket<P> = { name: string; posts: Array<P>; slug: string };\n\ntype PostCategory = { name: string; slug: string };\n\ntype GroupOptions = {\n includeSecondaryCategories?: boolean;\n};\n\nconst humanize = (slug: string): string => {\n const words = slug.replaceAll(\"-\", \" \");\n return words.charAt(0).toUpperCase() + words.slice(1);\n};\n\nconst createCategoryIndex = <P extends CategoryPostLike>(\n categorySlugMap: Record<string, string>,\n defaultCategory: string,\n posts: Array<P>,\n) => {\n const { postParams } = createPostUrl(categorySlugMap, defaultCategory);\n const categorySlug = (name: string) => categorySlugMap[name] ?? slugify(name);\n\n const namesBySlug = new Map<string, Set<string>>();\n const register = (slug: string, name: string) => {\n namesBySlug.set(slug, (namesBySlug.get(slug) ?? new Set()).add(name));\n };\n for (const post of posts) {\n const { category } = postParams(post);\n const names = post.data.categories;\n for (const name of names) {\n register(categorySlug(name), name);\n }\n const first = names[0];\n if (first !== undefined && !names.some((name) => categorySlug(name) === category)) {\n register(category, first);\n }\n }\n\n const categoryName = (slug: string): string => {\n const names = [...(namesBySlug.get(slug) ?? [])];\n const native = names.find((name) => slugify(categoryLabel(name)) === slug);\n const mapped = names.find((name) => categorySlug(name) === slug);\n const name = native ?? mapped ?? names[0];\n return name === undefined ? humanize(slug) : categoryLabel(name);\n };\n\n const postCategory = (post: PostLike): PostCategory => {\n const { category } = postParams(post);\n return { name: categoryName(category), slug: category };\n };\n\n const groupByCategory = ({ includeSecondaryCategories = false }: GroupOptions = {}): Array<\n CategoryBucket<P>\n > => {\n const postsBySlug = new Map<string, Array<P>>();\n const add = (slug: string, post: P) => {\n const bucket = postsBySlug.get(slug);\n if (bucket) {\n bucket.push(post);\n } else {\n postsBySlug.set(slug, [post]);\n }\n };\n\n for (const post of posts) {\n const { category } = postParams(post);\n add(category, post);\n if (!includeSecondaryCategories) {\n continue;\n }\n for (const name of post.data.categories) {\n const slug = categorySlug(name);\n if (slug !== category) {\n add(slug, post);\n }\n }\n }\n\n return [...postsBySlug].map(([slug, bucketPosts]) => ({\n name: categoryName(slug),\n posts: bucketPosts.toSorted(byPubDateDesc),\n slug,\n }));\n };\n\n return { categoryName, groupByCategory, postCategory };\n};\n\nexport type { CategoryBucket, CategoryPostLike, PostCategory };\nexport { createCategoryIndex };\n","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 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\nconst tagLabel = (tag: string): string => tag.replace(/^#/v, \"\");\n\nconst uniqueTags = (tags: Array<string>): Array<string> => {\n const seen = new Set<string>();\n return tags.filter((tag) => {\n const slug = slugify(tag);\n if (seen.has(slug)) {\n return false;\n }\n seen.add(slug);\n return true;\n });\n};\n\nexport { DEFAULT_TAG_BASE, tagLabel, tagUrl, uniqueTags };\n","export { cn } from \"./cn\";\nexport type { CategoryBucket, CategoryPostLike, PostCategory } from \"./category-index\";\nexport { createCategoryIndex } from \"./category-index\";\nexport { categoryLabel } from \"./category-label\";\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, tagLabel, tagUrl, uniqueTags } from \"./tag-url\";\n"],"mappings":"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"}
|
package/dist/styles/globals.css
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
It contributes tokens, base styles, prose, and the dark variant. */
|
|
3
3
|
|
|
4
4
|
@import "tw-animate-css";
|
|
5
|
+
@import "shadcn/tailwind.css";
|
|
5
6
|
|
|
6
7
|
/* Scan our own .astro/.tsx files so consumer Tailwind generates utilities
|
|
7
8
|
used by the theme. Paths resolve from dist/styles/globals.css after build:
|
|
@@ -9,10 +10,17 @@
|
|
|
9
10
|
@source "../../src/astro/**/*.astro";
|
|
10
11
|
@source "../../src/layouts/**/*.astro";
|
|
11
12
|
@source "../../src/components/**/*.{ts,tsx}";
|
|
13
|
+
@source "../../src/compositions/**/*.{ts,tsx}";
|
|
12
14
|
|
|
13
15
|
@custom-variant dark (&:is(.dark *));
|
|
14
16
|
|
|
15
17
|
@theme {
|
|
18
|
+
--tracking-eyebrow: 0.16em;
|
|
19
|
+
--tracking-list-title: -0.045em;
|
|
20
|
+
--leading-list-title: 0.95;
|
|
21
|
+
--measure-list-title: 14ch;
|
|
22
|
+
--measure-list-intro: 38ch;
|
|
23
|
+
|
|
16
24
|
--color-background: oklch(1 0 0);
|
|
17
25
|
--color-foreground: oklch(0.145 0 0);
|
|
18
26
|
--color-card: oklch(1 0 0);
|
|
@@ -24,7 +32,7 @@
|
|
|
24
32
|
--color-secondary: oklch(0.97 0 0);
|
|
25
33
|
--color-secondary-foreground: oklch(0.205 0 0);
|
|
26
34
|
--color-muted: oklch(0.97 0 0);
|
|
27
|
-
--color-muted-foreground: oklch(0.
|
|
35
|
+
--color-muted-foreground: oklch(0.54 0 0);
|
|
28
36
|
--color-accent: oklch(0.97 0 0);
|
|
29
37
|
--color-accent-foreground: oklch(0.205 0 0);
|
|
30
38
|
--color-destructive: oklch(0.577 0.245 27.325);
|
|
@@ -264,3 +272,58 @@
|
|
|
264
272
|
text-align: left;
|
|
265
273
|
}
|
|
266
274
|
}
|
|
275
|
+
|
|
276
|
+
@utility post-grid {
|
|
277
|
+
display: grid;
|
|
278
|
+
gap: 1px;
|
|
279
|
+
overflow: hidden;
|
|
280
|
+
border: 1px solid var(--color-border);
|
|
281
|
+
background: var(--color-border);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.post-grid .post-card {
|
|
285
|
+
border: 0;
|
|
286
|
+
background: var(--color-background);
|
|
287
|
+
padding: 1.75rem;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.post-grid .post-card:first-child h2 {
|
|
291
|
+
max-width: 18ch;
|
|
292
|
+
font-family: var(--font-serif);
|
|
293
|
+
font-size: clamp(1.75rem, 4vw, 2.75rem);
|
|
294
|
+
line-height: 1.05;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
@media (min-width: 48rem) {
|
|
298
|
+
.post-grid {
|
|
299
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.post-grid .post-card:first-child {
|
|
303
|
+
grid-column: 1 / -1;
|
|
304
|
+
min-height: 18rem;
|
|
305
|
+
display: flex;
|
|
306
|
+
flex-direction: column;
|
|
307
|
+
justify-content: end;
|
|
308
|
+
padding: 3rem;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/* Keep first 3 eager so above-the-fold paint isn't gated on containment checks. */
|
|
313
|
+
@utility post-card {
|
|
314
|
+
&:nth-child(n + 4) {
|
|
315
|
+
content-visibility: auto;
|
|
316
|
+
contain-intrinsic-size: auto 200px;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
@utility grid-cols-list-heading {
|
|
321
|
+
grid-template-columns: 1.2fr 0.8fr;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
@theme inline {
|
|
325
|
+
--radius-control-sm: min(var(--radius-md), 10px);
|
|
326
|
+
--radius-control: min(var(--radius-md), 12px);
|
|
327
|
+
--text-control: 0.8rem;
|
|
328
|
+
--color-secondary-hover: color-mix(in oklch, var(--color-secondary), var(--color-foreground) 5%);
|
|
329
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "astro-awesomeness",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Astro 6/7 blog theme with shadcn primitives and a Tailwind v4 preset.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"astro",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
},
|
|
45
45
|
"./tailwind": {
|
|
46
46
|
"types": "./dist/tailwind-preset.d.ts",
|
|
47
|
-
"
|
|
47
|
+
"default": "./dist/tailwind-preset.js"
|
|
48
48
|
},
|
|
49
49
|
"./styles.css": "./dist/styles/globals.css"
|
|
50
50
|
},
|
|
@@ -52,22 +52,23 @@
|
|
|
52
52
|
"access": "public"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@base-ui/react": "^1.
|
|
55
|
+
"@base-ui/react": "^1.8.0",
|
|
56
56
|
"class-variance-authority": "^0.7.1",
|
|
57
|
-
"cn": "^0.2.
|
|
58
|
-
"lucide-react": "^1.
|
|
57
|
+
"cn": "^0.2.6",
|
|
58
|
+
"lucide-react": "^1.44.0",
|
|
59
|
+
"shadcn": "^4.21.0",
|
|
59
60
|
"tw-animate-css": "^1.4.0",
|
|
60
|
-
"zod": "^4.
|
|
61
|
+
"zod": "^4.6.1"
|
|
61
62
|
},
|
|
62
63
|
"devDependencies": {
|
|
63
64
|
"@tailwindcss/postcss": "^4.3.2",
|
|
64
|
-
"@types/node": "^26.
|
|
65
|
-
"@types/react": "^19.
|
|
66
|
-
"@types/react-dom": "^19.
|
|
67
|
-
"@vitest/coverage-v8": "^
|
|
65
|
+
"@types/node": "^26.5.1",
|
|
66
|
+
"@types/react": "^19.3.0",
|
|
67
|
+
"@types/react-dom": "^19.3.0",
|
|
68
|
+
"@vitest/coverage-v8": "^5.0.0",
|
|
68
69
|
"tailwindcss": "^4.3.2",
|
|
69
70
|
"typescript": "^7.0.2",
|
|
70
|
-
"vitest": "^
|
|
71
|
+
"vitest": "^5.0.0",
|
|
71
72
|
"@repo/config-vitest": "0.0.0",
|
|
72
73
|
"@repo/typescript-config": "0.0.0"
|
|
73
74
|
},
|