refactor: biome lint
All checks were successful
build dist / build-dist (push) Successful in 32s

This commit is contained in:
z0x 2025-04-24 22:12:22 -04:00
parent 844d31754d
commit 36870785bc
35 changed files with 1344 additions and 1330 deletions

View file

@ -1,20 +1,20 @@
---
import { Separator } from '@/components/ui/separator'
import { formatDate, readingTime } from '@/lib/utils'
import { Image } from 'astro:assets'
import type { CollectionEntry } from 'astro:content'
import Link from './Link.astro'
import { Image } from "astro:assets";
import type { CollectionEntry } from "astro:content";
import { Separator } from "@/components/ui/separator";
import { formatDate, readingTime } from "@/lib/utils";
import Link from "./Link.astro";
type Props = {
entry: CollectionEntry<'blog'>
}
entry: CollectionEntry<"blog">;
};
const { entry } = Astro.props as {
entry: CollectionEntry<'blog'>
}
entry: CollectionEntry<"blog">;
};
const formattedDate = formatDate(entry.data.date)
const readTime = readingTime(entry.body!)
const formattedDate = formatDate(entry.data.date);
const readTime = readingTime(entry.body!);
---
<div

View file

@ -6,21 +6,21 @@ import {
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from '@/components/ui/breadcrumb'
import { Icon } from 'astro-icon/components'
} from "@/components/ui/breadcrumb";
import { Icon } from "astro-icon/components";
export interface BreadcrumbItem {
href?: string
label: string
icon?: string
href?: string;
label: string;
icon?: string;
}
interface Props {
items: BreadcrumbItem[]
class?: string
items: BreadcrumbItem[];
class?: string;
}
const { items, class: className } = Astro.props
const { items, class: className } = Astro.props;
---
<Breadcrumb className={className}>

View file

@ -1,11 +1,11 @@
---
import { cn } from '@/lib/utils'
import { cn } from "@/lib/utils";
interface Props {
class?: string
class?: string;
}
const { class: className } = Astro.props
const { class: className } = Astro.props;
---
<div class={cn('mx-auto max-w-(--breakpoint-md) px-4', className)}>

View file

@ -1,8 +1,8 @@
---
import Container from '@/components/Container.astro'
import { Separator } from '@/components/ui/separator'
import { SOCIAL_LINKS } from '@/consts'
import SocialIcons from './SocialIcons.astro'
import Container from "@/components/Container.astro";
import { Separator } from "@/components/ui/separator";
import { SOCIAL_LINKS } from "@/consts";
import SocialIcons from "./SocialIcons.astro";
---
<footer class="py-4">

View file

@ -1,22 +1,22 @@
---
import '@fontsource-variable/geist'
import '@fontsource-variable/geist-mono'
import '../styles/callout.css'
import '../styles/global.css'
import '../styles/typography.css'
import "@fontsource-variable/geist";
import "@fontsource-variable/geist-mono";
import "../styles/callout.css";
import "../styles/global.css";
import "../styles/typography.css";
import { SITE } from '@/consts'
import { ClientRouter } from 'astro:transitions'
import { ClientRouter } from "astro:transitions";
import { SITE } from "@/consts";
interface Props {
title: string
description: string
image?: string
title: string;
description: string;
image?: string;
}
const canonicalURL = new URL(Astro.url.pathname, Astro.site)
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
const { title, description, image = '/static/twitter-card.png' } = Astro.props
const { title, description, image = "/static/twitter-card.png" } = Astro.props;
---
<meta charset="utf-8" />

View file

@ -1,8 +1,8 @@
---
import Container from '@/components/Container.astro'
import Link from '@/components/Link.astro'
import { ModeToggle } from '@/components/ui/mode-toggle'
import { SITE } from '@/consts'
import Container from "@/components/Container.astro";
import Link from "@/components/Link.astro";
import { ModeToggle } from "@/components/ui/mode-toggle";
import { SITE } from "@/consts";
---
<header

View file

@ -1,15 +1,15 @@
---
import { cn } from '@/lib/utils'
import { cn } from "@/lib/utils";
type Props = {
href: string
external?: boolean
class?: string
underline?: boolean
[key: string]: any
}
href: string;
external?: boolean;
class?: string;
underline?: boolean;
[key: string]: any;
};
const { href, external, class: className, underline, ...rest } = Astro.props
const { href, external, class: className, underline, ...rest } = Astro.props;
---
<a

View file

@ -1,10 +1,10 @@
---
import Link from '@/components/Link.astro'
import { buttonVariants } from '@/components/ui/button'
import { cn } from '@/lib/utils'
import { Icon } from 'astro-icon/components'
import Link from "@/components/Link.astro";
import { buttonVariants } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { Icon } from "astro-icon/components";
const { prevPost, nextPost } = Astro.props
const { prevPost, nextPost } = Astro.props;
---
<div class="col-start-2 grid grid-cols-1 gap-4 sm:grid-cols-2">

View file

@ -1,15 +1,15 @@
---
import Link from '@/components/Link.astro'
import { buttonVariants } from '@/components/ui/button'
import { ICON_MAP } from '@/consts'
import type { SocialLink } from '@/types'
import { Icon } from 'astro-icon/components'
import Link from "@/components/Link.astro";
import { buttonVariants } from "@/components/ui/button";
import { ICON_MAP } from "@/consts";
import type { SocialLink } from "@/types";
import { Icon } from "astro-icon/components";
interface Props {
links: SocialLink[]
links: SocialLink[];
}
const { links } = Astro.props
const { links } = Astro.props;
---
<ul class="flex flex-wrap gap-2" role="list">

View file

@ -1,23 +1,23 @@
---
import { ScrollArea } from '@/components/ui/scroll-area'
import { cn } from '@/lib/utils'
import type { MarkdownHeading } from 'astro'
import { Icon } from 'astro-icon/components'
import { ScrollArea } from "@/components/ui/scroll-area";
import { cn } from "@/lib/utils";
import type { MarkdownHeading } from "astro";
import { Icon } from "astro-icon/components";
type Props = {
headings: MarkdownHeading[]
}
headings: MarkdownHeading[];
};
const { headings } = Astro.props
const { headings } = Astro.props;
function getHeadingMargin(depth: number): string {
const margins: Record<number, string> = {
3: 'ml-4',
4: 'ml-8',
5: 'ml-12',
6: 'ml-16',
}
return margins[depth] || ''
3: "ml-4",
4: "ml-8",
5: "ml-12",
6: "ml-16",
};
return margins[depth] || "";
}
---

View file

@ -1,8 +1,8 @@
---
import Link from './Link.astro'
import type { Heading } from './TableOfContents.astro'
import Link from "./Link.astro";
import type { Heading } from "./TableOfContents.astro";
const { heading } = Astro.props
const { heading } = Astro.props;
---
<li

View file

@ -1,7 +1,7 @@
import * as React from 'react'
import * as AvatarPrimitive from '@radix-ui/react-avatar'
import * as AvatarPrimitive from "@radix-ui/react-avatar";
import type * as React from "react";
import { cn } from '@/lib/utils'
import { cn } from "@/lib/utils";
function Avatar({
className,
@ -11,12 +11,12 @@ function Avatar({
<AvatarPrimitive.Root
data-slot="avatar"
className={cn(
'relative flex size-8 shrink-0 overflow-hidden rounded-full',
"relative flex size-8 shrink-0 overflow-hidden rounded-full",
className,
)}
{...props}
/>
)
);
}
function AvatarImage({
@ -26,10 +26,10 @@ function AvatarImage({
return (
<AvatarPrimitive.Image
data-slot="avatar-image"
className={cn('aspect-square size-full', className)}
className={cn("aspect-square size-full", className)}
{...props}
/>
)
);
}
function AvatarFallback({
@ -40,12 +40,12 @@ function AvatarFallback({
<AvatarPrimitive.Fallback
data-slot="avatar-fallback"
className={cn(
'bg-muted flex size-full items-center justify-center rounded-full',
"bg-muted flex size-full items-center justify-center rounded-full",
className,
)}
{...props}
/>
)
);
}
const AvatarComponent: React.FC<AvatarComponentProps> = ({
@ -59,16 +59,16 @@ const AvatarComponent: React.FC<AvatarComponentProps> = ({
<AvatarImage src={src} alt={alt} />
<AvatarFallback>{fallback}</AvatarFallback>
</Avatar>
)
}
);
};
export default AvatarComponent
export default AvatarComponent;
interface AvatarComponentProps {
src?: string
alt?: string
fallback: string
className?: string
src?: string;
alt?: string;
fallback: string;
className?: string;
}
export { Avatar, AvatarImage, AvatarFallback }
export { Avatar, AvatarImage, AvatarFallback };

View file

@ -1,38 +1,38 @@
import * as React from 'react'
import { Slot } from '@radix-ui/react-slot'
import { cva, type VariantProps } from 'class-variance-authority'
import { Slot } from "@radix-ui/react-slot";
import { type VariantProps, cva } from "class-variance-authority";
import type * as React from "react";
import { cn } from '@/lib/utils'
import { cn } from "@/lib/utils";
const badgeVariants = cva(
'inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden',
"inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
{
variants: {
variant: {
default:
'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90',
"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
secondary:
'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90',
"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
destructive:
'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/70',
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/70",
outline:
'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground',
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
},
},
defaultVariants: {
variant: 'default',
variant: "default",
},
},
)
);
function Badge({
className,
variant,
asChild = false,
...props
}: React.ComponentProps<'span'> &
}: React.ComponentProps<"span"> &
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
const Comp = asChild ? Slot : 'span'
const Comp = asChild ? Slot : "span";
return (
<Comp
@ -40,7 +40,7 @@ function Badge({
className={cn(badgeVariants({ variant }), className)}
{...props}
/>
)
);
}
export { Badge, badgeVariants }
export { Badge, badgeVariants };

View file

@ -1,100 +1,100 @@
import * as React from 'react'
import { Slot } from '@radix-ui/react-slot'
import { cn } from '@/lib/utils'
import { ChevronRightIcon, DotsHorizontalIcon } from '@radix-ui/react-icons'
import { cn } from "@/lib/utils";
import { ChevronRightIcon, DotsHorizontalIcon } from "@radix-ui/react-icons";
import { Slot } from "@radix-ui/react-slot";
import type * as React from "react";
function Breadcrumb({ ...props }: React.ComponentProps<'nav'>) {
return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />
function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />;
}
function BreadcrumbList({ className, ...props }: React.ComponentProps<'ol'>) {
function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
return (
<ol
data-slot="breadcrumb-list"
className={cn(
'text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5',
"text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5",
className,
)}
{...props}
/>
)
);
}
function BreadcrumbItem({ className, ...props }: React.ComponentProps<'li'>) {
function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
return (
<li
data-slot="breadcrumb-item"
className={cn('inline-flex items-center gap-1.5', className)}
className={cn("inline-flex items-center gap-1.5", className)}
{...props}
/>
)
);
}
function BreadcrumbLink({
asChild,
className,
...props
}: React.ComponentProps<'a'> & {
asChild?: boolean
}: React.ComponentProps<"a"> & {
asChild?: boolean;
}) {
const Comp = asChild ? Slot : 'a'
const Comp = asChild ? Slot : "a";
return (
<Comp
data-slot="breadcrumb-link"
className={cn('hover:text-foreground transition-colors', className)}
className={cn("hover:text-foreground transition-colors", className)}
{...props}
/>
)
);
}
function BreadcrumbPage({ className, ...props }: React.ComponentProps<'span'>) {
function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
data-slot="breadcrumb-page"
role="link"
aria-disabled="true"
aria-current="page"
className={cn('text-foreground font-normal', className)}
className={cn("text-foreground font-normal", className)}
{...props}
/>
)
);
}
function BreadcrumbSeparator({
children,
className,
...props
}: React.ComponentProps<'li'>) {
}: React.ComponentProps<"li">) {
return (
<li
data-slot="breadcrumb-separator"
role="presentation"
aria-hidden="true"
className={cn('[&>svg]:size-3.5', className)}
className={cn("[&>svg]:size-3.5", className)}
{...props}
>
{children ?? <ChevronRightIcon />}
</li>
)
);
}
function BreadcrumbEllipsis({
className,
...props
}: React.ComponentProps<'span'>) {
}: React.ComponentProps<"span">) {
return (
<span
data-slot="breadcrumb-ellipsis"
role="presentation"
aria-hidden="true"
className={cn('flex size-9 items-center justify-center', className)}
className={cn("flex size-9 items-center justify-center", className)}
{...props}
>
<DotsHorizontalIcon className="size-4" />
<span className="sr-only">More</span>
</span>
)
);
}
export {
@ -105,4 +105,4 @@ export {
BreadcrumbPage,
BreadcrumbSeparator,
BreadcrumbEllipsis,
}
};

View file

@ -1,84 +1,84 @@
import * as React from 'react'
import type * as React from "react";
import { cn } from '@/lib/utils'
import { cn } from "@/lib/utils";
function Card({ className, ...props }: React.ComponentProps<'div'>) {
function Card({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card"
className={cn(
'bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm',
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
className,
)}
{...props}
/>
)
);
}
function CardHeader({ className, ...props }: React.ComponentProps<'div'>) {
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-header"
className={cn(
'@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6',
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
className,
)}
{...props}
/>
)
);
}
function CardTitle({ className, ...props }: React.ComponentProps<'div'>) {
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-title"
className={cn('leading-none font-medium', className)}
className={cn("leading-none font-medium", className)}
{...props}
/>
)
);
}
function CardDescription({ className, ...props }: React.ComponentProps<'div'>) {
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-description"
className={cn('text-muted-foreground text-sm', className)}
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
)
);
}
function CardAction({ className, ...props }: React.ComponentProps<'div'>) {
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-action"
className={cn(
'col-start-2 row-span-2 row-start-1 self-start justify-self-end',
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
className,
)}
{...props}
/>
)
);
}
function CardContent({ className, ...props }: React.ComponentProps<'div'>) {
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-content"
className={cn('px-6', className)}
className={cn("px-6", className)}
{...props}
/>
)
);
}
function CardFooter({ className, ...props }: React.ComponentProps<'div'>) {
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-footer"
className={cn('flex items-center px-6 [.border-t]:pt-6', className)}
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
{...props}
/>
)
);
}
export {
@ -89,4 +89,4 @@ export {
CardAction,
CardDescription,
CardContent,
}
};

View file

@ -1,13 +1,13 @@
import * as React from 'react'
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
import { CheckIcon, ChevronRightIcon, CircleIcon } from 'lucide-react'
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
import type * as React from "react";
import { cn } from '@/lib/utils'
import { cn } from "@/lib/utils";
function DropdownMenu({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
}
function DropdownMenuPortal({
@ -15,7 +15,7 @@ function DropdownMenuPortal({
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
return (
<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
)
);
}
function DropdownMenuTrigger({
@ -26,7 +26,7 @@ function DropdownMenuTrigger({
data-slot="dropdown-menu-trigger"
{...props}
/>
)
);
}
function DropdownMenuContent({
@ -40,13 +40,13 @@ function DropdownMenuContent({
data-slot="dropdown-menu-content"
sideOffset={sideOffset}
className={cn(
'bg-popover text-popover-foreground z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md',
"bg-popover text-popover-foreground z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
className,
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
)
);
}
function DropdownMenuGroup({
@ -54,17 +54,17 @@ function DropdownMenuGroup({
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
return (
<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
)
);
}
function DropdownMenuItem({
className,
inset,
variant = 'default',
variant = "default",
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean
variant?: 'default' | 'destructive'
inset?: boolean;
variant?: "default" | "destructive";
}) {
return (
<DropdownMenuPrimitive.Item
@ -77,7 +77,7 @@ function DropdownMenuItem({
)}
{...props}
/>
)
);
}
function DropdownMenuCheckboxItem({
@ -103,7 +103,7 @@ function DropdownMenuCheckboxItem({
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
)
);
}
function DropdownMenuRadioGroup({
@ -114,7 +114,7 @@ function DropdownMenuRadioGroup({
data-slot="dropdown-menu-radio-group"
{...props}
/>
)
);
}
function DropdownMenuRadioItem({
@ -138,7 +138,7 @@ function DropdownMenuRadioItem({
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
)
);
}
function DropdownMenuLabel({
@ -146,19 +146,19 @@ function DropdownMenuLabel({
inset,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean
inset?: boolean;
}) {
return (
<DropdownMenuPrimitive.Label
data-slot="dropdown-menu-label"
data-inset={inset}
className={cn(
'px-2 py-1.5 text-sm font-medium data-[inset]:pl-8',
"px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
className,
)}
{...props}
/>
)
);
}
function DropdownMenuSeparator({
@ -168,32 +168,32 @@ function DropdownMenuSeparator({
return (
<DropdownMenuPrimitive.Separator
data-slot="dropdown-menu-separator"
className={cn('bg-border -mx-1 my-1 h-px', className)}
className={cn("bg-border -mx-1 my-1 h-px", className)}
{...props}
/>
)
);
}
function DropdownMenuShortcut({
className,
...props
}: React.ComponentProps<'span'>) {
}: React.ComponentProps<"span">) {
return (
<span
data-slot="dropdown-menu-shortcut"
className={cn(
'text-muted-foreground ml-auto text-xs tracking-widest',
"text-muted-foreground ml-auto text-xs tracking-widest",
className,
)}
{...props}
/>
)
);
}
function DropdownMenuSub({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
}
function DropdownMenuSubTrigger({
@ -202,14 +202,14 @@ function DropdownMenuSubTrigger({
children,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean
inset?: boolean;
}) {
return (
<DropdownMenuPrimitive.SubTrigger
data-slot="dropdown-menu-sub-trigger"
data-inset={inset}
className={cn(
'focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8',
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8",
className,
)}
{...props}
@ -217,7 +217,7 @@ function DropdownMenuSubTrigger({
{children}
<ChevronRightIcon className="ml-auto size-4" />
</DropdownMenuPrimitive.SubTrigger>
)
);
}
function DropdownMenuSubContent({
@ -228,12 +228,12 @@ function DropdownMenuSubContent({
<DropdownMenuPrimitive.SubContent
data-slot="dropdown-menu-sub-content"
className={cn(
'bg-popover text-popover-foreground z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg',
"bg-popover text-popover-foreground z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
className,
)}
{...props}
/>
)
);
}
export {
@ -252,4 +252,4 @@ export {
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuSubContent,
}
};

View file

@ -1,41 +1,41 @@
import { Button } from '@/components/ui/button'
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { Laptop, Moon, Sun } from 'lucide-react'
import * as React from 'react'
} from "@/components/ui/dropdown-menu";
import { Laptop, Moon, Sun } from "lucide-react";
import * as React from "react";
export function ModeToggle() {
const [theme, setThemeState] = React.useState<
'theme-light' | 'dark' | 'system'
>('theme-light')
"theme-light" | "dark" | "system"
>("theme-light");
React.useEffect(() => {
const isDarkMode = document.documentElement.classList.contains('dark')
setThemeState(isDarkMode ? 'dark' : 'theme-light')
}, [])
const isDarkMode = document.documentElement.classList.contains("dark");
setThemeState(isDarkMode ? "dark" : "theme-light");
}, []);
React.useEffect(() => {
const isDark =
theme === 'dark' ||
(theme === 'system' &&
window.matchMedia('(prefers-color-scheme: dark)').matches)
theme === "dark" ||
(theme === "system" &&
window.matchMedia("(prefers-color-scheme: dark)").matches);
document.documentElement.classList.add('disable-transitions')
document.documentElement.classList.add("disable-transitions");
document.documentElement.classList[isDark ? 'add' : 'remove']('dark')
document.documentElement.classList[isDark ? "add" : "remove"]("dark");
window
.getComputedStyle(document.documentElement)
.getPropertyValue('opacity')
.getPropertyValue("opacity");
requestAnimationFrame(() => {
document.documentElement.classList.remove('disable-transitions')
})
}, [theme])
document.documentElement.classList.remove("disable-transitions");
});
}, [theme]);
return (
<DropdownMenu modal={false}>
@ -52,19 +52,19 @@ export function ModeToggle() {
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="bg-background">
<DropdownMenuItem onClick={() => setThemeState('theme-light')}>
<DropdownMenuItem onClick={() => setThemeState("theme-light")}>
<Sun className="mr-2 size-4" />
<span>Light</span>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setThemeState('dark')}>
<DropdownMenuItem onClick={() => setThemeState("dark")}>
<Moon className="mr-2 size-4" />
<span>Dark</span>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setThemeState('system')}>
<DropdownMenuItem onClick={() => setThemeState("system")}>
<Laptop className="mr-2 size-4" />
<span>System</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
);
}

View file

@ -1,72 +1,71 @@
import * as React from 'react'
import {
ChevronLeftIcon,
ChevronRightIcon,
MoreHorizontalIcon,
} from 'lucide-react'
} from "lucide-react";
import type * as React from "react";
import { cn } from '@/lib/utils'
import { Button, buttonVariants } from '@/components/ui/button'
import { type Button, buttonVariants } from "@/components/ui/button";
import { cn } from "@/lib/utils";
function Pagination({ className, ...props }: React.ComponentProps<'nav'>) {
function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
return (
<nav
role="navigation"
aria-label="pagination"
data-slot="pagination"
className={cn('mx-auto flex w-full justify-center', className)}
className={cn("mx-auto flex w-full justify-center", className)}
{...props}
/>
)
);
}
function PaginationContent({
className,
...props
}: React.ComponentProps<'ul'>) {
}: React.ComponentProps<"ul">) {
return (
<ul
data-slot="pagination-content"
className={cn('flex flex-row items-center gap-1', className)}
className={cn("flex flex-row items-center gap-1", className)}
{...props}
/>
)
);
}
function PaginationItem({ ...props }: React.ComponentProps<'li'>) {
return <li data-slot="pagination-item" {...props} />
function PaginationItem({ ...props }: React.ComponentProps<"li">) {
return <li data-slot="pagination-item" {...props} />;
}
type PaginationLinkProps = {
isActive?: boolean
isDisabled?: boolean
} & Pick<React.ComponentProps<typeof Button>, 'size'> &
React.ComponentProps<'a'>
isActive?: boolean;
isDisabled?: boolean;
} & Pick<React.ComponentProps<typeof Button>, "size"> &
React.ComponentProps<"a">;
function PaginationLink({
className,
isActive,
isDisabled,
size = 'icon',
size = "icon",
...props
}: PaginationLinkProps) {
return (
<a
aria-current={isActive ? 'page' : undefined}
aria-current={isActive ? "page" : undefined}
data-slot="pagination-link"
data-active={isActive}
data-disabled={isDisabled}
className={cn(
buttonVariants({
variant: isActive ? 'outline' : 'ghost',
variant: isActive ? "outline" : "ghost",
size,
}),
isDisabled && 'pointer-events-none opacity-50',
isDisabled && "pointer-events-none opacity-50",
className,
)}
{...props}
/>
)
);
}
function PaginationPrevious({
@ -78,14 +77,14 @@ function PaginationPrevious({
<PaginationLink
aria-label="Go to previous page"
size="default"
className={cn('gap-1 px-2.5 sm:pl-2.5', className)}
className={cn("gap-1 px-2.5 sm:pl-2.5", className)}
isDisabled={isDisabled}
{...props}
>
<ChevronLeftIcon />
<span className="hidden sm:block">Previous</span>
</PaginationLink>
)
);
}
function PaginationNext({
@ -97,31 +96,31 @@ function PaginationNext({
<PaginationLink
aria-label="Go to next page"
size="default"
className={cn('gap-1 px-2.5 sm:pr-2.5', className)}
className={cn("gap-1 px-2.5 sm:pr-2.5", className)}
isDisabled={isDisabled}
{...props}
>
<span className="hidden sm:block">Next</span>
<ChevronRightIcon />
</PaginationLink>
)
);
}
function PaginationEllipsis({
className,
...props
}: React.ComponentProps<'span'>) {
}: React.ComponentProps<"span">) {
return (
<span
aria-hidden
data-slot="pagination-ellipsis"
className={cn('flex size-9 items-center justify-center', className)}
className={cn("flex size-9 items-center justify-center", className)}
{...props}
>
<MoreHorizontalIcon className="size-4" />
<span className="sr-only">More pages</span>
</span>
)
);
}
const PaginationComponent: React.FC<PaginationProps> = ({
@ -129,12 +128,12 @@ const PaginationComponent: React.FC<PaginationProps> = ({
totalPages,
baseUrl,
}) => {
const pages = Array.from({ length: totalPages }, (_, i) => i + 1)
const pages = Array.from({ length: totalPages }, (_, i) => i + 1);
const getPageUrl = (page: number) => {
if (page === 1) return baseUrl
return `${baseUrl}${page}`
}
if (page === 1) return baseUrl;
return `${baseUrl}${page}`;
};
return (
<Pagination>
@ -173,16 +172,16 @@ const PaginationComponent: React.FC<PaginationProps> = ({
</PaginationItem>
</PaginationContent>
</Pagination>
)
}
);
};
interface PaginationProps {
currentPage: number
totalPages: number
baseUrl: string
currentPage: number;
totalPages: number;
baseUrl: string;
}
export default PaginationComponent
export default PaginationComponent;
export {
Pagination,
@ -192,4 +191,4 @@ export {
PaginationPrevious,
PaginationNext,
PaginationEllipsis,
}
};

View file

@ -1,7 +1,7 @@
import * as React from 'react'
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area'
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
import type * as React from "react";
import { cn } from '@/lib/utils'
import { cn } from "@/lib/utils";
function ScrollArea({
className,
@ -11,7 +11,7 @@ function ScrollArea({
return (
<ScrollAreaPrimitive.Root
data-slot="scroll-area"
className={cn('relative', className)}
className={cn("relative", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport
@ -23,12 +23,12 @@ function ScrollArea({
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
)
);
}
function ScrollBar({
className,
orientation = 'vertical',
orientation = "vertical",
...props
}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
return (
@ -36,11 +36,11 @@ function ScrollBar({
data-slot="scroll-area-scrollbar"
orientation={orientation}
className={cn(
'flex touch-none p-px transition-colors select-none',
orientation === 'vertical' &&
'h-full w-2.5 border-l border-l-transparent',
orientation === 'horizontal' &&
'h-2.5 flex-col border-t border-t-transparent',
"flex touch-none p-px transition-colors select-none",
orientation === "vertical" &&
"h-full w-2.5 border-l border-l-transparent",
orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent",
className,
)}
{...props}
@ -50,7 +50,7 @@ function ScrollBar({
className="bg-border relative flex-1 rounded-full"
/>
</ScrollAreaPrimitive.ScrollAreaScrollbar>
)
);
}
export { ScrollArea, ScrollBar }
export { ScrollArea, ScrollBar };

View file

@ -1,11 +1,11 @@
import * as React from 'react'
import * as SeparatorPrimitive from '@radix-ui/react-separator'
import * as SeparatorPrimitive from "@radix-ui/react-separator";
import type * as React from "react";
import { cn } from '@/lib/utils'
import { cn } from "@/lib/utils";
function Separator({
className,
orientation = 'horizontal',
orientation = "horizontal",
decorative = true,
...props
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
@ -15,12 +15,12 @@ function Separator({
decorative={decorative}
orientation={orientation}
className={cn(
'bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px',
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
className,
)}
{...props}
/>
)
);
}
export { Separator }
export { Separator };

View file

@ -1,8 +1,8 @@
import { glob } from 'astro/loaders'
import { defineCollection, z } from 'astro:content'
import { defineCollection, z } from "astro:content";
import { glob } from "astro/loaders";
const blog = defineCollection({
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content' }),
loader: glob({ pattern: "**/*.{md,mdx}", base: "./src/content" }),
schema: ({ image }) =>
z.object({
title: z.string(),
@ -13,6 +13,6 @@ const blog = defineCollection({
authors: z.array(z.string()).optional(),
draft: z.boolean().optional(),
}),
})
});
export const collections = { blog }
export const collections = { blog };

View file

@ -1,15 +1,15 @@
---
import Footer from '@/components/Footer.astro'
import Head from '@/components/Head.astro'
import Header from '@/components/Header.astro'
import { SITE } from '@/consts'
import Footer from "@/components/Footer.astro";
import Head from "@/components/Head.astro";
import Header from "@/components/Header.astro";
import { SITE } from "@/consts";
type Props = {
title: string
description: string
}
title: string;
description: string;
};
const { title, description } = Astro.props
const { title, description } = Astro.props;
---
<!doctype html>

View file

@ -1,27 +1,27 @@
import { getEntry } from 'astro:content'
import { getEntry } from "astro:content";
export async function parseAuthors(authors: string[]) {
if (!authors || authors.length === 0) return []
if (!authors || authors.length === 0) return [];
const parseAuthor = async (id: string) => {
try {
const author = await getEntry('authors', id)
const author = await getEntry("authors", id);
return {
id,
name: author?.data?.name || id,
avatar: author?.data?.avatar || '/static/logo.png',
avatar: author?.data?.avatar || "/static/logo.png",
isRegistered: !!author,
}
};
} catch (error) {
console.error(`Error fetching author with id ${id}:`, error)
console.error(`Error fetching author with id ${id}:`, error);
return {
id,
name: id,
avatar: '/static/logo.png',
avatar: "/static/logo.png",
isRegistered: false,
};
}
}
}
};
return await Promise.all(authors.map(parseAuthor))
return await Promise.all(authors.map(parseAuthor));
}

View file

@ -1,21 +1,21 @@
import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
return twMerge(clsx(inputs));
}
export function formatDate(date: Date) {
return Intl.DateTimeFormat('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
}).format(date)
return Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "long",
day: "numeric",
}).format(date);
}
export function readingTime(html: string) {
const textOnly = html.replace(/<[^>]+>/g, '')
const wordCount = textOnly.split(/\s+/).length
const readingTimeMinutes = (wordCount / 200 + 1).toFixed()
return `${readingTimeMinutes} min read`
const textOnly = html.replace(/<[^>]+>/g, "");
const wordCount = textOnly.split(/\s+/).length;
const readingTimeMinutes = (wordCount / 200 + 1).toFixed();
return `${readingTimeMinutes} min read`;
}

View file

@ -1,11 +1,11 @@
---
import Breadcrumbs from '@/components/Breadcrumbs.astro'
import Container from '@/components/Container.astro'
import Link from '@/components/Link.astro'
import { buttonVariants } from '@/components/ui/button'
import { SITE } from '@/consts'
import Layout from '@/layouts/Layout.astro'
import { cn } from '@/lib/utils'
import Breadcrumbs from "@/components/Breadcrumbs.astro";
import Container from "@/components/Container.astro";
import Link from "@/components/Link.astro";
import { buttonVariants } from "@/components/ui/button";
import { SITE } from "@/consts";
import Layout from "@/layouts/Layout.astro";
import { cn } from "@/lib/utils";
---
<Layout title="404" description={SITE.description}>

View file

@ -1,37 +1,39 @@
---
import BlogCard from '@/components/BlogCard.astro'
import Breadcrumbs from '@/components/Breadcrumbs.astro'
import Container from '@/components/Container.astro'
import PaginationComponent from '@/components/ui/pagination'
import { SITE } from '@/consts'
import Layout from '@/layouts/Layout.astro'
import type { PaginateFunction } from 'astro'
import { type CollectionEntry, getCollection } from 'astro:content'
import { type CollectionEntry, getCollection } from "astro:content";
import BlogCard from "@/components/BlogCard.astro";
import Breadcrumbs from "@/components/Breadcrumbs.astro";
import Container from "@/components/Container.astro";
import PaginationComponent from "@/components/ui/pagination";
import { SITE } from "@/consts";
import Layout from "@/layouts/Layout.astro";
import type { PaginateFunction } from "astro";
export async function getStaticPaths({
paginate,
}: {
paginate: PaginateFunction
paginate: PaginateFunction;
}) {
const allPosts = await getCollection('blog', ({ data }) => !data.draft)
const allPosts = await getCollection("blog", ({ data }) => !data.draft);
return paginate(
allPosts.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf()),
{ pageSize: SITE.postsPerPage },
)
);
}
const { page } = Astro.props
const { page } = Astro.props;
const postsByYear = page.data.reduce(
(acc: Record<string, CollectionEntry<'blog'>[]>, post) => {
const year = post.data.date.getFullYear().toString()
;(acc[year] ??= []).push(post)
return acc
(acc: Record<string, CollectionEntry<"blog">[]>, post) => {
const year = post.data.date.getFullYear().toString();
(acc[year] ??= []).push(post);
return acc;
},
{},
)
);
const years = Object.keys(postsByYear).sort((a, b) => parseInt(b) - parseInt(a))
const years = Object.keys(postsByYear).sort(
(a, b) => Number.parseInt(b) - Number.parseInt(a),
);
---
<Layout title="Blog" description="Blog">

View file

@ -1,16 +1,16 @@
---
import BlogCard from '@/components/BlogCard.astro'
import Container from '@/components/Container.astro'
import Link from '@/components/Link.astro'
import { buttonVariants } from '@/components/ui/button'
import { SITE } from '@/consts'
import Layout from '@/layouts/Layout.astro'
import { getCollection } from 'astro:content'
import { getCollection } from "astro:content";
import BlogCard from "@/components/BlogCard.astro";
import Container from "@/components/Container.astro";
import Link from "@/components/Link.astro";
import { buttonVariants } from "@/components/ui/button";
import { SITE } from "@/consts";
import Layout from "@/layouts/Layout.astro";
const blog = (await getCollection('blog'))
const blog = (await getCollection("blog"))
.filter((post) => !post.data.draft)
.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf())
.slice(0, SITE.featuredPostCount)
.slice(0, SITE.featuredPostCount);
---
<Layout title="Home" description={SITE.description}>

View file

@ -1,4 +1,4 @@
import type { APIRoute } from 'astro'
import type { APIRoute } from "astro";
const getRobotsTxt = (sitemapURL: URL) => `
User-agent: AI2Bot
@ -49,9 +49,9 @@ User-agent: YouBot
Disallow: /
Sitemap: ${sitemapURL.href}
`
`;
export const GET: APIRoute = ({ site }) => {
const sitemapURL = new URL('sitemap-index.xml', site)
return new Response(getRobotsTxt(sitemapURL))
}
const sitemapURL = new URL("sitemap-index.xml", site);
return new Response(getRobotsTxt(sitemapURL));
};

View file

@ -1,12 +1,12 @@
import { SITE } from '@/consts'
import rss from '@astrojs/rss'
import type { APIContext } from 'astro'
import { getCollection } from 'astro:content'
import { getCollection } from "astro:content";
import { SITE } from "@/consts";
import rss from "@astrojs/rss";
import type { APIContext } from "astro";
export async function GET(context: APIContext) {
try {
const posts = await getCollection('blog', ({ data }) => !data.draft)
posts.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf())
const posts = await getCollection("blog", ({ data }) => !data.draft);
posts.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
return rss({
title: SITE.title,
@ -18,9 +18,9 @@ export async function GET(context: APIContext) {
pubDate: post.data.date,
link: `/blog/${post.id}/`,
})),
})
});
} catch (error) {
console.error('Error generating RSS feed:', error)
return new Response('Error generating RSS feed', { status: 500 })
console.error("Error generating RSS feed:", error);
return new Response("Error generating RSS feed", { status: 500 });
}
}

View file

@ -1,4 +1,4 @@
@import 'tailwindcss';
@import "tailwindcss";
[data-callout] {
& {
@ -124,11 +124,13 @@ details[data-callout][open] > summary[data-callout-title]::after {
}
}
[data-callout][data-callout-type="abstract"] > [data-callout-title]:empty::after {
[data-callout][data-callout-type="abstract"]
> [data-callout-title]:empty::after {
content: "Abstract";
}
[data-callout][data-callout-type="summary"] > [data-callout-title]:empty::after {
[data-callout][data-callout-type="summary"]
> [data-callout-title]:empty::after {
content: "Summary";
}
@ -163,7 +165,8 @@ details[data-callout][open] > summary[data-callout-title]::after {
content: "Hint";
}
[data-callout][data-callout-type="important"] > [data-callout-title]:empty::after {
[data-callout][data-callout-type="important"]
> [data-callout-title]:empty::after {
content: "Important";
}
@ -186,7 +189,8 @@ details[data-callout][open] > summary[data-callout-title]::after {
}
}
[data-callout][data-callout-type="success"] > [data-callout-title]:empty::after {
[data-callout][data-callout-type="success"]
> [data-callout-title]:empty::after {
content: "Success";
}
@ -217,7 +221,8 @@ details[data-callout][open] > summary[data-callout-title]::after {
}
}
[data-callout][data-callout-type="question"] > [data-callout-title]:empty::after {
[data-callout][data-callout-type="question"]
> [data-callout-title]:empty::after {
content: "Question";
}
@ -248,15 +253,18 @@ details[data-callout][open] > summary[data-callout-title]::after {
}
}
[data-callout][data-callout-type="warning"] > [data-callout-title]:empty::after {
[data-callout][data-callout-type="warning"]
> [data-callout-title]:empty::after {
content: "Warning";
}
[data-callout][data-callout-type="caution"] > [data-callout-title]:empty::after {
[data-callout][data-callout-type="caution"]
> [data-callout-title]:empty::after {
content: "Caution";
}
[data-callout][data-callout-type="attention"] > [data-callout-title]:empty::after {
[data-callout][data-callout-type="attention"]
> [data-callout-title]:empty::after {
content: "Attention";
}
@ -279,7 +287,8 @@ details[data-callout][open] > summary[data-callout-title]::after {
}
}
[data-callout][data-callout-type="failure"] > [data-callout-title]:empty::after {
[data-callout][data-callout-type="failure"]
> [data-callout-title]:empty::after {
content: "Failure";
}
@ -287,7 +296,8 @@ details[data-callout][open] > summary[data-callout-title]::after {
content: "Fail";
}
[data-callout][data-callout-type="missing"] > [data-callout-title]:empty::after {
[data-callout][data-callout-type="missing"]
> [data-callout-title]:empty::after {
content: "Missing";
}
@ -355,7 +365,8 @@ details[data-callout][open] > summary[data-callout-title]::after {
}
}
[data-callout][data-callout-type="example"] > [data-callout-title]:empty::after {
[data-callout][data-callout-type="example"]
> [data-callout-title]:empty::after {
content: "Example";
}

View file

@ -1,10 +1,12 @@
@import 'tailwindcss';
@import "tailwindcss";
@custom-variant dark (&:is(.dark *));
@theme inline {
--font-sans: 'Geist Variable', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
--font-mono: "Geist Mono Variable", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
--font-sans: "Geist Variable", ui-sans-serif, system-ui, sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--font-mono: "Geist Mono Variable", ui-monospace, SFMono-Regular, Menlo,
Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--color-background: var(--background);
--color-foreground: var(--foreground);

View file

@ -1,16 +1,16 @@
export type Site = {
title: string
description: string
href: string
featuredPostCount: number
postsPerPage: number
}
title: string;
description: string;
href: string;
featuredPostCount: number;
postsPerPage: number;
};
export type SocialLink = {
href: string
label: string
}
href: string;
label: string;
};
export type IconMap = {
[key: string]: string
}
[key: string]: string;
};