astro-awesomeness 0.7.1 → 0.7.3
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 +17 -17
- package/dist/components/index.js +48 -36
- package/dist/components/index.js.map +1 -1
- package/dist/content/index.d.ts +2 -2
- package/dist/content/index.js +1 -1
- package/dist/content/index.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/index.js +1 -1
- package/dist/{lib-mtnJ1lv8.js → lib-WSV3WGQr.js} +10 -11
- package/dist/lib-WSV3WGQr.js.map +1 -0
- package/dist/styles/globals.css +10 -0
- package/package.json +1 -1
- package/src/astro/table-of-contents.astro +4 -4
- package/src/components/command-menu.tsx +6 -2
- package/src/components/ui/command.tsx +28 -22
- package/src/components/ui/dialog.tsx +27 -24
- package/src/content/index.ts +4 -5
- package/src/lib/format-date.ts +8 -14
- package/src/lib/post-url.ts +7 -4
- package/src/lib/resolve-navigation-url.test.ts +25 -0
- package/src/lib/resolve-navigation-url.ts +10 -0
- package/src/styles/globals.css +10 -0
- package/dist/lib-mtnJ1lv8.js.map +0 -1
|
@@ -16,27 +16,27 @@ type ButtonProps = Omit<React.ComponentProps<typeof Button$1>, "className"> & Va
|
|
|
16
16
|
declare const Button: ({ className, size, variant, ...props }: ButtonProps) => React.JSX.Element;
|
|
17
17
|
//#endregion
|
|
18
18
|
//#region src/components/ui/dialog.d.ts
|
|
19
|
-
declare
|
|
20
|
-
declare
|
|
21
|
-
declare
|
|
19
|
+
declare const Dialog: ({ ...props }: React.ComponentProps<typeof Dialog$1.Root>) => React.JSX.Element;
|
|
20
|
+
declare const DialogTrigger: ({ ...props }: React.ComponentProps<typeof Dialog$1.Trigger>) => React.JSX.Element;
|
|
21
|
+
declare const DialogContent: ({ children, className, showCloseButton, ...props }: React.ComponentProps<typeof Dialog$1.Popup> & {
|
|
22
22
|
showCloseButton?: boolean;
|
|
23
|
-
})
|
|
24
|
-
declare
|
|
25
|
-
declare
|
|
23
|
+
}) => React.JSX.Element;
|
|
24
|
+
declare const DialogHeader: ({ className, ...props }: React.ComponentProps<"div">) => React.JSX.Element;
|
|
25
|
+
declare const DialogFooter: ({ children, className, showCloseButton, ...props }: React.ComponentProps<"div"> & {
|
|
26
26
|
showCloseButton?: boolean;
|
|
27
|
-
})
|
|
28
|
-
declare
|
|
29
|
-
declare
|
|
27
|
+
}) => React.JSX.Element;
|
|
28
|
+
declare const DialogTitle: ({ className, ...props }: React.ComponentProps<typeof Dialog$1.Title>) => React.JSX.Element;
|
|
29
|
+
declare const DialogDescription: ({ className, ...props }: React.ComponentProps<typeof Dialog$1.Description>) => React.JSX.Element;
|
|
30
30
|
//#endregion
|
|
31
31
|
//#region src/components/ui/command.d.ts
|
|
32
|
-
declare
|
|
33
|
-
declare
|
|
34
|
-
declare
|
|
35
|
-
declare
|
|
36
|
-
declare
|
|
37
|
-
declare
|
|
38
|
-
declare
|
|
39
|
-
declare
|
|
32
|
+
declare const Command: ({ className, ...props }: React.ComponentProps<typeof Command$1>) => React.JSX.Element;
|
|
33
|
+
declare const CommandInput: ({ className, ...props }: React.ComponentProps<typeof Command$1.Input>) => React.JSX.Element;
|
|
34
|
+
declare const CommandList: ({ className, ...props }: React.ComponentProps<typeof Command$1.List>) => React.JSX.Element;
|
|
35
|
+
declare const CommandEmpty: ({ ...props }: React.ComponentProps<typeof Command$1.Empty>) => React.JSX.Element;
|
|
36
|
+
declare const CommandGroup: ({ className, ...props }: React.ComponentProps<typeof Command$1.Group>) => React.JSX.Element;
|
|
37
|
+
declare const CommandSeparator: ({ className, ...props }: React.ComponentProps<typeof Command$1.Separator>) => React.JSX.Element;
|
|
38
|
+
declare const CommandItem: ({ className, ...props }: React.ComponentProps<typeof Command$1.Item>) => React.JSX.Element;
|
|
39
|
+
declare const CommandShortcut: ({ className, ...props }: React.ComponentProps<"span">) => React.JSX.Element;
|
|
40
40
|
//#endregion
|
|
41
41
|
//#region src/components/command-menu.d.ts
|
|
42
42
|
type CommandMenuItem = {
|
package/dist/components/index.js
CHANGED
|
@@ -53,32 +53,32 @@ const Button = ({ className, size = "default", variant = "default", ...props })
|
|
|
53
53
|
|
|
54
54
|
//#endregion
|
|
55
55
|
//#region src/components/ui/dialog.tsx
|
|
56
|
-
|
|
56
|
+
const Dialog = ({ ...props }) => {
|
|
57
57
|
return /* @__PURE__ */ jsx(Dialog$1.Root, {
|
|
58
58
|
"data-slot": "dialog",
|
|
59
59
|
...props
|
|
60
60
|
});
|
|
61
|
-
}
|
|
62
|
-
|
|
61
|
+
};
|
|
62
|
+
const DialogTrigger = ({ ...props }) => {
|
|
63
63
|
return /* @__PURE__ */ jsx(Dialog$1.Trigger, {
|
|
64
64
|
"data-slot": "dialog-trigger",
|
|
65
65
|
...props
|
|
66
66
|
});
|
|
67
|
-
}
|
|
68
|
-
|
|
67
|
+
};
|
|
68
|
+
const DialogPortal = ({ ...props }) => {
|
|
69
69
|
return /* @__PURE__ */ jsx(Dialog$1.Portal, {
|
|
70
70
|
"data-slot": "dialog-portal",
|
|
71
71
|
...props
|
|
72
72
|
});
|
|
73
|
-
}
|
|
74
|
-
|
|
73
|
+
};
|
|
74
|
+
const DialogOverlay = ({ className, ...props }) => {
|
|
75
75
|
return /* @__PURE__ */ jsx(Dialog$1.Backdrop, {
|
|
76
76
|
className: cn("fixed inset-0 z-50 bg-black/50 data-[closed]:animate-out data-[closed]:fade-out-0 data-[open]:animate-in data-[open]:fade-in-0", className),
|
|
77
77
|
"data-slot": "dialog-overlay",
|
|
78
78
|
...props
|
|
79
79
|
});
|
|
80
|
-
}
|
|
81
|
-
|
|
80
|
+
};
|
|
81
|
+
const DialogContent = ({ children, className, showCloseButton = true, ...props }) => {
|
|
82
82
|
return /* @__PURE__ */ jsxs(DialogPortal, {
|
|
83
83
|
"data-slot": "dialog-portal",
|
|
84
84
|
children: [/* @__PURE__ */ jsx(DialogOverlay, {}), /* @__PURE__ */ jsxs(Dialog$1.Popup, {
|
|
@@ -96,15 +96,15 @@ function DialogContent({ children, className, showCloseButton = true, ...props }
|
|
|
96
96
|
})]
|
|
97
97
|
})]
|
|
98
98
|
});
|
|
99
|
-
}
|
|
100
|
-
|
|
99
|
+
};
|
|
100
|
+
const DialogHeader = ({ className, ...props }) => {
|
|
101
101
|
return /* @__PURE__ */ jsx("div", {
|
|
102
102
|
className: cn("flex flex-col gap-2 text-center sm:text-left", className),
|
|
103
103
|
"data-slot": "dialog-header",
|
|
104
104
|
...props
|
|
105
105
|
});
|
|
106
|
-
}
|
|
107
|
-
|
|
106
|
+
};
|
|
107
|
+
const DialogFooter = ({ children, className, showCloseButton = false, ...props }) => {
|
|
108
108
|
return /* @__PURE__ */ jsxs("div", {
|
|
109
109
|
className: cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className),
|
|
110
110
|
"data-slot": "dialog-footer",
|
|
@@ -114,32 +114,32 @@ function DialogFooter({ children, className, showCloseButton = false, ...props }
|
|
|
114
114
|
children: "Close"
|
|
115
115
|
})]
|
|
116
116
|
});
|
|
117
|
-
}
|
|
118
|
-
|
|
117
|
+
};
|
|
118
|
+
const DialogTitle = ({ className, ...props }) => {
|
|
119
119
|
return /* @__PURE__ */ jsx(Dialog$1.Title, {
|
|
120
120
|
className: cn("text-lg leading-none font-semibold", className),
|
|
121
121
|
"data-slot": "dialog-title",
|
|
122
122
|
...props
|
|
123
123
|
});
|
|
124
|
-
}
|
|
125
|
-
|
|
124
|
+
};
|
|
125
|
+
const DialogDescription = ({ className, ...props }) => {
|
|
126
126
|
return /* @__PURE__ */ jsx(Dialog$1.Description, {
|
|
127
127
|
className: cn("text-sm text-muted-foreground", className),
|
|
128
128
|
"data-slot": "dialog-description",
|
|
129
129
|
...props
|
|
130
130
|
});
|
|
131
|
-
}
|
|
131
|
+
};
|
|
132
132
|
|
|
133
133
|
//#endregion
|
|
134
134
|
//#region src/components/ui/command.tsx
|
|
135
|
-
|
|
135
|
+
const Command = ({ className, ...props }) => {
|
|
136
136
|
return /* @__PURE__ */ jsx(Command$1, {
|
|
137
137
|
className: cn("flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground", className),
|
|
138
138
|
"data-slot": "command",
|
|
139
139
|
...props
|
|
140
140
|
});
|
|
141
|
-
}
|
|
142
|
-
|
|
141
|
+
};
|
|
142
|
+
const CommandInput = ({ className, ...props }) => {
|
|
143
143
|
return /* @__PURE__ */ jsxs("div", {
|
|
144
144
|
className: "flex h-9 items-center gap-2 border-b px-3",
|
|
145
145
|
"data-slot": "command-input-wrapper",
|
|
@@ -149,49 +149,60 @@ function CommandInput({ className, ...props }) {
|
|
|
149
149
|
...props
|
|
150
150
|
})]
|
|
151
151
|
});
|
|
152
|
-
}
|
|
153
|
-
|
|
152
|
+
};
|
|
153
|
+
const CommandList = ({ className, ...props }) => {
|
|
154
154
|
return /* @__PURE__ */ jsx(Command$1.List, {
|
|
155
155
|
className: cn("max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto", className),
|
|
156
156
|
"data-slot": "command-list",
|
|
157
157
|
...props
|
|
158
158
|
});
|
|
159
|
-
}
|
|
160
|
-
|
|
159
|
+
};
|
|
160
|
+
const CommandEmpty = ({ ...props }) => {
|
|
161
161
|
return /* @__PURE__ */ jsx(Command$1.Empty, {
|
|
162
162
|
className: "py-6 text-center text-sm",
|
|
163
163
|
"data-slot": "command-empty",
|
|
164
164
|
...props
|
|
165
165
|
});
|
|
166
|
-
}
|
|
167
|
-
|
|
166
|
+
};
|
|
167
|
+
const CommandGroup = ({ className, ...props }) => {
|
|
168
168
|
return /* @__PURE__ */ jsx(Command$1.Group, {
|
|
169
169
|
className: cn("overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground", className),
|
|
170
170
|
"data-slot": "command-group",
|
|
171
171
|
...props
|
|
172
172
|
});
|
|
173
|
-
}
|
|
174
|
-
|
|
173
|
+
};
|
|
174
|
+
const CommandSeparator = ({ className, ...props }) => {
|
|
175
175
|
return /* @__PURE__ */ jsx(Command$1.Separator, {
|
|
176
176
|
className: cn("-mx-1 h-px bg-border", className),
|
|
177
177
|
"data-slot": "command-separator",
|
|
178
178
|
...props
|
|
179
179
|
});
|
|
180
|
-
}
|
|
181
|
-
|
|
180
|
+
};
|
|
181
|
+
const CommandItem = ({ className, ...props }) => {
|
|
182
182
|
return /* @__PURE__ */ jsx(Command$1.Item, {
|
|
183
183
|
className: cn("relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground", className),
|
|
184
184
|
"data-slot": "command-item",
|
|
185
185
|
...props
|
|
186
186
|
});
|
|
187
|
-
}
|
|
188
|
-
|
|
187
|
+
};
|
|
188
|
+
const CommandShortcut = ({ className, ...props }) => {
|
|
189
189
|
return /* @__PURE__ */ jsx("span", {
|
|
190
190
|
className: cn("ml-auto text-xs tracking-widest text-muted-foreground", className),
|
|
191
191
|
"data-slot": "command-shortcut",
|
|
192
192
|
...props
|
|
193
193
|
});
|
|
194
|
-
}
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
//#endregion
|
|
197
|
+
//#region src/lib/resolve-navigation-url.ts
|
|
198
|
+
const resolveNavigationUrl = (value, baseUrl) => {
|
|
199
|
+
try {
|
|
200
|
+
const url = new URL(value, baseUrl);
|
|
201
|
+
return url.protocol === "http:" || url.protocol === "https:" ? url.href : null;
|
|
202
|
+
} catch {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
};
|
|
195
206
|
|
|
196
207
|
//#endregion
|
|
197
208
|
//#region src/components/command-menu.tsx
|
|
@@ -228,12 +239,13 @@ const CommandMenu = ({ emptyMessage = "No results.", groupHeading = "Posts", ite
|
|
|
228
239
|
heading: groupHeading,
|
|
229
240
|
children: items.map((item) => /* @__PURE__ */ jsx(CommandItem, {
|
|
230
241
|
onSelect: () => {
|
|
231
|
-
|
|
242
|
+
const destination = resolveNavigationUrl(item.url, window.location.href);
|
|
243
|
+
if (destination !== null && destination !== "") window.location.href = destination;
|
|
232
244
|
},
|
|
233
245
|
value: item.title,
|
|
234
246
|
children: /* @__PURE__ */ jsxs("div", {
|
|
235
247
|
className: "flex flex-col",
|
|
236
|
-
children: [/* @__PURE__ */ jsx("span", { children: item.title }), item.description ? /* @__PURE__ */ jsx("span", {
|
|
248
|
+
children: [/* @__PURE__ */ jsx("span", { children: item.title }), item.description !== void 0 && item.description !== "" ? /* @__PURE__ */ jsx("span", {
|
|
237
249
|
className: "text-muted-foreground text-xs",
|
|
238
250
|
children: item.description
|
|
239
251
|
}) : null]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["ButtonPrimitive","DialogPrimitive","CommandPrimitive"],"sources":["../../src/components/ui/button-variants.ts","../../src/components/ui/button.tsx","../../src/components/ui/dialog.tsx","../../src/components/ui/command.tsx","../../src/components/command-menu.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 { Dialog as DialogPrimitive } from \"@base-ui/react/dialog\";\nimport * as React from \"react\";\nimport { XIcon } from \"lucide-react\";\n\nimport { cn } from \"../../lib/cn\";\nimport { Button } from \"./button\";\n\nfunction Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) {\n return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />;\n}\n\nfunction DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {\n return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n}\n\nfunction DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>) {\n return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\nfunction DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>) {\n return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n}\n\nfunction DialogOverlay({\n className,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Backdrop>) {\n return (\n <DialogPrimitive.Backdrop\n className={cn(\n \"fixed inset-0 z-50 bg-black/50 data-[closed]:animate-out data-[closed]:fade-out-0 data-[open]:animate-in data-[open]:fade-in-0\",\n className,\n )}\n data-slot=\"dialog-overlay\"\n {...props}\n />\n );\n}\n\nfunction DialogContent({\n children,\n className,\n showCloseButton = true,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Popup> & {\n showCloseButton?: boolean;\n}) {\n return (\n <DialogPortal data-slot=\"dialog-portal\">\n <DialogOverlay />\n <DialogPrimitive.Popup\n className={cn(\n \"fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 outline-none data-[closed]:animate-out data-[closed]:fade-out-0 data-[closed]:zoom-out-95 data-[open]:animate-in data-[open]:fade-in-0 data-[open]:zoom-in-95 sm:max-w-lg\",\n className,\n )}\n data-slot=\"dialog-content\"\n {...props}\n >\n {children}\n {showCloseButton && (\n <DialogPrimitive.Close\n className=\"absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\"\n data-slot=\"dialog-close\"\n type=\"button\"\n >\n <XIcon />\n <span className=\"sr-only\">Close</span>\n </DialogPrimitive.Close>\n )}\n </DialogPrimitive.Popup>\n </DialogPortal>\n );\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n className={cn(\"flex flex-col gap-2 text-center sm:text-left\", className)}\n data-slot=\"dialog-header\"\n {...props}\n />\n );\n}\n\nfunction DialogFooter({\n children,\n className,\n showCloseButton = false,\n ...props\n}: React.ComponentProps<\"div\"> & {\n showCloseButton?: boolean;\n}) {\n return (\n <div\n className={cn(\"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end\", className)}\n data-slot=\"dialog-footer\"\n {...props}\n >\n {children}\n {showCloseButton && (\n <DialogPrimitive.Close render={<Button variant=\"outline\" />}>Close</DialogPrimitive.Close>\n )}\n </div>\n );\n}\n\nfunction DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {\n return (\n <DialogPrimitive.Title\n className={cn(\"text-lg leading-none font-semibold\", className)}\n data-slot=\"dialog-title\"\n {...props}\n />\n );\n}\n\nfunction DialogDescription({\n className,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Description>) {\n return (\n <DialogPrimitive.Description\n className={cn(\"text-sm text-muted-foreground\", className)}\n data-slot=\"dialog-description\"\n {...props}\n />\n );\n}\n\nexport {\n Dialog,\n // fallow-ignore-next-line unused-export\n DialogClose,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n // fallow-ignore-next-line unused-export\n DialogOverlay,\n // fallow-ignore-next-line unused-export\n DialogPortal,\n DialogTitle,\n DialogTrigger,\n};\n","import * as React from \"react\";\nimport { Command as CommandPrimitive } from \"cmdk\";\nimport { SearchIcon } from \"lucide-react\";\n\nimport { cn } from \"../../lib/cn\";\nimport { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from \"./dialog\";\n\nfunction Command({ className, ...props }: React.ComponentProps<typeof CommandPrimitive>) {\n return (\n <CommandPrimitive\n className={cn(\n \"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground\",\n className,\n )}\n data-slot=\"command\"\n {...props}\n />\n );\n}\n\nfunction CommandDialog({\n children,\n className,\n description = \"Search for a command to run...\",\n showCloseButton = true,\n title = \"Command Palette\",\n ...props\n}: React.ComponentProps<typeof Dialog> & {\n children?: React.ReactNode;\n className?: string;\n description?: string;\n showCloseButton?: boolean;\n title?: string;\n}) {\n return (\n <Dialog {...props}>\n <DialogContent\n className={cn(\"overflow-hidden p-0\", className)}\n showCloseButton={showCloseButton}\n >\n {/* Inside DialogContent so the sr-only name renders in the portaled popup, not the page. */}\n <DialogHeader className=\"sr-only\">\n <DialogTitle>{title}</DialogTitle>\n <DialogDescription>{description}</DialogDescription>\n </DialogHeader>\n <Command className=\"**:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5\">\n {children}\n </Command>\n </DialogContent>\n </Dialog>\n );\n}\n\nfunction CommandInput({\n className,\n ...props\n}: React.ComponentProps<typeof CommandPrimitive.Input>) {\n return (\n <div className=\"flex h-9 items-center gap-2 border-b px-3\" data-slot=\"command-input-wrapper\">\n <SearchIcon className=\"size-4 shrink-0 opacity-50\" />\n <CommandPrimitive.Input\n className={cn(\n \"flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50\",\n className,\n )}\n data-slot=\"command-input\"\n {...props}\n />\n </div>\n );\n}\n\nfunction CommandList({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.List>) {\n return (\n <CommandPrimitive.List\n className={cn(\"max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto\", className)}\n data-slot=\"command-list\"\n {...props}\n />\n );\n}\n\nfunction CommandEmpty({ ...props }: React.ComponentProps<typeof CommandPrimitive.Empty>) {\n return (\n <CommandPrimitive.Empty\n className=\"py-6 text-center text-sm\"\n data-slot=\"command-empty\"\n {...props}\n />\n );\n}\n\nfunction CommandGroup({\n className,\n ...props\n}: React.ComponentProps<typeof CommandPrimitive.Group>) {\n return (\n <CommandPrimitive.Group\n className={cn(\n \"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground\",\n className,\n )}\n data-slot=\"command-group\"\n {...props}\n />\n );\n}\n\nfunction CommandSeparator({\n className,\n ...props\n}: React.ComponentProps<typeof CommandPrimitive.Separator>) {\n return (\n <CommandPrimitive.Separator\n className={cn(\"-mx-1 h-px bg-border\", className)}\n data-slot=\"command-separator\"\n {...props}\n />\n );\n}\n\nfunction CommandItem({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Item>) {\n return (\n <CommandPrimitive.Item\n className={cn(\n \"relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground\",\n className,\n )}\n data-slot=\"command-item\"\n {...props}\n />\n );\n}\n\nfunction CommandShortcut({ className, ...props }: React.ComponentProps<\"span\">) {\n return (\n <span\n className={cn(\"ml-auto text-xs tracking-widest text-muted-foreground\", className)}\n data-slot=\"command-shortcut\"\n {...props}\n />\n );\n}\n\nexport {\n Command,\n // fallow-ignore-next-line unused-export\n CommandDialog,\n CommandInput,\n CommandList,\n CommandEmpty,\n CommandGroup,\n CommandItem,\n CommandShortcut,\n CommandSeparator,\n};\n","import { Search } from \"lucide-react\";\nimport { useEffect, useState } from \"react\";\n\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n} from \"./ui/command\";\nimport { Dialog, DialogContent, DialogTitle, DialogTrigger } from \"./ui/dialog\";\nimport { Button } from \"./ui/button\";\n\ntype CommandMenuItem = { description?: string; title: string; url: string };\n\ntype CommandMenuProps = {\n emptyMessage?: string;\n groupHeading?: string;\n items: Array<CommandMenuItem>;\n placeholder?: string;\n title?: string;\n};\n\nconst CommandMenu = ({\n emptyMessage = \"No results.\",\n groupHeading = \"Posts\",\n items,\n placeholder = \"Search posts…\",\n title = \"Search posts\",\n}: CommandMenuProps) => {\n const [open, setOpen] = useState(false);\n\n useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"k\" && (event.metaKey || event.ctrlKey)) {\n event.preventDefault();\n setOpen((value) => !value);\n }\n };\n document.addEventListener(\"keydown\", handleKeyDown);\n return () => {\n document.removeEventListener(\"keydown\", handleKeyDown);\n };\n }, []);\n\n return (\n <Dialog onOpenChange={setOpen} open={open}>\n <DialogTrigger render={<Button aria-label=\"Open search\" size=\"icon\" variant=\"ghost\" />}>\n <Search />\n </DialogTrigger>\n <DialogContent className=\"p-0\">\n <DialogTitle className=\"sr-only\">{title}</DialogTitle>\n <Command>\n <CommandInput placeholder={placeholder} />\n <CommandList>\n <CommandEmpty>{emptyMessage}</CommandEmpty>\n <CommandGroup heading={groupHeading}>\n {items.map((item) => (\n <CommandItem\n key={item.url}\n onSelect={() => {\n window.location.href = item.url;\n }}\n value={item.title}\n >\n <div className=\"flex flex-col\">\n <span>{item.title}</span>\n {item.description ? (\n <span className=\"text-muted-foreground text-xs\">{item.description}</span>\n ) : null}\n </div>\n </CommandItem>\n ))}\n </CommandGroup>\n </CommandList>\n </Command>\n </DialogContent>\n </Dialog>\n );\n};\n\nexport { CommandMenu };\nexport type { CommandMenuItem, CommandMenuProps };\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":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["ButtonPrimitive","DialogPrimitive","CommandPrimitive"],"sources":["../../src/components/ui/button-variants.ts","../../src/components/ui/button.tsx","../../src/components/ui/dialog.tsx","../../src/components/ui/command.tsx","../../src/lib/resolve-navigation-url.ts","../../src/components/command-menu.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 { Dialog as DialogPrimitive } from \"@base-ui/react/dialog\";\nimport * as React from \"react\";\nimport { XIcon } from \"lucide-react\";\n\nimport { cn } from \"../../lib/cn\";\nimport { Button } from \"./button\";\n\nconst Dialog = ({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) => {\n return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />;\n};\n\nconst DialogTrigger = ({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>) => {\n return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n};\n\nconst DialogPortal = ({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>) => {\n return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n};\n\nconst DialogClose = ({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>) => {\n return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n};\n\nconst DialogOverlay = ({\n className,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Backdrop>) => {\n return (\n <DialogPrimitive.Backdrop\n className={cn(\n \"fixed inset-0 z-50 bg-black/50 data-[closed]:animate-out data-[closed]:fade-out-0 data-[open]:animate-in data-[open]:fade-in-0\",\n className,\n )}\n data-slot=\"dialog-overlay\"\n {...props}\n />\n );\n};\n\nconst DialogContent = ({\n children,\n className,\n showCloseButton = true,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Popup> & {\n showCloseButton?: boolean;\n}) => {\n return (\n <DialogPortal data-slot=\"dialog-portal\">\n <DialogOverlay />\n <DialogPrimitive.Popup\n className={cn(\n \"fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 outline-none data-[closed]:animate-out data-[closed]:fade-out-0 data-[closed]:zoom-out-95 data-[open]:animate-in data-[open]:fade-in-0 data-[open]:zoom-in-95 sm:max-w-lg\",\n className,\n )}\n data-slot=\"dialog-content\"\n {...props}\n >\n {children}\n {showCloseButton && (\n <DialogPrimitive.Close\n className=\"absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\"\n data-slot=\"dialog-close\"\n type=\"button\"\n >\n <XIcon />\n <span className=\"sr-only\">Close</span>\n </DialogPrimitive.Close>\n )}\n </DialogPrimitive.Popup>\n </DialogPortal>\n );\n};\n\nconst DialogHeader = ({ className, ...props }: React.ComponentProps<\"div\">) => {\n return (\n <div\n className={cn(\"flex flex-col gap-2 text-center sm:text-left\", className)}\n data-slot=\"dialog-header\"\n {...props}\n />\n );\n};\n\nconst DialogFooter = ({\n children,\n className,\n showCloseButton = false,\n ...props\n}: React.ComponentProps<\"div\"> & {\n showCloseButton?: boolean;\n}) => {\n return (\n <div\n className={cn(\"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end\", className)}\n data-slot=\"dialog-footer\"\n {...props}\n >\n {children}\n {showCloseButton && (\n <DialogPrimitive.Close render={<Button variant=\"outline\" />}>Close</DialogPrimitive.Close>\n )}\n </div>\n );\n};\n\nconst DialogTitle = ({\n className,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Title>) => {\n return (\n <DialogPrimitive.Title\n className={cn(\"text-lg leading-none font-semibold\", className)}\n data-slot=\"dialog-title\"\n {...props}\n />\n );\n};\n\nconst DialogDescription = ({\n className,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Description>) => {\n return (\n <DialogPrimitive.Description\n className={cn(\"text-sm text-muted-foreground\", className)}\n data-slot=\"dialog-description\"\n {...props}\n />\n );\n};\n\nexport {\n Dialog,\n // fallow-ignore-next-line unused-export\n DialogClose,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n // fallow-ignore-next-line unused-export\n DialogOverlay,\n // fallow-ignore-next-line unused-export\n DialogPortal,\n DialogTitle,\n DialogTrigger,\n};\n","import * as React from \"react\";\nimport { Command as CommandPrimitive } from \"cmdk\";\nimport { SearchIcon } from \"lucide-react\";\n\nimport { cn } from \"../../lib/cn\";\nimport { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from \"./dialog\";\n\nconst Command = ({ className, ...props }: React.ComponentProps<typeof CommandPrimitive>) => {\n return (\n <CommandPrimitive\n className={cn(\n \"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground\",\n className,\n )}\n data-slot=\"command\"\n {...props}\n />\n );\n};\n\nconst CommandDialog = ({\n children,\n className,\n description = \"Search for a command to run...\",\n showCloseButton = true,\n title = \"Command Palette\",\n ...props\n}: React.ComponentProps<typeof Dialog> & {\n children?: React.ReactNode;\n className?: string;\n description?: string;\n showCloseButton?: boolean;\n title?: string;\n}) => {\n return (\n <Dialog {...props}>\n <DialogContent\n className={cn(\"overflow-hidden p-0\", className)}\n showCloseButton={showCloseButton}\n >\n {/* Inside DialogContent so the sr-only name renders in the portaled popup, not the page. */}\n <DialogHeader className=\"sr-only\">\n <DialogTitle>{title}</DialogTitle>\n <DialogDescription>{description}</DialogDescription>\n </DialogHeader>\n <Command className=\"**:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5\">\n {children}\n </Command>\n </DialogContent>\n </Dialog>\n );\n};\n\nconst CommandInput = ({\n className,\n ...props\n}: React.ComponentProps<typeof CommandPrimitive.Input>) => {\n return (\n <div className=\"flex h-9 items-center gap-2 border-b px-3\" data-slot=\"command-input-wrapper\">\n <SearchIcon className=\"size-4 shrink-0 opacity-50\" />\n <CommandPrimitive.Input\n className={cn(\n \"flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50\",\n className,\n )}\n data-slot=\"command-input\"\n {...props}\n />\n </div>\n );\n};\n\nconst CommandList = ({\n className,\n ...props\n}: React.ComponentProps<typeof CommandPrimitive.List>) => {\n return (\n <CommandPrimitive.List\n className={cn(\"max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto\", className)}\n data-slot=\"command-list\"\n {...props}\n />\n );\n};\n\nconst CommandEmpty = ({ ...props }: React.ComponentProps<typeof CommandPrimitive.Empty>) => {\n return (\n <CommandPrimitive.Empty\n className=\"py-6 text-center text-sm\"\n data-slot=\"command-empty\"\n {...props}\n />\n );\n};\n\nconst CommandGroup = ({\n className,\n ...props\n}: React.ComponentProps<typeof CommandPrimitive.Group>) => {\n return (\n <CommandPrimitive.Group\n className={cn(\n \"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground\",\n className,\n )}\n data-slot=\"command-group\"\n {...props}\n />\n );\n};\n\nconst CommandSeparator = ({\n className,\n ...props\n}: React.ComponentProps<typeof CommandPrimitive.Separator>) => {\n return (\n <CommandPrimitive.Separator\n className={cn(\"-mx-1 h-px bg-border\", className)}\n data-slot=\"command-separator\"\n {...props}\n />\n );\n};\n\nconst CommandItem = ({\n className,\n ...props\n}: React.ComponentProps<typeof CommandPrimitive.Item>) => {\n return (\n <CommandPrimitive.Item\n className={cn(\n \"relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground\",\n className,\n )}\n data-slot=\"command-item\"\n {...props}\n />\n );\n};\n\nconst CommandShortcut = ({ className, ...props }: React.ComponentProps<\"span\">) => {\n return (\n <span\n className={cn(\"ml-auto text-xs tracking-widest text-muted-foreground\", className)}\n data-slot=\"command-shortcut\"\n {...props}\n />\n );\n};\n\nexport {\n Command,\n // fallow-ignore-next-line unused-export\n CommandDialog,\n CommandInput,\n CommandList,\n CommandEmpty,\n CommandGroup,\n CommandItem,\n CommandShortcut,\n CommandSeparator,\n};\n","const resolveNavigationUrl = (value: string, baseUrl: string): string | null => {\n try {\n const url = new URL(value, baseUrl);\n return url.protocol === \"http:\" || url.protocol === \"https:\" ? url.href : null;\n } catch {\n return null;\n }\n};\n\nexport { resolveNavigationUrl };\n","import { Search } from \"lucide-react\";\nimport { useEffect, useState } from \"react\";\n\nimport { resolveNavigationUrl } from \"../lib/resolve-navigation-url\";\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n} from \"./ui/command\";\nimport { Dialog, DialogContent, DialogTitle, DialogTrigger } from \"./ui/dialog\";\nimport { Button } from \"./ui/button\";\n\ntype CommandMenuItem = { description?: string; title: string; url: string };\n\ntype CommandMenuProps = {\n emptyMessage?: string;\n groupHeading?: string;\n items: Array<CommandMenuItem>;\n placeholder?: string;\n title?: string;\n};\n\nconst CommandMenu = ({\n emptyMessage = \"No results.\",\n groupHeading = \"Posts\",\n items,\n placeholder = \"Search posts…\",\n title = \"Search posts\",\n}: CommandMenuProps) => {\n const [open, setOpen] = useState(false);\n\n useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"k\" && (event.metaKey || event.ctrlKey)) {\n event.preventDefault();\n setOpen((value) => !value);\n }\n };\n document.addEventListener(\"keydown\", handleKeyDown);\n return () => {\n document.removeEventListener(\"keydown\", handleKeyDown);\n };\n }, []);\n\n return (\n <Dialog onOpenChange={setOpen} open={open}>\n <DialogTrigger render={<Button aria-label=\"Open search\" size=\"icon\" variant=\"ghost\" />}>\n <Search />\n </DialogTrigger>\n <DialogContent className=\"p-0\">\n <DialogTitle className=\"sr-only\">{title}</DialogTitle>\n <Command>\n <CommandInput placeholder={placeholder} />\n <CommandList>\n <CommandEmpty>{emptyMessage}</CommandEmpty>\n <CommandGroup heading={groupHeading}>\n {items.map((item) => (\n <CommandItem\n key={item.url}\n onSelect={() => {\n const destination = resolveNavigationUrl(item.url, window.location.href);\n if (destination !== null && destination !== \"\") {\n window.location.href = destination;\n }\n }}\n value={item.title}\n >\n <div className=\"flex flex-col\">\n <span>{item.title}</span>\n {item.description !== undefined && item.description !== \"\" ? (\n <span className=\"text-muted-foreground text-xs\">{item.description}</span>\n ) : null}\n </div>\n </CommandItem>\n ))}\n </CommandGroup>\n </CommandList>\n </Command>\n </DialogContent>\n </Dialog>\n );\n};\n\nexport { CommandMenu };\nexport type { CommandMenuItem, CommandMenuProps };\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,UAAU,EAAE,GAAG,YAA+D;CAClF,OAAO,oBAACC,SAAgB,MAAjB;EAAsB,aAAU;EAAS,GAAI;CAAQ;AAC9D;AAEA,MAAM,iBAAiB,EAAE,GAAG,YAAkE;CAC5F,OAAO,oBAACA,SAAgB,SAAjB;EAAyB,aAAU;EAAiB,GAAI;CAAQ;AACzE;AAEA,MAAM,gBAAgB,EAAE,GAAG,YAAiE;CAC1F,OAAO,oBAACA,SAAgB,QAAjB;EAAwB,aAAU;EAAgB,GAAI;CAAQ;AACvE;AAMA,MAAM,iBAAiB,EACrB,WACA,GAAG,YACwD;CAC3D,OACE,oBAACA,SAAgB,UAAjB;EACE,WAAW,GACT,kIACA,SACF;EACA,aAAU;EACV,GAAI;CACL;AAEL;AAEA,MAAM,iBAAiB,EACrB,UACA,WACA,kBAAkB,MAClB,GAAG,YAGC;CACJ,OACE,qBAAC,cAAD;EAAc,aAAU;YAAxB,CACE,oBAAC,eAAD,CAAgB,IAChB,qBAACA,SAAgB,OAAjB;GACE,WAAW,GACT,wVACA,SACF;GACA,aAAU;GACV,GAAI;aANN,CAQG,UACA,mBACC,qBAACA,SAAgB,OAAjB;IACE,WAAU;IACV,aAAU;IACV,MAAK;cAHP,CAKE,oBAAC,OAAD,CAAQ,IACR,oBAAC,QAAD;KAAM,WAAU;eAAU;IAAW,EAChB;KAEJ;IACX;;AAElB;AAEA,MAAM,gBAAgB,EAAE,WAAW,GAAG,YAAyC;CAC7E,OACE,oBAAC,OAAD;EACE,WAAW,GAAG,gDAAgD,SAAS;EACvE,aAAU;EACV,GAAI;CACL;AAEL;AAEA,MAAM,gBAAgB,EACpB,UACA,WACA,kBAAkB,OAClB,GAAG,YAGC;CACJ,OACE,qBAAC,OAAD;EACE,WAAW,GAAG,0DAA0D,SAAS;EACjF,aAAU;EACV,GAAI;YAHN,CAKG,UACA,mBACC,oBAACA,SAAgB,OAAjB;GAAuB,QAAQ,oBAAC,QAAD,EAAQ,SAAQ,UAAW;aAAG;EAA4B,EAExF;;AAET;AAEA,MAAM,eAAe,EACnB,WACA,GAAG,YACqD;CACxD,OACE,oBAACA,SAAgB,OAAjB;EACE,WAAW,GAAG,sCAAsC,SAAS;EAC7D,aAAU;EACV,GAAI;CACL;AAEL;AAEA,MAAM,qBAAqB,EACzB,WACA,GAAG,YAC2D;CAC9D,OACE,oBAACA,SAAgB,aAAjB;EACE,WAAW,GAAG,iCAAiC,SAAS;EACxD,aAAU;EACV,GAAI;CACL;AAEL;;;;AC3HA,MAAM,WAAW,EAAE,WAAW,GAAG,YAA2D;CAC1F,OACE,oBAACC,WAAD;EACE,WAAW,GACT,6FACA,SACF;EACA,aAAU;EACV,GAAI;CACL;AAEL;AAmCA,MAAM,gBAAgB,EACpB,WACA,GAAG,YACsD;CACzD,OACE,qBAAC,OAAD;EAAK,WAAU;EAA4C,aAAU;YAArE,CACE,oBAAC,YAAD,EAAY,WAAU,6BAA8B,IACpD,oBAACA,UAAiB,OAAlB;GACE,WAAW,GACT,4JACA,SACF;GACA,aAAU;GACV,GAAI;EACL,EACE;;AAET;AAEA,MAAM,eAAe,EACnB,WACA,GAAG,YACqD;CACxD,OACE,oBAACA,UAAiB,MAAlB;EACE,WAAW,GAAG,+DAA+D,SAAS;EACtF,aAAU;EACV,GAAI;CACL;AAEL;AAEA,MAAM,gBAAgB,EAAE,GAAG,YAAiE;CAC1F,OACE,oBAACA,UAAiB,OAAlB;EACE,WAAU;EACV,aAAU;EACV,GAAI;CACL;AAEL;AAEA,MAAM,gBAAgB,EACpB,WACA,GAAG,YACsD;CACzD,OACE,oBAACA,UAAiB,OAAlB;EACE,WAAW,GACT,0NACA,SACF;EACA,aAAU;EACV,GAAI;CACL;AAEL;AAEA,MAAM,oBAAoB,EACxB,WACA,GAAG,YAC0D;CAC7D,OACE,oBAACA,UAAiB,WAAlB;EACE,WAAW,GAAG,wBAAwB,SAAS;EAC/C,aAAU;EACV,GAAI;CACL;AAEL;AAEA,MAAM,eAAe,EACnB,WACA,GAAG,YACqD;CACxD,OACE,oBAACA,UAAiB,MAAlB;EACE,WAAW,GACT,uYACA,SACF;EACA,aAAU;EACV,GAAI;CACL;AAEL;AAEA,MAAM,mBAAmB,EAAE,WAAW,GAAG,YAA0C;CACjF,OACE,oBAAC,QAAD;EACE,WAAW,GAAG,yDAAyD,SAAS;EAChF,aAAU;EACV,GAAI;CACL;AAEL;;;;ACpJA,MAAM,wBAAwB,OAAe,YAAmC;CAC9E,IAAI;EACF,MAAM,MAAM,IAAI,IAAI,OAAO,OAAO;EAClC,OAAO,IAAI,aAAa,WAAW,IAAI,aAAa,WAAW,IAAI,OAAO;CAC5E,QAAQ;EACN,OAAO;CACT;AACF;;;;ACkBA,MAAM,eAAe,EACnB,eAAe,eACf,eAAe,SACf,OACA,cAAc,iBACd,QAAQ,qBACc;CACtB,MAAM,CAAC,MAAM,WAAW,SAAS,KAAK;CAEtC,gBAAgB;EACd,MAAM,iBAAiB,UAAyB;GAC9C,IAAI,MAAM,QAAQ,QAAQ,MAAM,WAAW,MAAM,UAAU;IACzD,MAAM,eAAe;IACrB,SAAS,UAAU,CAAC,KAAK;GAC3B;EACF;EACA,SAAS,iBAAiB,WAAW,aAAa;EAClD,aAAa;GACX,SAAS,oBAAoB,WAAW,aAAa;EACvD;CACF,GAAG,CAAC,CAAC;CAEL,OACE,qBAAC,QAAD;EAAQ,cAAc;EAAe;YAArC,CACE,oBAAC,eAAD;GAAe,QAAQ,oBAAC,QAAD;IAAQ,cAAW;IAAc,MAAK;IAAO,SAAQ;GAAS;aACnF,oBAAC,QAAD,CAAS;EACI,IACf,qBAAC,eAAD;GAAe,WAAU;aAAzB,CACE,oBAAC,aAAD;IAAa,WAAU;cAAW;GAAmB,IACrD,qBAAC,SAAD,aACE,oBAAC,cAAD,EAA2B,YAAc,IACzC,qBAAC,aAAD,aACE,oBAAC,cAAD,YAAe,aAA2B,IAC1C,oBAAC,cAAD;IAAc,SAAS;cACpB,MAAM,KAAK,SACV,oBAAC,aAAD;KAEE,gBAAgB;MACd,MAAM,cAAc,qBAAqB,KAAK,KAAK,OAAO,SAAS,IAAI;MACvE,IAAI,gBAAgB,QAAQ,gBAAgB,IAC1C,OAAO,SAAS,OAAO;KAE3B;KACA,OAAO,KAAK;eAEZ,qBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,oBAAC,QAAD,YAAO,KAAK,MAAY,IACvB,KAAK,gBAAgB,UAAa,KAAK,gBAAgB,KACtD,oBAAC,QAAD;OAAM,WAAU;iBAAiC,KAAK;MAAkB,KACtE,IACD;;IACM,GAfN,KAAK,GAeC,CACd;GACW,EACH,IACN,IACI;IACT;;AAEZ;;;;AC7EA,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"}
|
package/dist/content/index.d.ts
CHANGED
|
@@ -36,12 +36,12 @@ declare const authorSchema: z.ZodObject<{
|
|
|
36
36
|
type Post = z.infer<typeof postSchema>;
|
|
37
37
|
type Tag = z.infer<typeof tagSchema>;
|
|
38
38
|
type Author = z.infer<typeof authorSchema>;
|
|
39
|
-
declare const notDraft:
|
|
39
|
+
declare const notDraft: (post: {
|
|
40
40
|
data: {
|
|
41
41
|
draft?: boolean;
|
|
42
42
|
status?: string;
|
|
43
43
|
};
|
|
44
|
-
}
|
|
44
|
+
}) => boolean;
|
|
45
45
|
//#endregion
|
|
46
46
|
export { type Author, type Post, type Tag, authorSchema, notDraft, postSchema, index_d_exports as t, tagSchema };
|
|
47
47
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/content/index.js
CHANGED
|
@@ -37,7 +37,7 @@ const authorSchema = z.object({
|
|
|
37
37
|
name: z.string().min(1),
|
|
38
38
|
url: z.url().optional()
|
|
39
39
|
});
|
|
40
|
-
const notDraft = (post) => post.data.status !== "DRAFT" &&
|
|
40
|
+
const notDraft = (post) => post.data.status !== "DRAFT" && post.data.draft !== true;
|
|
41
41
|
|
|
42
42
|
//#endregion
|
|
43
43
|
export { authorSchema, notDraft, postSchema, content_exports as t, tagSchema };
|
|
@@ -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().min(1),\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//
|
|
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().min(1),\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\nexport { authorSchema, 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,IAAI,CAAC;CAC7B,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"}
|
package/dist/index.js
CHANGED
package/dist/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { t as cn } from "../cn-DmzdyV07.js";
|
|
2
|
-
import { a as getRelatedPosts, i as slugify, n as readingTime, o as formatDate, r as createPostUrl } from "../lib-
|
|
2
|
+
import { a as getRelatedPosts, i as slugify, n as readingTime, o as formatDate, r as createPostUrl } from "../lib-WSV3WGQr.js";
|
|
3
3
|
|
|
4
4
|
export { cn, createPostUrl, formatDate, getRelatedPosts, readingTime, slugify };
|
|
@@ -8,10 +8,8 @@ const defaultOptions = {
|
|
|
8
8
|
timeZone: "UTC",
|
|
9
9
|
year: "numeric"
|
|
10
10
|
};
|
|
11
|
-
const defaultFormatter = new Intl.DateTimeFormat("en-US", defaultOptions);
|
|
12
11
|
const formatterCache = /* @__PURE__ */ new Map();
|
|
13
|
-
const getFormatter = (locale, options) => {
|
|
14
|
-
const key = `${locale}:${JSON.stringify(options)}`;
|
|
12
|
+
const getFormatter = (key, locale, options) => {
|
|
15
13
|
let formatter = formatterCache.get(key);
|
|
16
14
|
if (!formatter) {
|
|
17
15
|
formatter = new Intl.DateTimeFormat(locale, options);
|
|
@@ -19,12 +17,13 @@ const getFormatter = (locale, options) => {
|
|
|
19
17
|
}
|
|
20
18
|
return formatter;
|
|
21
19
|
};
|
|
22
|
-
const formatDate = (date, locale = "en-US", options
|
|
23
|
-
if (
|
|
24
|
-
|
|
20
|
+
const formatDate = (date, locale = "en-US", options) => {
|
|
21
|
+
if (!options) return getFormatter(locale, locale, defaultOptions).format(date);
|
|
22
|
+
const merged = {
|
|
25
23
|
...defaultOptions,
|
|
26
24
|
...options
|
|
27
|
-
}
|
|
25
|
+
};
|
|
26
|
+
return getFormatter(`${locale}:${JSON.stringify(merged)}`, locale, merged).format(date);
|
|
28
27
|
};
|
|
29
28
|
|
|
30
29
|
//#endregion
|
|
@@ -53,18 +52,18 @@ const slugify = (input) => input.normalize("NFD").replaceAll(/* @__PURE__ */ new
|
|
|
53
52
|
const createPostUrl = (categorySlugMap, defaultCategory) => {
|
|
54
53
|
const postParams = (post) => {
|
|
55
54
|
const canonical = post.data.seo?.canonical_url;
|
|
56
|
-
if (canonical) try {
|
|
55
|
+
if (canonical !== void 0 && canonical !== "") try {
|
|
57
56
|
const segments = new URL(canonical).pathname.split("/").filter(Boolean);
|
|
58
57
|
const category = segments.at(-2);
|
|
59
58
|
const slug = segments.at(-1);
|
|
60
|
-
if (category && slug) return {
|
|
59
|
+
if (category !== void 0 && slug !== void 0) return {
|
|
61
60
|
category,
|
|
62
61
|
slug
|
|
63
62
|
};
|
|
64
63
|
} catch {}
|
|
65
64
|
const first = post.data.categories?.at(0);
|
|
66
65
|
return {
|
|
67
|
-
category: first ? categorySlugMap[first] ?? slugify(first) : defaultCategory,
|
|
66
|
+
category: first !== void 0 && first !== "" ? categorySlugMap[first] ?? slugify(first) : defaultCategory,
|
|
68
67
|
slug: post.id
|
|
69
68
|
};
|
|
70
69
|
};
|
|
@@ -103,4 +102,4 @@ var lib_exports = /* @__PURE__ */ __exportAll({
|
|
|
103
102
|
|
|
104
103
|
//#endregion
|
|
105
104
|
export { getRelatedPosts as a, slugify as i, readingTime as n, formatDate as o, createPostUrl as r, lib_exports as t };
|
|
106
|
-
//# sourceMappingURL=lib-
|
|
105
|
+
//# sourceMappingURL=lib-WSV3WGQr.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lib-WSV3WGQr.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/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","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 { slugify } from \"./slugify\";\n"],"mappings":";;;;AAAA,MAAM,iBAA6C;CACjD,KAAK;CACL,OAAO;CACP,UAAU;CACV,MAAM;AACR;AAEA,MAAM,iCAAiB,IAAI,IAAiC;AAE5D,MAAM,gBAAgB,KAAa,QAAgB,YAAwC;CACzF,IAAI,YAAY,eAAe,IAAI,GAAG;CACtC,IAAI,CAAC,WAAW;EACd,YAAY,IAAI,KAAK,eAAe,QAAQ,OAAO;EACnD,eAAe,IAAI,KAAK,SAAS;CACnC;CACA,OAAO;AACT;AAEA,MAAM,cAAc,MAAY,SAAiB,SAAS,YAAyC;CAGjG,IAAI,CAAC,SACH,OAAO,aAAa,QAAQ,QAAQ,cAAc,CAAC,CAAC,OAAO,IAAI;CAEjE,MAAM,SAAS;EAAE,GAAG;EAAgB,GAAG;CAAQ;CAC/C,OAAO,aAAa,GAAG,OAAO,GAAG,KAAK,UAAU,MAAM,KAAK,QAAQ,MAAM,CAAC,CAAC,OAAO,IAAI;AACxF;;;;ACxBA,MAAM,mBAAsC,SAAY,KAAe,MAAc;CACnF,MAAM,gBAAgB,IAAI,IAAI,QAAQ,KAAK,IAAI;CAC/C,MAAM,SAA4C,CAAC;CAEnD,KAAK,MAAM,QAAQ,KAAK;EACtB,IAAI,KAAK,OAAO,QAAQ,IACtB;EAEF,IAAI,QAAQ;EACZ,KAAK,MAAM,OAAO,KAAK,KAAK,MAC1B,IAAI,cAAc,IAAI,GAAG,GACvB;EAGJ,OAAO,KAAK;GAAE;GAAM;EAAM,CAAC;CAC7B;CAEA,OAAO,OACJ,UAAU,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,CACrC,MAAM,GAAG,CAAC,CAAC,CACX,KAAK,EAAE,WAAW,IAAI;AAC3B;;;;ACvBA,MAAM,WAAW,UACf,MACG,UAAU,KAAK,CAAC,CAChB,2BAAW,wBAAQ,GAAG,EAAE,CAAC,CACzB,YAAY,CAAC,CACb,2BAAW,kCAAgB,GAAG,EAAE,CAAC,CACjC,KAAK,CAAC,CACN,2BAAW,4BAAU,GAAG,GAAG,CAAC,CAC5B,2BAAW,0BAAU,GAAG,EAAE;;;;ACc/B,MAAM,iBACJ,iBACA,oBACmB;CACnB,MAAM,cAAc,SAA+B;EACjD,MAAM,YAAY,KAAK,KAAK,KAAK;EACjC,IAAI,cAAc,UAAa,cAAc,IAC3C,IAAI;GACF,MAAM,WAAW,IAAI,IAAI,SAAS,CAAC,CAAC,SAAS,MAAM,GAAG,CAAC,CAAC,OAAO,OAAO;GACtE,MAAM,WAAW,SAAS,GAAG,EAAE;GAC/B,MAAM,OAAO,SAAS,GAAG,EAAE;GAC3B,IAAI,aAAa,UAAa,SAAS,QACrC,OAAO;IAAE;IAAU;GAAK;EAE5B,QAAQ,CAER;EAEF,MAAM,QAAQ,KAAK,KAAK,YAAY,GAAG,CAAC;EAKxC,OAAO;GAAE,UAHP,UAAU,UAAa,UAAU,KAC5B,gBAAgB,UAAU,QAAQ,KAAK,IACxC;GACa,MAAM,KAAK;EAAG;CACnC;CAEA,MAAM,WAAW,SAA2B;EAC1C,MAAM,EAAE,UAAU,SAAS,WAAW,IAAI;EAC1C,OAAO,IAAI,SAAS,GAAG,KAAK;CAC9B;CAEA,OAAO;EAAE;EAAY;CAAQ;AAC/B;;;;ACtDA,MAAM,mBAAmB;AAEzB,MAAM,eAAe,UAAkB;CACrC,MAAM,WAAW,MACd,2BAAW,0BAAU,GAAG,EAAE,CAAC,CAC3B,2BAAW,6BAAY,GAAG,GAAG,CAAC,CAC9B,2BAAW,uBAAM,GAAG,GAAG,CAAC,CACxB,KAAK;CACR,MAAM,QAAQ,SAAS,WAAW,IAAI,IAAI,SAAS,MAAM,GAAG,CAAC,CAAC;CAE9D,OAAO;EAAE,SADO,KAAK,IAAI,GAAG,KAAK,KAAK,QAAQ,gBAAgB,CAC/C;EAAG;CAAM;AAC1B"}
|
package/dist/styles/globals.css
CHANGED
|
@@ -46,6 +46,12 @@
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
.dark {
|
|
49
|
+
/* `:root` opts into `light dark`, which follows the OS preference. When the
|
|
50
|
+
toggle forces dark on a light-OS visitor, native UI (scrollbars, form
|
|
51
|
+
controls) would stay light without this override. The `:root:not(.dark)`
|
|
52
|
+
mirror below covers the opposite direction: light forced on a dark OS. */
|
|
53
|
+
color-scheme: dark;
|
|
54
|
+
|
|
49
55
|
--color-background: oklch(0.145 0 0);
|
|
50
56
|
--color-foreground: oklch(0.985 0 0);
|
|
51
57
|
--color-card: oklch(0.145 0 0);
|
|
@@ -66,6 +72,10 @@
|
|
|
66
72
|
--color-ring: oklch(0.556 0 0);
|
|
67
73
|
}
|
|
68
74
|
|
|
75
|
+
:root:not(.dark) {
|
|
76
|
+
color-scheme: light;
|
|
77
|
+
}
|
|
78
|
+
|
|
69
79
|
/* Opt-in to cross-document view transitions on same-origin navigations.
|
|
70
80
|
Gated by prefers-reduced-motion: users who request reduced motion get
|
|
71
81
|
instant navigation. Browsers without support degrade to instant nav. */
|
package/package.json
CHANGED
|
@@ -27,13 +27,13 @@ const visible: Array<Heading> = headings
|
|
|
27
27
|
<nav class:list={["text-sm", className]} aria-label="Table of contents">
|
|
28
28
|
<p class="text-muted-foreground mb-2 font-medium tracking-wide uppercase">{heading}</p>
|
|
29
29
|
<ul class="space-y-1">
|
|
30
|
-
{visible.map((
|
|
31
|
-
<li class:list={["leading-snug",
|
|
30
|
+
{visible.map((item) => (
|
|
31
|
+
<li class:list={["leading-snug", item.depth === 3 && "pl-4"]}>
|
|
32
32
|
<a
|
|
33
|
-
href={`#${
|
|
33
|
+
href={`#${item.slug}`}
|
|
34
34
|
class="text-muted-foreground hover:text-foreground block transition-colors"
|
|
35
35
|
>
|
|
36
|
-
{
|
|
36
|
+
{item.text}
|
|
37
37
|
</a>
|
|
38
38
|
</li>
|
|
39
39
|
))}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Search } from "lucide-react";
|
|
2
2
|
import { useEffect, useState } from "react";
|
|
3
3
|
|
|
4
|
+
import { resolveNavigationUrl } from "../lib/resolve-navigation-url";
|
|
4
5
|
import {
|
|
5
6
|
Command,
|
|
6
7
|
CommandEmpty,
|
|
@@ -60,13 +61,16 @@ const CommandMenu = ({
|
|
|
60
61
|
<CommandItem
|
|
61
62
|
key={item.url}
|
|
62
63
|
onSelect={() => {
|
|
63
|
-
|
|
64
|
+
const destination = resolveNavigationUrl(item.url, window.location.href);
|
|
65
|
+
if (destination !== null && destination !== "") {
|
|
66
|
+
window.location.href = destination;
|
|
67
|
+
}
|
|
64
68
|
}}
|
|
65
69
|
value={item.title}
|
|
66
70
|
>
|
|
67
71
|
<div className="flex flex-col">
|
|
68
72
|
<span>{item.title}</span>
|
|
69
|
-
{item.description ? (
|
|
73
|
+
{item.description !== undefined && item.description !== "" ? (
|
|
70
74
|
<span className="text-muted-foreground text-xs">{item.description}</span>
|
|
71
75
|
) : null}
|
|
72
76
|
</div>
|
|
@@ -5,7 +5,7 @@ import { SearchIcon } from "lucide-react";
|
|
|
5
5
|
import { cn } from "../../lib/cn";
|
|
6
6
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "./dialog";
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
const Command = ({ className, ...props }: React.ComponentProps<typeof CommandPrimitive>) => {
|
|
9
9
|
return (
|
|
10
10
|
<CommandPrimitive
|
|
11
11
|
className={cn(
|
|
@@ -16,9 +16,9 @@ function Command({ className, ...props }: React.ComponentProps<typeof CommandPri
|
|
|
16
16
|
{...props}
|
|
17
17
|
/>
|
|
18
18
|
);
|
|
19
|
-
}
|
|
19
|
+
};
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
const CommandDialog = ({
|
|
22
22
|
children,
|
|
23
23
|
className,
|
|
24
24
|
description = "Search for a command to run...",
|
|
@@ -31,7 +31,7 @@ function CommandDialog({
|
|
|
31
31
|
description?: string;
|
|
32
32
|
showCloseButton?: boolean;
|
|
33
33
|
title?: string;
|
|
34
|
-
}) {
|
|
34
|
+
}) => {
|
|
35
35
|
return (
|
|
36
36
|
<Dialog {...props}>
|
|
37
37
|
<DialogContent
|
|
@@ -49,12 +49,12 @@ function CommandDialog({
|
|
|
49
49
|
</DialogContent>
|
|
50
50
|
</Dialog>
|
|
51
51
|
);
|
|
52
|
-
}
|
|
52
|
+
};
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
const CommandInput = ({
|
|
55
55
|
className,
|
|
56
56
|
...props
|
|
57
|
-
}: React.ComponentProps<typeof CommandPrimitive.Input>) {
|
|
57
|
+
}: React.ComponentProps<typeof CommandPrimitive.Input>) => {
|
|
58
58
|
return (
|
|
59
59
|
<div className="flex h-9 items-center gap-2 border-b px-3" data-slot="command-input-wrapper">
|
|
60
60
|
<SearchIcon className="size-4 shrink-0 opacity-50" />
|
|
@@ -68,9 +68,12 @@ function CommandInput({
|
|
|
68
68
|
/>
|
|
69
69
|
</div>
|
|
70
70
|
);
|
|
71
|
-
}
|
|
71
|
+
};
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
const CommandList = ({
|
|
74
|
+
className,
|
|
75
|
+
...props
|
|
76
|
+
}: React.ComponentProps<typeof CommandPrimitive.List>) => {
|
|
74
77
|
return (
|
|
75
78
|
<CommandPrimitive.List
|
|
76
79
|
className={cn("max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto", className)}
|
|
@@ -78,9 +81,9 @@ function CommandList({ className, ...props }: React.ComponentProps<typeof Comman
|
|
|
78
81
|
{...props}
|
|
79
82
|
/>
|
|
80
83
|
);
|
|
81
|
-
}
|
|
84
|
+
};
|
|
82
85
|
|
|
83
|
-
|
|
86
|
+
const CommandEmpty = ({ ...props }: React.ComponentProps<typeof CommandPrimitive.Empty>) => {
|
|
84
87
|
return (
|
|
85
88
|
<CommandPrimitive.Empty
|
|
86
89
|
className="py-6 text-center text-sm"
|
|
@@ -88,12 +91,12 @@ function CommandEmpty({ ...props }: React.ComponentProps<typeof CommandPrimitive
|
|
|
88
91
|
{...props}
|
|
89
92
|
/>
|
|
90
93
|
);
|
|
91
|
-
}
|
|
94
|
+
};
|
|
92
95
|
|
|
93
|
-
|
|
96
|
+
const CommandGroup = ({
|
|
94
97
|
className,
|
|
95
98
|
...props
|
|
96
|
-
}: React.ComponentProps<typeof CommandPrimitive.Group>) {
|
|
99
|
+
}: React.ComponentProps<typeof CommandPrimitive.Group>) => {
|
|
97
100
|
return (
|
|
98
101
|
<CommandPrimitive.Group
|
|
99
102
|
className={cn(
|
|
@@ -104,12 +107,12 @@ function CommandGroup({
|
|
|
104
107
|
{...props}
|
|
105
108
|
/>
|
|
106
109
|
);
|
|
107
|
-
}
|
|
110
|
+
};
|
|
108
111
|
|
|
109
|
-
|
|
112
|
+
const CommandSeparator = ({
|
|
110
113
|
className,
|
|
111
114
|
...props
|
|
112
|
-
}: React.ComponentProps<typeof CommandPrimitive.Separator>) {
|
|
115
|
+
}: React.ComponentProps<typeof CommandPrimitive.Separator>) => {
|
|
113
116
|
return (
|
|
114
117
|
<CommandPrimitive.Separator
|
|
115
118
|
className={cn("-mx-1 h-px bg-border", className)}
|
|
@@ -117,9 +120,12 @@ function CommandSeparator({
|
|
|
117
120
|
{...props}
|
|
118
121
|
/>
|
|
119
122
|
);
|
|
120
|
-
}
|
|
123
|
+
};
|
|
121
124
|
|
|
122
|
-
|
|
125
|
+
const CommandItem = ({
|
|
126
|
+
className,
|
|
127
|
+
...props
|
|
128
|
+
}: React.ComponentProps<typeof CommandPrimitive.Item>) => {
|
|
123
129
|
return (
|
|
124
130
|
<CommandPrimitive.Item
|
|
125
131
|
className={cn(
|
|
@@ -130,9 +136,9 @@ function CommandItem({ className, ...props }: React.ComponentProps<typeof Comman
|
|
|
130
136
|
{...props}
|
|
131
137
|
/>
|
|
132
138
|
);
|
|
133
|
-
}
|
|
139
|
+
};
|
|
134
140
|
|
|
135
|
-
|
|
141
|
+
const CommandShortcut = ({ className, ...props }: React.ComponentProps<"span">) => {
|
|
136
142
|
return (
|
|
137
143
|
<span
|
|
138
144
|
className={cn("ml-auto text-xs tracking-widest text-muted-foreground", className)}
|
|
@@ -140,7 +146,7 @@ function CommandShortcut({ className, ...props }: React.ComponentProps<"span">)
|
|
|
140
146
|
{...props}
|
|
141
147
|
/>
|
|
142
148
|
);
|
|
143
|
-
}
|
|
149
|
+
};
|
|
144
150
|
|
|
145
151
|
export {
|
|
146
152
|
Command,
|
|
@@ -5,26 +5,26 @@ import { XIcon } from "lucide-react";
|
|
|
5
5
|
import { cn } from "../../lib/cn";
|
|
6
6
|
import { Button } from "./button";
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
const Dialog = ({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) => {
|
|
9
9
|
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
|
|
10
|
-
}
|
|
10
|
+
};
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
const DialogTrigger = ({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>) => {
|
|
13
13
|
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
|
|
14
|
-
}
|
|
14
|
+
};
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
const DialogPortal = ({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>) => {
|
|
17
17
|
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
|
|
18
|
-
}
|
|
18
|
+
};
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
const DialogClose = ({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>) => {
|
|
21
21
|
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
|
|
22
|
-
}
|
|
22
|
+
};
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
const DialogOverlay = ({
|
|
25
25
|
className,
|
|
26
26
|
...props
|
|
27
|
-
}: React.ComponentProps<typeof DialogPrimitive.Backdrop>) {
|
|
27
|
+
}: React.ComponentProps<typeof DialogPrimitive.Backdrop>) => {
|
|
28
28
|
return (
|
|
29
29
|
<DialogPrimitive.Backdrop
|
|
30
30
|
className={cn(
|
|
@@ -35,16 +35,16 @@ function DialogOverlay({
|
|
|
35
35
|
{...props}
|
|
36
36
|
/>
|
|
37
37
|
);
|
|
38
|
-
}
|
|
38
|
+
};
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
const DialogContent = ({
|
|
41
41
|
children,
|
|
42
42
|
className,
|
|
43
43
|
showCloseButton = true,
|
|
44
44
|
...props
|
|
45
45
|
}: React.ComponentProps<typeof DialogPrimitive.Popup> & {
|
|
46
46
|
showCloseButton?: boolean;
|
|
47
|
-
}) {
|
|
47
|
+
}) => {
|
|
48
48
|
return (
|
|
49
49
|
<DialogPortal data-slot="dialog-portal">
|
|
50
50
|
<DialogOverlay />
|
|
@@ -70,9 +70,9 @@ function DialogContent({
|
|
|
70
70
|
</DialogPrimitive.Popup>
|
|
71
71
|
</DialogPortal>
|
|
72
72
|
);
|
|
73
|
-
}
|
|
73
|
+
};
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
const DialogHeader = ({ className, ...props }: React.ComponentProps<"div">) => {
|
|
76
76
|
return (
|
|
77
77
|
<div
|
|
78
78
|
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
|
|
@@ -80,16 +80,16 @@ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
80
80
|
{...props}
|
|
81
81
|
/>
|
|
82
82
|
);
|
|
83
|
-
}
|
|
83
|
+
};
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
const DialogFooter = ({
|
|
86
86
|
children,
|
|
87
87
|
className,
|
|
88
88
|
showCloseButton = false,
|
|
89
89
|
...props
|
|
90
90
|
}: React.ComponentProps<"div"> & {
|
|
91
91
|
showCloseButton?: boolean;
|
|
92
|
-
}) {
|
|
92
|
+
}) => {
|
|
93
93
|
return (
|
|
94
94
|
<div
|
|
95
95
|
className={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)}
|
|
@@ -102,9 +102,12 @@ function DialogFooter({
|
|
|
102
102
|
)}
|
|
103
103
|
</div>
|
|
104
104
|
);
|
|
105
|
-
}
|
|
105
|
+
};
|
|
106
106
|
|
|
107
|
-
|
|
107
|
+
const DialogTitle = ({
|
|
108
|
+
className,
|
|
109
|
+
...props
|
|
110
|
+
}: React.ComponentProps<typeof DialogPrimitive.Title>) => {
|
|
108
111
|
return (
|
|
109
112
|
<DialogPrimitive.Title
|
|
110
113
|
className={cn("text-lg leading-none font-semibold", className)}
|
|
@@ -112,12 +115,12 @@ function DialogTitle({ className, ...props }: React.ComponentProps<typeof Dialog
|
|
|
112
115
|
{...props}
|
|
113
116
|
/>
|
|
114
117
|
);
|
|
115
|
-
}
|
|
118
|
+
};
|
|
116
119
|
|
|
117
|
-
|
|
120
|
+
const DialogDescription = ({
|
|
118
121
|
className,
|
|
119
122
|
...props
|
|
120
|
-
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
|
|
123
|
+
}: React.ComponentProps<typeof DialogPrimitive.Description>) => {
|
|
121
124
|
return (
|
|
122
125
|
<DialogPrimitive.Description
|
|
123
126
|
className={cn("text-sm text-muted-foreground", className)}
|
|
@@ -125,7 +128,7 @@ function DialogDescription({
|
|
|
125
128
|
{...props}
|
|
126
129
|
/>
|
|
127
130
|
);
|
|
128
|
-
}
|
|
131
|
+
};
|
|
129
132
|
|
|
130
133
|
export {
|
|
131
134
|
Dialog,
|
package/src/content/index.ts
CHANGED
|
@@ -40,11 +40,10 @@ 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
|
-
//
|
|
44
|
-
// (`data.status !== "DRAFT" && !data.draft`).
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
post.data.status !== "DRAFT" && !post.data.draft;
|
|
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
|
+
const notDraft = (post: { data: { draft?: boolean; status?: string } }): boolean =>
|
|
46
|
+
post.data.status !== "DRAFT" && post.data.draft !== true;
|
|
48
47
|
|
|
49
48
|
export { authorSchema, notDraft, postSchema, tagSchema };
|
|
50
49
|
export type { Author, Post, Tag };
|
package/src/lib/format-date.ts
CHANGED
|
@@ -5,13 +5,9 @@ const defaultOptions: Intl.DateTimeFormatOptions = {
|
|
|
5
5
|
year: "numeric",
|
|
6
6
|
};
|
|
7
7
|
|
|
8
|
-
// Hoisted so the default-arg call (the hot path) skips the JSON.stringify cache key.
|
|
9
|
-
const defaultFormatter = new Intl.DateTimeFormat("en-US", defaultOptions);
|
|
10
|
-
|
|
11
8
|
const formatterCache = new Map<string, Intl.DateTimeFormat>();
|
|
12
9
|
|
|
13
|
-
const getFormatter = (locale: string, options: Intl.DateTimeFormatOptions) => {
|
|
14
|
-
const key = `${locale}:${JSON.stringify(options)}`;
|
|
10
|
+
const getFormatter = (key: string, locale: string, options: Intl.DateTimeFormatOptions) => {
|
|
15
11
|
let formatter = formatterCache.get(key);
|
|
16
12
|
if (!formatter) {
|
|
17
13
|
formatter = new Intl.DateTimeFormat(locale, options);
|
|
@@ -20,16 +16,14 @@ const getFormatter = (locale: string, options: Intl.DateTimeFormatOptions) => {
|
|
|
20
16
|
return formatter;
|
|
21
17
|
};
|
|
22
18
|
|
|
23
|
-
const formatDate = (
|
|
24
|
-
date
|
|
25
|
-
|
|
26
|
-
options
|
|
27
|
-
|
|
28
|
-
const isDefaultCall = locale === "en-US" && options === defaultOptions;
|
|
29
|
-
if (isDefaultCall) {
|
|
30
|
-
return defaultFormatter.format(date);
|
|
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
|
+
if (!options) {
|
|
23
|
+
return getFormatter(locale, locale, defaultOptions).format(date);
|
|
31
24
|
}
|
|
32
|
-
|
|
25
|
+
const merged = { ...defaultOptions, ...options };
|
|
26
|
+
return getFormatter(`${locale}:${JSON.stringify(merged)}`, locale, merged).format(date);
|
|
33
27
|
};
|
|
34
28
|
|
|
35
29
|
export { formatDate };
|
package/src/lib/post-url.ts
CHANGED
|
@@ -26,20 +26,23 @@ const createPostUrl = (
|
|
|
26
26
|
): PostUrlBuilder => {
|
|
27
27
|
const postParams = (post: PostLike): PostParams => {
|
|
28
28
|
const canonical = post.data.seo?.canonical_url;
|
|
29
|
-
if (canonical) {
|
|
29
|
+
if (canonical !== undefined && canonical !== "") {
|
|
30
30
|
try {
|
|
31
31
|
const segments = new URL(canonical).pathname.split("/").filter(Boolean);
|
|
32
32
|
const category = segments.at(-2);
|
|
33
33
|
const slug = segments.at(-1);
|
|
34
|
-
if (category && slug) {
|
|
34
|
+
if (category !== undefined && slug !== undefined) {
|
|
35
35
|
return { category, slug };
|
|
36
36
|
}
|
|
37
37
|
} catch {
|
|
38
|
-
// Malformed canonical_url
|
|
38
|
+
// Malformed canonical_url; fall through to category-derived logic.
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
const first = post.data.categories?.at(0);
|
|
42
|
-
const category =
|
|
42
|
+
const category =
|
|
43
|
+
first !== undefined && first !== ""
|
|
44
|
+
? (categorySlugMap[first] ?? slugify(first))
|
|
45
|
+
: defaultCategory;
|
|
43
46
|
return { category, slug: post.id };
|
|
44
47
|
};
|
|
45
48
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { resolveNavigationUrl } from "./resolve-navigation-url";
|
|
4
|
+
|
|
5
|
+
const baseUrl = "https://example.com/blog/current";
|
|
6
|
+
|
|
7
|
+
describe("resolveNavigationUrl", () => {
|
|
8
|
+
it.each([
|
|
9
|
+
["/posts/hello?ref=search#comments", "https://example.com/posts/hello?ref=search#comments"],
|
|
10
|
+
["../archive", "https://example.com/archive"],
|
|
11
|
+
["https://other.example/post", "https://other.example/post"],
|
|
12
|
+
["http://other.example/post", "http://other.example/post"],
|
|
13
|
+
])("resolves a web URL: %s", (value, expected) => {
|
|
14
|
+
expect(resolveNavigationUrl(value, baseUrl)).toBe(expected);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const unsafeScheme = ["java", "script:alert(1)"].join("");
|
|
18
|
+
|
|
19
|
+
it.each([unsafeScheme, "data:text/html,hello", "mailto:hello@example.com", "http://["])(
|
|
20
|
+
"rejects a non-web or malformed URL: %s",
|
|
21
|
+
(value) => {
|
|
22
|
+
expect(resolveNavigationUrl(value, baseUrl)).toBeNull();
|
|
23
|
+
},
|
|
24
|
+
);
|
|
25
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const resolveNavigationUrl = (value: string, baseUrl: string): string | null => {
|
|
2
|
+
try {
|
|
3
|
+
const url = new URL(value, baseUrl);
|
|
4
|
+
return url.protocol === "http:" || url.protocol === "https:" ? url.href : null;
|
|
5
|
+
} catch {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export { resolveNavigationUrl };
|
package/src/styles/globals.css
CHANGED
|
@@ -46,6 +46,12 @@
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
.dark {
|
|
49
|
+
/* `:root` opts into `light dark`, which follows the OS preference. When the
|
|
50
|
+
toggle forces dark on a light-OS visitor, native UI (scrollbars, form
|
|
51
|
+
controls) would stay light without this override. The `:root:not(.dark)`
|
|
52
|
+
mirror below covers the opposite direction: light forced on a dark OS. */
|
|
53
|
+
color-scheme: dark;
|
|
54
|
+
|
|
49
55
|
--color-background: oklch(0.145 0 0);
|
|
50
56
|
--color-foreground: oklch(0.985 0 0);
|
|
51
57
|
--color-card: oklch(0.145 0 0);
|
|
@@ -66,6 +72,10 @@
|
|
|
66
72
|
--color-ring: oklch(0.556 0 0);
|
|
67
73
|
}
|
|
68
74
|
|
|
75
|
+
:root:not(.dark) {
|
|
76
|
+
color-scheme: light;
|
|
77
|
+
}
|
|
78
|
+
|
|
69
79
|
/* Opt-in to cross-document view transitions on same-origin navigations.
|
|
70
80
|
Gated by prefers-reduced-motion: users who request reduced motion get
|
|
71
81
|
instant navigation. Browsers without support degrade to instant nav. */
|
package/dist/lib-mtnJ1lv8.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"lib-mtnJ1lv8.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/index.ts"],"sourcesContent":["const defaultOptions: Intl.DateTimeFormatOptions = {\n day: \"numeric\",\n month: \"long\",\n timeZone: \"UTC\",\n year: \"numeric\",\n};\n\n// Hoisted so the default-arg call (the hot path) skips the JSON.stringify cache key.\nconst defaultFormatter = new Intl.DateTimeFormat(\"en-US\", defaultOptions);\n\nconst formatterCache = new Map<string, Intl.DateTimeFormat>();\n\nconst getFormatter = (locale: string, options: Intl.DateTimeFormatOptions) => {\n const key = `${locale}:${JSON.stringify(options)}`;\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 = (\n date: Date,\n locale: string = \"en-US\",\n options: Intl.DateTimeFormatOptions = defaultOptions,\n) => {\n const isDefaultCall = locale === \"en-US\" && options === defaultOptions;\n if (isDefaultCall) {\n return defaultFormatter.format(date);\n }\n return getFormatter(locale, { ...defaultOptions, ...options }).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) {\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 && slug) {\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 = first ? (categorySlugMap[first] ?? slugify(first)) : 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","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 { slugify } from \"./slugify\";\n"],"mappings":";;;;AAAA,MAAM,iBAA6C;CACjD,KAAK;CACL,OAAO;CACP,UAAU;CACV,MAAM;AACR;AAGA,MAAM,mBAAmB,IAAI,KAAK,eAAe,SAAS,cAAc;AAExE,MAAM,iCAAiB,IAAI,IAAiC;AAE5D,MAAM,gBAAgB,QAAgB,YAAwC;CAC5E,MAAM,MAAM,GAAG,OAAO,GAAG,KAAK,UAAU,OAAO;CAC/C,IAAI,YAAY,eAAe,IAAI,GAAG;CACtC,IAAI,CAAC,WAAW;EACd,YAAY,IAAI,KAAK,eAAe,QAAQ,OAAO;EACnD,eAAe,IAAI,KAAK,SAAS;CACnC;CACA,OAAO;AACT;AAEA,MAAM,cACJ,MACA,SAAiB,SACjB,UAAsC,mBACnC;CAEH,IADsB,WAAW,WAAW,YAAY,gBAEtD,OAAO,iBAAiB,OAAO,IAAI;CAErC,OAAO,aAAa,QAAQ;EAAE,GAAG;EAAgB,GAAG;CAAQ,CAAC,CAAC,CAAC,OAAO,IAAI;AAC5E;;;;AC9BA,MAAM,mBAAsC,SAAY,KAAe,MAAc;CACnF,MAAM,gBAAgB,IAAI,IAAI,QAAQ,KAAK,IAAI;CAC/C,MAAM,SAA4C,CAAC;CAEnD,KAAK,MAAM,QAAQ,KAAK;EACtB,IAAI,KAAK,OAAO,QAAQ,IACtB;EAEF,IAAI,QAAQ;EACZ,KAAK,MAAM,OAAO,KAAK,KAAK,MAC1B,IAAI,cAAc,IAAI,GAAG,GACvB;EAGJ,OAAO,KAAK;GAAE;GAAM;EAAM,CAAC;CAC7B;CAEA,OAAO,OACJ,UAAU,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,CACrC,MAAM,GAAG,CAAC,CAAC,CACX,KAAK,EAAE,WAAW,IAAI;AAC3B;;;;ACvBA,MAAM,WAAW,UACf,MACG,UAAU,KAAK,CAAC,CAChB,2BAAW,wBAAQ,GAAG,EAAE,CAAC,CACzB,YAAY,CAAC,CACb,2BAAW,kCAAgB,GAAG,EAAE,CAAC,CACjC,KAAK,CAAC,CACN,2BAAW,4BAAU,GAAG,GAAG,CAAC,CAC5B,2BAAW,0BAAU,GAAG,EAAE;;;;ACc/B,MAAM,iBACJ,iBACA,oBACmB;CACnB,MAAM,cAAc,SAA+B;EACjD,MAAM,YAAY,KAAK,KAAK,KAAK;EACjC,IAAI,WACF,IAAI;GACF,MAAM,WAAW,IAAI,IAAI,SAAS,CAAC,CAAC,SAAS,MAAM,GAAG,CAAC,CAAC,OAAO,OAAO;GACtE,MAAM,WAAW,SAAS,GAAG,EAAE;GAC/B,MAAM,OAAO,SAAS,GAAG,EAAE;GAC3B,IAAI,YAAY,MACd,OAAO;IAAE;IAAU;GAAK;EAE5B,QAAQ,CAER;EAEF,MAAM,QAAQ,KAAK,KAAK,YAAY,GAAG,CAAC;EAExC,OAAO;GAAE,UADQ,QAAS,gBAAgB,UAAU,QAAQ,KAAK,IAAK;GACnD,MAAM,KAAK;EAAG;CACnC;CAEA,MAAM,WAAW,SAA2B;EAC1C,MAAM,EAAE,UAAU,SAAS,WAAW,IAAI;EAC1C,OAAO,IAAI,SAAS,GAAG,KAAK;CAC9B;CAEA,OAAO;EAAE;EAAY;CAAQ;AAC/B;;;;ACnDA,MAAM,mBAAmB;AAEzB,MAAM,eAAe,UAAkB;CACrC,MAAM,WAAW,MACd,2BAAW,0BAAU,GAAG,EAAE,CAAC,CAC3B,2BAAW,6BAAY,GAAG,GAAG,CAAC,CAC9B,2BAAW,uBAAM,GAAG,GAAG,CAAC,CACxB,KAAK;CACR,MAAM,QAAQ,SAAS,WAAW,IAAI,IAAI,SAAS,MAAM,GAAG,CAAC,CAAC;CAE9D,OAAO;EAAE,SADO,KAAK,IAAI,GAAG,KAAK,KAAK,QAAQ,gBAAgB,CAC/C;EAAG;CAAM;AAC1B"}
|