chore: update

This commit is contained in:
enscribe 2024-09-10 17:51:46 -07:00
parent 8fe228e243
commit 43e35a3f8b
No known key found for this signature in database
GPG key ID: 9BBD5C4114E25322
54 changed files with 1013 additions and 496 deletions

View file

@ -1 +1 @@
# erudite
# erudite

26
package-lock.json generated
View file

@ -17,6 +17,7 @@
"@astrojs/tailwind": "^5.1.0",
"@fontsource/geist-mono": "^5.0.3",
"@fontsource/geist-sans": "^5.0.3",
"@radix-ui/react-avatar": "^1.1.0",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-slot": "^1.1.0",
@ -1823,6 +1824,31 @@
}
}
},
"node_modules/@radix-ui/react-avatar": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.0.tgz",
"integrity": "sha512-Q/PbuSMk/vyAd/UoIShVGZ7StHHeRFYU7wXmi5GV+8cLXflZAEpHL/F697H1klrzxKXNtZ97vWiC0q3RKUH8UA==",
"dependencies": {
"@radix-ui/react-context": "1.1.0",
"@radix-ui/react-primitive": "2.0.0",
"@radix-ui/react-use-callback-ref": "1.1.0",
"@radix-ui/react-use-layout-effect": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-collection": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz",

View file

@ -21,6 +21,7 @@
"@astrojs/tailwind": "^5.1.0",
"@fontsource/geist-mono": "^5.0.3",
"@fontsource/geist-sans": "^5.0.3",
"@radix-ui/react-avatar": "^1.1.0",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-slot": "^1.1.0",

BIN
public/1200x630.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 241 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

View file

@ -1,17 +0,0 @@
<svg width="179" height="32" viewBox="0 0 179 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_8_30)">
<path d="M173 0H6C2.68629 0 0 2.68629 0 6V26C0 29.3137 2.68629 32 6 32H173C176.314 32 179 29.3137 179 26V6C179 2.68629 176.314 0 173 0Z" fill="#2E51ED"/>
<path d="M15.027 23.227H14.781L13.556 22.049V21.813L15.429 20.011H16.727L16.9 20.178V21.426L15.027 23.227ZM13.556 9.89999V9.66399L14.781 8.48499H15.027L16.9 10.287V11.535L16.727 11.701H15.429L13.556 9.89999ZM24.343 19.429H22.561L22.411 19.286V15.273C22.411 14.559 22.12 14.005 21.224 13.986C20.764 13.975 20.236 13.986 19.673 14.007L19.588 14.091V19.284L19.439 19.427H17.657L17.507 19.284V12.429L17.657 12.285H21.669C23.229 12.285 24.492 13.5 24.492 15V19.286L24.343 19.429ZM15.28 16.86H8.15L8 16.716V14.998L8.149 14.855H15.28L15.43 14.998V16.716L15.28 16.859V16.86ZM33.853 16.86H26.722L26.572 16.716V14.998L26.722 14.855H33.853L34.002 14.998V16.716L33.853 16.859V16.86ZM19.973 10.143V4.99999L20.122 4.85699H21.909L22.057 4.99999V10.143L21.909 10.287H20.122L19.973 10.143ZM19.973 26.714V21.571L20.122 21.428H21.909L22.057 21.571V26.714L21.909 26.857H20.122L19.973 26.714ZM155.15 10.64C154.72 11.06 154.51 11.64 154.51 12.39V13.43H153.28V15.1H154.51V21.19H156.47V15.1H158.11V13.43H156.47V12.38C156.47 11.85 156.75 11.58 157.3 11.58H158.34V9.99999H156.94C156.18 9.99999 155.59 10.21 155.16 10.64H155.15ZM150.93 10.13C150.57 10.13 150.27 10.25 150.05 10.48C149.84 10.7 149.73 10.98 149.73 11.32C149.73 11.66 149.84 11.95 150.05 12.19C150.27 12.42 150.57 12.54 150.93 12.54C151.29 12.54 151.57 12.42 151.78 12.19C152 11.96 152.12 11.67 152.12 11.32C152.12 10.97 152.01 10.7 151.78 10.48C151.56 10.25 151.28 10.13 150.93 10.13ZM73.23 10.14H75.19V21.19H73.23V10.14Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M47.32 10.63C48.49 10.63 49.49 10.84 50.33 11.27C51.17 11.69 51.81 12.3 52.25 13.09C52.7 13.88 52.92 14.83 52.92 15.92C52.92 17.01 52.7 17.96 52.25 18.75C51.81 19.54 51.17 20.14 50.33 20.56C49.49 20.98 48.49 21.19 47.32 21.19H44V10.63H47.32ZM47.23 19.22C48.31 19.22 49.16 18.93 49.78 18.35V18.36C50.41 17.77 50.72 16.97 50.72 15.92C50.72 14.87 50.41 14.05 49.78 13.47C49.16 12.89 48.31 12.6 47.23 12.6H46.11V19.22H47.23ZM58.16 13.29C58.96 13.29 59.65 13.45 60.25 13.79L60.24 13.78C60.85 14.11 61.31 14.57 61.64 15.17C61.97 15.77 62.13 16.47 62.13 17.26V17.92H56.28C56.31 18.18 56.36 18.44 56.47 18.66C56.63 18.96 56.85 19.19 57.14 19.36C57.43 19.53 57.78 19.61 58.19 19.61C58.6 19.61 58.95 19.54 59.23 19.41C59.51 19.28 59.72 19.09 59.86 18.86H61.96C61.8 19.33 61.54 19.76 61.19 20.13C60.85 20.51 60.41 20.79 59.89 21C59.38 21.21 58.8 21.31 58.17 21.31C57.38 21.31 56.68 21.15 56.07 20.82C55.47 20.49 55.01 20.02 54.67 19.41C54.34 18.81 54.18 18.1 54.18 17.3C54.18 16.5 54.34 15.8 54.67 15.2C55 14.6 55.46 14.13 56.06 13.79C56.67 13.46 57.36 13.29 58.16 13.29ZM58.16 14.99C57.8 14.99 57.47 15.08 57.18 15.26C56.89 15.42 56.66 15.66 56.49 15.97C56.41 16.14 56.35 16.31 56.31 16.49H60.05C60.0095 16.2241 59.9069 15.9714 59.7505 15.7525C59.5941 15.5336 59.3884 15.3546 59.15 15.23C58.86 15.07 58.52 14.99 58.16 14.99ZM70.16 13.74C69.64 13.42 69.04 13.26 68.35 13.26C67.66 13.26 67.06 13.42 66.57 13.74C66.28 13.93 66.05 14.19 65.86 14.47V13.43H63.9V23.85H65.86V20.16C66.06 20.45 66.29 20.7 66.57 20.9C67.06 21.23 67.65 21.39 68.35 21.39C69.05 21.39 69.63 21.23 70.16 20.91C70.68 20.58 71.09 20.12 71.38 19.51C71.67 18.89 71.81 18.17 71.81 17.33C71.81 16.49 71.67 15.75 71.38 15.15C71.09 14.53 70.68 14.07 70.16 13.75V13.74ZM69.5 18.6C69.33 18.95 69.1 19.22 68.8 19.41C68.51 19.6 68.18 19.69 67.81 19.69C67.22 19.69 66.75 19.48 66.4 19.07C66.05 18.65 65.87 18.08 65.87 17.35C65.87 16.62 66.05 16.07 66.4 15.66C66.75 15.25 67.23 15.04 67.81 15.04C68.18 15.04 68.51 15.14 68.8 15.33C69.1 15.52 69.33 15.79 69.5 16.14C69.67 16.49 69.75 16.89 69.75 17.34C69.75 17.79 69.67 18.23 69.5 18.59V18.6ZM82.85 13.79C82.23 13.44 81.51 13.26 80.68 13.26C79.85 13.26 79.13 13.44 78.51 13.79C77.9 14.14 77.44 14.62 77.11 15.23C76.78 15.85 76.62 16.54 76.62 17.32C76.62 18.1 76.78 18.79 77.11 19.41C77.44 20.02 77.9 20.5 78.51 20.85C79.13 21.2 79.85 21.38 80.68 21.38C81.51 21.38 82.23 21.2 82.85 20.85C83.47 20.5 83.93 20.01 84.25 19.39C84.58 18.77 84.74 18.08 84.74 17.32C84.74 16.56 84.58 15.85 84.25 15.23C83.93 14.61 83.47 14.13 82.85 13.79ZM82.43 18.49C82.27 18.83 82.04 19.09 81.73 19.27C81.43 19.46 81.08 19.55 80.68 19.55C80.28 19.55 79.91 19.46 79.62 19.27C79.32 19.08 79.09 18.82 78.92 18.49C78.76 18.15 78.68 17.76 78.68 17.31C78.68 16.86 78.76 16.46 78.92 16.12C79.09 15.78 79.32 15.53 79.62 15.35C79.92 15.16 80.27 15.07 80.68 15.07C81.09 15.07 81.43 15.16 81.73 15.35C82.04 15.53 82.27 15.79 82.43 16.13C82.6 16.47 82.68 16.86 82.68 17.31C82.68 17.76 82.6 18.15 82.43 18.49Z" fill="white"/>
<path d="M87.11 13.43L89.15 18.5L91.26 13.43H93.15L88.84 23.75H86.95L88.14 20.9L85.13 13.43H87.11ZM102.71 10.98H100.75V13.43H99.26V15.1H100.75V21.19H102.71V15.1H104.39V13.43H102.71V10.98Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M109.18 13.26C110.01 13.26 110.73 13.44 111.35 13.79C111.97 14.13 112.43 14.61 112.75 15.23C113.08 15.85 113.24 16.56 113.24 17.32C113.24 18.08 113.08 18.77 112.75 19.39C112.43 20.01 111.97 20.5 111.35 20.85C110.73 21.2 110.01 21.38 109.18 21.38C108.35 21.38 107.63 21.2 107.01 20.85C106.4 20.5 105.94 20.02 105.61 19.41C105.28 18.79 105.12 18.1 105.12 17.32C105.12 16.54 105.28 15.85 105.61 15.23C105.94 14.62 106.4 14.14 107.01 13.79C107.63 13.44 108.35 13.26 109.18 13.26ZM110.23 19.27C110.54 19.09 110.77 18.83 110.93 18.49C111.1 18.15 111.18 17.76 111.18 17.31C111.18 16.86 111.1 16.47 110.93 16.13C110.77 15.79 110.54 15.53 110.23 15.35C109.93 15.16 109.59 15.07 109.18 15.07C108.77 15.07 108.42 15.16 108.12 15.35C107.82 15.53 107.59 15.78 107.42 16.12C107.26 16.46 107.18 16.86 107.18 17.31C107.18 17.76 107.26 18.15 107.42 18.49C107.59 18.82 107.82 19.08 108.12 19.27C108.41 19.46 108.78 19.55 109.18 19.55C109.58 19.55 109.93 19.46 110.23 19.27Z" fill="white"/>
<path d="M126.91 16.02L122.22 10.63H120.41V21.19H122.52V14.26L126.91 19.31V21.19H129.02V10.63H126.91V16.02Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M134.5 13.29C135.3 13.29 135.99 13.45 136.59 13.79L136.58 13.78C137.19 14.11 137.65 14.57 137.98 15.17C138.31 15.77 138.47 16.47 138.47 17.26V17.92H132.62C132.65 18.18 132.7 18.44 132.81 18.66C132.97 18.96 133.19 19.19 133.48 19.36C133.77 19.53 134.12 19.61 134.53 19.61C134.94 19.61 135.29 19.54 135.57 19.41C135.85 19.28 136.06 19.09 136.2 18.86H138.3C138.14 19.33 137.88 19.76 137.53 20.13C137.19 20.51 136.75 20.79 136.23 21C135.72 21.21 135.14 21.31 134.51 21.31C133.72 21.31 133.02 21.15 132.41 20.82C131.81 20.49 131.35 20.02 131.01 19.41C130.68 18.81 130.52 18.1 130.52 17.3C130.52 16.5 130.68 15.8 131.01 15.2C131.34 14.6 131.8 14.13 132.4 13.79C133.01 13.46 133.7 13.29 134.5 13.29ZM134.5 14.99C134.14 14.99 133.81 15.08 133.52 15.26C133.23 15.42 133 15.66 132.83 15.97C132.75 16.14 132.69 16.31 132.65 16.49H136.39C136.349 16.224 136.247 15.9714 136.09 15.7525C135.934 15.5336 135.728 15.3546 135.49 15.23C135.2 15.07 134.86 14.99 134.5 14.99Z" fill="white"/>
<path d="M142.58 10.98H140.62V13.43H139.14V15.1H140.62V21.19H142.58V15.1H144.26V13.43H142.58V10.98ZM145.92 10.14H147.88V21.19H145.92V10.14ZM149.95 21.18V13.26C150.21 13.45 150.53 13.55 150.93 13.55C151.33 13.55 151.66 13.46 151.91 13.26V21.18H149.95ZM163.14 18.5L165.26 13.43H167.15L162.84 23.75H160.95L162.14 20.9L159.13 13.43H161.1L163.14 18.5Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_8_30">
<rect width="179" height="32" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 7.5 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 354 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 218 KiB

View file

@ -1,42 +0,0 @@
---
import type { CollectionEntry } from 'astro:content'
type Props = {
entry: CollectionEntry<'blog'>
}
const { entry } = Astro.props as {
entry: CollectionEntry<'blog'>
}
---
<a
href={`/${entry.collection}/${entry.slug}`}
class="not-prose group relative flex flex-nowrap rounded-lg border px-4 py-3 pr-10 transition-colors duration-300 ease-in-out"
>
<div class="flex flex-1 flex-col truncate">
<div class="font-semibold">
{entry.data.title}
</div>
<div class="text-sm">
{entry.data.description}
</div>
</div>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
class="absolute right-2 top-1/2 size-5 -translate-y-1/2 fill-none stroke-current stroke-2"
>
<line
x1="5"
y1="12"
x2="19"
y2="12"
class="translate-x-3 scale-x-0 transition-transform duration-300 ease-in-out group-focus-visible:translate-x-0 group-focus-visible:scale-x-100"
></line>
<polyline
points="12 5 19 12 12 19"
class="-translate-x-1 transition-transform duration-300 ease-in-out group-focus-visible:translate-x-0"
></polyline>
</svg>
</a>

View file

@ -0,0 +1,29 @@
---
import type { CollectionEntry } from 'astro:content'
import { Image } from 'astro:assets'
type Props = {
author: CollectionEntry<'authors'>
}
const { author } = Astro.props
const { name, avatar, bio } = author.data
---
<div
class="rounded-lg border p-4 transition-colors duration-300 ease-in-out hover:bg-secondary/50"
>
<a href={`/authors/${author.slug}`} class="flex flex-wrap gap-4">
<Image
src={avatar}
alt={`Avatar of ${name}`}
width={128}
height={128}
class="rounded-lg object-cover"
/>
<div class="flex-grow">
<h3 class="mb-1 text-lg font-semibold">{name}</h3>
<p class="text-sm text-muted-foreground">{bio}</p>
</div>
</a>
</div>

View file

@ -1,33 +0,0 @@
---
type Props = {
href: string
}
const { href } = Astro.props
---
<a
href={href}
class="not-prose group relative flex w-fit flex-nowrap rounded border py-1.5 pl-7 pr-3 transition-colors duration-300 ease-in-out"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
class="absolute left-2 top-1/2 size-4 -translate-y-1/2 fill-none stroke-current stroke-2"
>
<line
x1="5"
y1="12"
x2="19"
y2="12"
class="translate-x-2 scale-x-0 transition-transform duration-300 ease-in-out"
></line>
<polyline
points="12 5 5 12 12 19"
class="translate-x-1 transition-transform duration-300 ease-in-out"
></polyline>
</svg>
<div class="text-sm">
<slot />
</div>
</a>

View file

@ -0,0 +1,61 @@
---
import type { CollectionEntry } from 'astro:content'
import { formatDate, readingTime } from '@lib/utils'
import { Image } from 'astro:assets'
import { Badge } from '@/components/ui/badge'
type Props = {
entry: CollectionEntry<'blog'>
}
const { entry } = Astro.props as {
entry: CollectionEntry<'blog'>
}
const formattedDate = formatDate(entry.data.date)
const readTime = readingTime(entry.body)
---
<div
class="not-prose rounded-lg border p-4 transition-colors duration-300 ease-in-out hover:bg-secondary/50"
>
<a href={`/${entry.collection}/${entry.slug}`} class="flex flex-wrap gap-4">
{
entry.data.image && (
<div class="flex-shrink-0">
<Image
src={entry.data.image}
alt={entry.data.title}
width={200}
height={200}
class="rounded-lg object-cover"
/>
</div>
)
}
<div class="flex-grow">
<h3 class="mb-1 text-lg font-semibold">
{entry.data.title}
</h3>
<p class="mb-2 text-sm text-muted-foreground">
{entry.data.description}
</p>
<div
class="mb-2 flex items-center space-x-2 text-xs text-muted-foreground"
>
<span>{formattedDate}</span>
<span>&bull;</span>
<span>{readTime}</span>
</div>
{
entry.data.tags && (
<div class="flex flex-wrap gap-2">
{entry.data.tags.map((tag) => (
<Badge variant="secondary">{tag}</Badge>
))}
</div>
)
}
</div>
</a>
</div>

View file

@ -6,10 +6,16 @@ import { ModeToggle } from '@components/ui/mode-toggle'
<footer class="py-4">
<Container>
<div class="flex justify-between items-center">
<div class="flex items-center space-x-4">
<ModeToggle client:load />
<p class="text-sm text-muted-foreground">&copy; {new Date().getFullYear()} All rights reserved.</p>
<div
class="flex flex-col items-center justify-center gap-y-2 sm:flex-row sm:justify-between"
>
<div class="flex items-center">
<div class="mr-2 hidden sm:block">
<ModeToggle client:load />
</div>
<p class="text-sm text-muted-foreground">
&copy; {new Date().getFullYear()} All rights reserved.
</p>
</div>
<SocialIcons />
</div>

View file

@ -7,27 +7,34 @@ const items = [
{ href: '/blog', label: 'blog' },
{ href: '/authors', label: 'authors' },
{ href: '/about', label: 'about' },
{ href: '/tags', label: 'tags' },
]
---
<header class="sticky top-0 z-10 bg-background/50 backdrop-blur-md" transition:persist>
<header
class="sticky top-0 z-10 bg-background/50 backdrop-blur-md"
transition:persist
>
<Container>
<div class="flex items-center justify-between py-4">
<Link href="/" class="text-xl font-semibold hover:text-primary transition-colors duration-300">
<Link
href="/"
class="text-xl font-semibold transition-colors duration-300 hover:text-primary"
>
{SITE.TITLE}
</Link>
<nav class="flex items-center gap-4 md:gap-6 text-sm">
{items.map((item, index) => (
<Fragment key={item.href}>
<Link
href={item.href}
class="transition-colors hover:text-foreground/80 text-foreground/60 capitalize"
>
{item.label}
</Link>
</Fragment>
))}
<nav class="flex items-center gap-4 text-sm sm:gap-6">
{
items.map((item, index) => (
<Fragment key={item.href}>
<Link
href={item.href}
class="capitalize text-foreground/60 transition-colors hover:text-foreground/80"
>
{item.label}
</Link>
</Fragment>
))
}
</nav>
</div>
</Container>

View file

@ -8,7 +8,13 @@ type Props = {
'data-heading'?: string
}
const { href, external, class: className, 'data-heading': dataHeading, ...rest } = Astro.props
const {
href,
external,
class: className,
'data-heading': dataHeading,
...rest
} = Astro.props
---
<a
@ -16,10 +22,10 @@ const { href, external, class: className, 'data-heading': dataHeading, ...rest }
target={external ? '_blank' : '_self'}
class={cn(
'inline-block transition-colors duration-300 ease-in-out hover:underline underline-offset-[3px]',
className
className,
)}
data-heading={dataHeading}
{...rest}
>
<slot />
</a>
</a>

View file

@ -1,29 +0,0 @@
---
import type { CollectionEntry } from 'astro:content'
import { Image } from 'astro:assets'
type Props = {
member: CollectionEntry<'authors'>
}
const { member } = Astro.props
const { name, avatar, bio } = member.data
---
<div
class="not-prose flex size-full flex-col gap-4 overflow-hidden rounded-xl border p-6 hover:bg-secondary sm:flex-row sm:items-center"
>
<Image
src={avatar}
alt={`Avatar of ${name}`}
width={256}
height={256}
class="aspect-square size-32 rounded-md object-cover"
/>
<div class="flex flex-col justify-between">
<a href={`/authors/${member.slug}`}>
<h3 class="mb-2 text-3xl text-foreground">{name}</h3>
<p class="mb-4 text-sm text-foreground">{bio}</p>
</a>
</div>
</div>

View file

@ -10,10 +10,6 @@ export interface Props {
const { URL, icon, icon_size } = Astro.props
---
<a
href={URL}
target={'_blank'}
class={`inline-block ${icon_size}`}
>
<a href={URL} target={'_blank'} class={`inline-block ${icon_size}`}>
<i class={`bi bi-${icon}`}></i>
</a>

View file

@ -1,7 +1,13 @@
---
import { Twitter, Github, Linkedin, Mail, GraduationCap, Rss } from 'lucide-react'
import {
Twitter,
Github,
Linkedin,
Mail,
GraduationCap,
Rss,
} from 'lucide-react'
import { SITE } from '@consts'
---
<ul class="not-prose flex flex-wrap gap-2">

View file

@ -27,16 +27,20 @@ function buildToc(headings: Heading[]) {
}
---
<details open class="block xl:hidden rounded-lg border p-3 mb-8">
<summary class="text-xl font-semibold">Table of Contents</summary>
<details open class="mb-8 block rounded-lg border p-3 xl:hidden">
<summary class="cursor-pointer text-xl font-semibold"
>Table of Contents</summary
>
<nav>
<ul class="py-3">
{toc.map((heading) => <TableOfContentsHeading heading={heading} />)}
</ul>
</nav>
</details>
<nav class="sticky top-16 hidden xl:block h-0 w-[calc(50vw-50%-4rem)] overflow-wrap-break-word text-xs leading-4 translate-x-[calc(-100%-2em)]">
<h2 class="text-xl font-semibold mb-4">Table of Contents</h2>
<nav
class="overflow-wrap-break-word sticky top-16 hidden h-0 w-[calc(50vw-50%-4rem)] translate-x-[calc(-100%-2em)] text-xs leading-4 xl:block"
>
<h2 class="mb-4 text-xl font-semibold">Table of Contents</h2>
<ul class="space-y-2">
{toc.map((heading) => <TableOfContentsHeading heading={heading} />)}
</ul>

View file

@ -5,13 +5,15 @@ import Link from './Link.astro'
const { heading } = Astro.props
---
<li class="list-inside list-disc px-6 py-1.5 xl:list-none xl:p-0 text-sm">
<li
class="list-inside list-disc px-6 py-1.5 text-sm text-foreground/60 xl:list-none xl:p-0"
>
<Link href={'#' + heading.slug} class="toc-link" data-heading={heading.slug}>
{heading.text}
</Link>
{
heading.subheadings.length > 0 && (
<ul class="translate-x-3 xl:translate-x-0 xl:ml-4 xl:mt-2 xl:space-y-2">
<ul class="translate-x-3 xl:ml-4 xl:mt-2 xl:translate-x-0 xl:space-y-2">
{heading.subheadings.map((subheading: Heading) => (
<Astro.self heading={subheading} />
))}
@ -22,28 +24,30 @@ const { heading } = Astro.props
<script>
function updateActiveHeading() {
const headings = document.querySelectorAll('h2, h3, h4, h5, h6');
const tocLinks = document.querySelectorAll('.toc-link');
let currentHeading = '';
headings.forEach(heading => {
const top = heading.getBoundingClientRect().top;
if (top < 100) {
currentHeading = heading.id;
const headings = document.querySelectorAll('h2, h3, h4, h5, h6')
const tocLinks = document.querySelectorAll('.toc-link')
let currentHeading = ''
headings.forEach((heading) => {
const top = heading.getBoundingClientRect().top
if (top < 200) {
currentHeading = heading.id
}
});
tocLinks.forEach(link => {
const headingSlug = link.getAttribute('data-heading');
})
tocLinks.forEach((link) => {
const headingSlug = link.getAttribute('data-heading')
if (headingSlug === currentHeading) {
link.classList.add('underline');
link.classList.add('underline')
link.classList.add('text-foreground')
} else {
link.classList.remove('underline');
link.classList.remove('underline')
link.classList.remove('text-foreground')
}
});
})
}
window.addEventListener('scroll', updateActiveHeading);
window.addEventListener('load', updateActiveHeading);
</script>
window.addEventListener('scroll', updateActiveHeading)
window.addEventListener('load', updateActiveHeading)
</script>

View file

@ -0,0 +1,48 @@
import * as AvatarPrimitive from '@radix-ui/react-avatar'
import * as React from 'react'
import { cn } from '@/lib/utils'
const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Root
ref={ref}
className={cn(
'relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full',
className,
)}
{...props}
/>
))
Avatar.displayName = AvatarPrimitive.Root.displayName
const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Image
ref={ref}
className={cn('aspect-square h-full w-full', className)}
{...props}
/>
))
AvatarImage.displayName = AvatarPrimitive.Image.displayName
const AvatarFallback = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Fallback>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Fallback
ref={ref}
className={cn(
'flex h-full w-full items-center justify-center rounded-full bg-muted',
className,
)}
{...props}
/>
))
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
export { Avatar, AvatarFallback, AvatarImage }

View file

@ -0,0 +1,36 @@
import { cva, type VariantProps } from 'class-variance-authority'
import * as React from 'react'
import { cn } from '@/lib/utils'
const badgeVariants = cva(
'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
{
variants: {
variant: {
default:
'border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80',
secondary:
'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
destructive:
'border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80',
outline: 'text-foreground',
},
},
defaultVariants: {
variant: 'default',
},
},
)
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
)
}
export { Badge, badgeVariants }

View file

@ -1,37 +1,37 @@
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 { cva, type VariantProps } from 'class-variance-authority'
import * as React from 'react'
import { cn } from "@/lib/utils"
import { cn } from '@/lib/utils'
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50',
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
'bg-primary text-primary-foreground shadow hover:bg-primary/90',
destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
outline:
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
secondary:
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-md px-3 text-xs",
lg: "h-10 rounded-md px-8",
icon: "h-9 w-9",
default: 'h-9 px-4 py-2',
sm: 'h-8 rounded-md px-3 text-xs',
lg: 'h-10 rounded-md px-8',
icon: 'h-9 w-9',
},
},
defaultVariants: {
variant: "default",
size: "default",
variant: 'default',
size: 'default',
},
}
},
)
export interface ButtonProps
@ -42,7 +42,7 @@ export interface ButtonProps
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
const Comp = asChild ? Slot : 'button'
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
@ -50,8 +50,8 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
{...props}
/>
)
}
},
)
Button.displayName = "Button"
Button.displayName = 'Button'
export { Button, buttonVariants }

View file

@ -1,12 +1,12 @@
import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
import {
CheckIcon,
ChevronRightIcon,
DotFilledIcon,
} from "@radix-ui/react-icons"
} from '@radix-ui/react-icons'
import * as React from 'react'
import { cn } from "@/lib/utils"
import { cn } from '@/lib/utils'
const DropdownMenu = DropdownMenuPrimitive.Root
@ -29,9 +29,9 @@ const DropdownMenuSubTrigger = React.forwardRef<
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",
inset && "pl-8",
className
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent',
inset && 'pl-8',
className,
)}
{...props}
>
@ -49,8 +49,8 @@ const DropdownMenuSubContent = React.forwardRef<
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className,
)}
{...props}
/>
@ -67,9 +67,9 @@ const DropdownMenuContent = React.forwardRef<
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md',
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className,
)}
{...props}
/>
@ -86,9 +86,9 @@ const DropdownMenuItem = React.forwardRef<
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8",
className
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
inset && 'pl-8',
className,
)}
{...props}
/>
@ -102,8 +102,8 @@ const DropdownMenuCheckboxItem = React.forwardRef<
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className,
)}
checked={checked}
{...props}
@ -126,8 +126,8 @@ const DropdownMenuRadioItem = React.forwardRef<
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className,
)}
{...props}
>
@ -150,9 +150,9 @@ const DropdownMenuLabel = React.forwardRef<
<DropdownMenuPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className
'px-2 py-1.5 text-sm font-semibold',
inset && 'pl-8',
className,
)}
{...props}
/>
@ -165,7 +165,7 @@ const DropdownMenuSeparator = React.forwardRef<
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
className={cn('-mx-1 my-1 h-px bg-muted', className)}
{...props}
/>
))
@ -177,27 +177,27 @@ const DropdownMenuShortcut = ({
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
className={cn('ml-auto text-xs tracking-widest opacity-60', className)}
{...props}
/>
)
}
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
DropdownMenuShortcut.displayName = 'DropdownMenuShortcut'
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuPortal,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
DropdownMenuTrigger,
}

View file

@ -1,30 +1,30 @@
import * as React from "react"
import { Moon, Sun } from "lucide-react"
import { Moon, Sun } from 'lucide-react'
import * as React from 'react'
import { Button } from "@/components/ui/button"
import { Button } from '@/components/ui/button'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
} from '@/components/ui/dropdown-menu'
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)
document.documentElement.classList[isDark ? "add" : "remove"]("dark")
theme === 'dark' ||
(theme === 'system' &&
window.matchMedia('(prefers-color-scheme: dark)').matches)
document.documentElement.classList[isDark ? 'add' : 'remove']('dark')
}, [theme])
return (
@ -37,13 +37,13 @@ export function ModeToggle() {
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setThemeState("theme-light")}>
<DropdownMenuItem onClick={() => setThemeState('theme-light')}>
Light
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setThemeState("dark")}>
<DropdownMenuItem onClick={() => setThemeState('dark')}>
Dark
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setThemeState("system")}>
<DropdownMenuItem onClick={() => setThemeState('system')}>
System
</DropdownMenuItem>
</DropdownMenuContent>

View file

@ -8,7 +8,8 @@ export type Site = {
export const SITE: Site = {
TITLE: 'astro-erudite',
DESCRIPTION: 'astro-erudite is a opinionated, no-frills blogging template. Built with Astro.',
DESCRIPTION:
'astro-erudite is a opinionated, no-frills blogging template. Built with Astro.',
EMAIL: 'youremail@gmail.com',
NUM_POSTS_ON_HOMEPAGE: 2,
SITEURL: 'https://astro-erudite.vercel.app',

View file

@ -1,112 +0,0 @@
---
title: '[Logs] What is new in Astro Micro Academic'
description: 'Features, enhancements, and changes.'
date: '2024-07-25'
tags: ['log', 'rss-feed']
---
## h2 Heading
### h3 Heading
#### h4 Heading
##### h5 Heading
###### h6 Heading
## Emphasis
**This is bold text**
__This is bold text__
*This is italic text*
_This is italic text_
~~Strikethrough~~
## Blockquotes
> Blockquotes can also be nested...
>> ...by using additional greater-than signs right next to each other...
> > > ...or with spaces between arrows.
## Lists
Unordered
+ Create a list by starting a line with `+`, `-`, or `*`
+ Sub-lists are made by indenting 2 spaces:
- Marker character change forces new list start:
* Ac tristique libero volutpat at
+ Facilisis in pretium nisl aliquet
- Nulla volutpat aliquam velit
+ Very easy!
Ordered
1. Lorem ipsum dolor sit amet
2. Consectetur adipiscing elit
3. Integer molestie lorem at massa
1. You can use sequential numbers...
1. ...or keep all the numbers as `1.`
Start numbering with offset:
57. foo
1. bar
## Code
Inline `code`
Indented code
// Some comments
line 1 of code
line 2 of code
line 3 of code
Block code "fences"
```
Sample text here...
```
Syntax highlighting
``` js
var foo = function (bar) {
return bar++;
};
console.log(foo(5));
```
## Tables
| Option | Description |
| ------ | ----------- |
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
Right aligned columns
| Option | Description |
| ------:| -----------:|
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
## Links
[link text](http://dev.nodeca.com)
[link with title](http://nodeca.github.io/pica/demo/ "title text!")

View file

@ -0,0 +1,113 @@
---
title: 'Blog Post'
description: 'Features, enhancements, and changes.'
date: '2022-07-25'
tags: ['log', 'rss-feed']
image: '/1200x630.png'
author: 'enscribe'
---
## h2 Heading
### h3 Heading
#### h4 Heading
##### h5 Heading
###### h6 Heading
## Emphasis
**This is bold text**
**This is bold text**
_This is italic text_
_This is italic text_
~~Strikethrough~~
## Blockquotes
> Blockquotes can also be nested...
>
> > ...by using additional greater-than signs right next to each other...
> >
> > > ...or with spaces between arrows.
## Lists
Unordered
- Create a list by starting a line with `+`, `-`, or `*`
- Sub-lists are made by indenting 2 spaces:
- Marker character change forces new list start:
- Ac tristique libero volutpat at
* Facilisis in pretium nisl aliquet
- Nulla volutpat aliquam velit
- Very easy!
Ordered
1. Lorem ipsum dolor sit amet
2. Consectetur adipiscing elit
3. Integer molestie lorem at massa
4. You can use sequential numbers...
5. ...or keep all the numbers as `1.`
Start numbering with offset:
57. foo
1. bar
## Code
Inline `code`
Indented code
// Some comments
line 1 of code
line 2 of code
line 3 of code
Block code "fences"
```
Sample text here...
```
Syntax highlighting
```js
var foo = function (bar) {
return bar++
}
console.log(foo(5))
```
## Tables
| Option | Description |
| ------ | ------------------------------------------------------------------------- |
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
Right aligned columns
| Option | Description |
| -----: | ------------------------------------------------------------------------: |
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
## Links
[link text](http://dev.nodeca.com)
[link with title](http://nodeca.github.io/pica/demo/ 'title text!')

View file

@ -0,0 +1,113 @@
---
title: 'Blog Post'
description: 'Features, enhancements, and changes.'
date: '2023-07-25'
tags: ['log', 'rss-feed']
image: '/1200x630.png'
author: 'enscribe'
---
## h2 Heading
### h3 Heading
#### h4 Heading
##### h5 Heading
###### h6 Heading
## Emphasis
**This is bold text**
**This is bold text**
_This is italic text_
_This is italic text_
~~Strikethrough~~
## Blockquotes
> Blockquotes can also be nested...
>
> > ...by using additional greater-than signs right next to each other...
> >
> > > ...or with spaces between arrows.
## Lists
Unordered
- Create a list by starting a line with `+`, `-`, or `*`
- Sub-lists are made by indenting 2 spaces:
- Marker character change forces new list start:
- Ac tristique libero volutpat at
* Facilisis in pretium nisl aliquet
- Nulla volutpat aliquam velit
- Very easy!
Ordered
1. Lorem ipsum dolor sit amet
2. Consectetur adipiscing elit
3. Integer molestie lorem at massa
4. You can use sequential numbers...
5. ...or keep all the numbers as `1.`
Start numbering with offset:
57. foo
1. bar
## Code
Inline `code`
Indented code
// Some comments
line 1 of code
line 2 of code
line 3 of code
Block code "fences"
```
Sample text here...
```
Syntax highlighting
```js
var foo = function (bar) {
return bar++
}
console.log(foo(5))
```
## Tables
| Option | Description |
| ------ | ------------------------------------------------------------------------- |
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
Right aligned columns
| Option | Description |
| -----: | ------------------------------------------------------------------------: |
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
## Links
[link text](http://dev.nodeca.com)
[link with title](http://nodeca.github.io/pica/demo/ 'title text!')

View file

@ -0,0 +1,113 @@
---
title: 'Blog Post'
description: 'Features, enhancements, and changes.'
date: '2024-07-25'
tags: ['log', 'rss-feed']
image: '/1200x630.png'
author: 'enscribe'
---
## h2 Heading
### h3 Heading
#### h4 Heading
##### h5 Heading
###### h6 Heading
## Emphasis
**This is bold text**
**This is bold text**
_This is italic text_
_This is italic text_
~~Strikethrough~~
## Blockquotes
> Blockquotes can also be nested...
>
> > ...by using additional greater-than signs right next to each other...
> >
> > > ...or with spaces between arrows.
## Lists
Unordered
- Create a list by starting a line with `+`, `-`, or `*`
- Sub-lists are made by indenting 2 spaces:
- Marker character change forces new list start:
- Ac tristique libero volutpat at
* Facilisis in pretium nisl aliquet
- Nulla volutpat aliquam velit
- Very easy!
Ordered
1. Lorem ipsum dolor sit amet
2. Consectetur adipiscing elit
3. Integer molestie lorem at massa
4. You can use sequential numbers...
5. ...or keep all the numbers as `1.`
Start numbering with offset:
57. foo
1. bar
## Code
Inline `code`
Indented code
// Some comments
line 1 of code
line 2 of code
line 3 of code
Block code "fences"
```
Sample text here...
```
Syntax highlighting
```js
var foo = function (bar) {
return bar++
}
console.log(foo(5))
```
## Tables
| Option | Description |
| ------ | ------------------------------------------------------------------------- |
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
Right aligned columns
| Option | Description |
| -----: | ------------------------------------------------------------------------: |
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
## Links
[link text](http://dev.nodeca.com)
[link with title](http://nodeca.github.io/pica/demo/ 'title text!')

View file

@ -0,0 +1,113 @@
---
title: 'Blog Post'
description: 'Features, enhancements, and changes.'
date: '2024-07-25'
tags: ['log', 'rss-feed']
image: '/1200x630.png'
author: 'enscribe'
---
## h2 Heading
### h3 Heading
#### h4 Heading
##### h5 Heading
###### h6 Heading
## Emphasis
**This is bold text**
**This is bold text**
_This is italic text_
_This is italic text_
~~Strikethrough~~
## Blockquotes
> Blockquotes can also be nested...
>
> > ...by using additional greater-than signs right next to each other...
> >
> > > ...or with spaces between arrows.
## Lists
Unordered
- Create a list by starting a line with `+`, `-`, or `*`
- Sub-lists are made by indenting 2 spaces:
- Marker character change forces new list start:
- Ac tristique libero volutpat at
* Facilisis in pretium nisl aliquet
- Nulla volutpat aliquam velit
- Very easy!
Ordered
1. Lorem ipsum dolor sit amet
2. Consectetur adipiscing elit
3. Integer molestie lorem at massa
4. You can use sequential numbers...
5. ...or keep all the numbers as `1.`
Start numbering with offset:
57. foo
1. bar
## Code
Inline `code`
Indented code
// Some comments
line 1 of code
line 2 of code
line 3 of code
Block code "fences"
```
Sample text here...
```
Syntax highlighting
```js
var foo = function (bar) {
return bar++
}
console.log(foo(5))
```
## Tables
| Option | Description |
| ------ | ------------------------------------------------------------------------- |
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
Right aligned columns
| Option | Description |
| -----: | ------------------------------------------------------------------------: |
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
## Links
[link text](http://dev.nodeca.com)
[link with title](http://nodeca.github.io/pica/demo/ 'title text!')

View file

@ -7,6 +7,7 @@ const blog = defineCollection({
description: z.string(),
date: z.coerce.date(),
draft: z.boolean().optional(),
image: z.string().optional(),
tags: z.array(z.string()).optional(),
author: z.union([reference('authors'), z.string()]).optional(),

View file

@ -18,7 +18,7 @@ const { title, description } = Astro.props
<Head title={`${title} | ${SITE.TITLE}`} description={description} />
</head>
<body
class="box-border flex h-fit min-h-screen flex-col px-4 font-sans bg-background text-foreground antialiased gap-y-4"
class="box-border flex h-fit min-h-screen flex-col gap-y-4 bg-background px-4 font-sans text-foreground antialiased"
>
<Header />
<main class="flex-grow">

View file

@ -2,18 +2,18 @@
import Layout from '@layouts/Layout.astro'
import Container from '@components/Container.astro'
import Link from '@components/Link.astro'
import BackToPrevious from '@components/BackToPrevious.astro'
import { SITE } from '@consts'
import { Button } from '@/components/ui/button'
---
<Layout title="404" description={SITE.DESCRIPTION}>
<Container>
<div class="mt-16 grid place-items-center gap-3">
<h4 class="text-2xl font-semibold">
404: Page not found
</h4>
<h4 class="text-2xl font-semibold">404: Page not found</h4>
<span>
<BackToPrevious href="/">Go to home page</BackToPrevious>
<a href="/">
<Button variant="outline"> Go to home page </Button>
</a>
</span>
</div>
</Container>

View file

@ -1,62 +1,35 @@
---
import { getCollection } from 'astro:content'
import Layout from '@layouts/Layout.astro'
import Container from '@components/Container.astro'
import { Image } from 'astro:assets'
import Link from '@components/Link.astro'
import { SITE } from '@consts'
---
<Layout title="About" description="About">
<Layout title="About" description={SITE.DESCRIPTION}>
<Container>
<div class="space-y-10">
<div class="font-semibold">About</div>
<section class="not-prose flex flex-col gap-4 text-justify">
<p class="text-justify">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolores porro
hic minima incidunt explicabo obcaecati consectetur consequuntur at
quisquam commodi.
</p>
<article class="prose max-w-none dark:prose-invert">
<h1 class="mb-8 text-3xl font-bold">About Us</h1>
<p class="mb-4">
Welcome to {SITE.TITLE}, a platform dedicated to sharing insights,
knowledge, and experiences in the world of technology and beyond. Our
mission is to provide valuable content that informs, inspires, and
engages our readers.
</p>
<p class="mb-8">
Founded in 2023, we've grown into a community of passionate writers and
tech enthusiasts. Our team brings diverse expertise to the table,
covering topics ranging from software development and artificial
intelligence to digital culture and tech ethics.
</p>
<p class="text-justify">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolores porro
hic minima incidunt explicabo obcaecati consectetur consequuntur at
quisquam commodi.
</p>
</section>
<div class="flex flex-col justify-center md:flex-row">
<div class="my-10 text-center">
<div
class="h-[250px] w-[350px] -rotate-6 overflow-hidden rounded-xl object-cover"
>
<Image
src={'/astro-nano.png'}
alt={'life2'}
width={350}
height={250}
class="h-[250px] w-[350px] overflow-hidden rounded-xl object-cover"
/>
</div>
<p class="mt-4 text-sm">
Lorem ipsum dolor sit amet, consectetur adipisicing elit.
</p>
</div>
<div class="mx-10 my-10 text-center">
<div
class="mx-auto h-[250px] w-[150px] rotate-6 rounded-xl object-cover sm:ml-auto"
>
<Image
src={'/astro-micro.jpg'}
alt={'life2'}
width={150}
height={250}
class="h-[250px] w-[150px] rounded-xl object-cover"
/>
</div>
<p class="mt-4 text-sm">
Lorem ipsum dolor sit amet, consectetur adipisicing elit.
</p>
</div>
</div>
</div>
<h2 class="mb-4 text-2xl font-semibold">Get in Touch</h2>
<p class="mb-4">
We love hearing from our readers! Whether you have a question,
suggestion, or just want to say hello, don't hesitate to reach out.
</p>
<p>
<Link href="/contact" class="font-semibold">Contact Us</Link>
</p>
</article>
</Container>
</Layout>

View file

@ -1,29 +1,32 @@
---
import { type CollectionEntry, getCollection, getEntry } from 'astro:content'
import { type CollectionEntry, getCollection } from 'astro:content'
import Layout from '@layouts/Layout.astro'
import MemberCard from '@components/MemberCard.astro'
import Container from '@components/Container.astro'
import AuthorCard from '@components/AuthorCard.astro'
import BlogCard from '@components/BlogCard.astro'
import { Button } from '@/components/ui/button'
export async function getStaticPaths() {
const authors = await getCollection('authors')
return authors.map((member) => ({
params: { slug: member.slug },
props: { member },
return authors.map((author) => ({
params: { slug: author.slug },
props: { author },
}))
}
type Props = {
member: CollectionEntry<'authors'>
author: CollectionEntry<'authors'>
}
const { member } = Astro.props
const { author } = Astro.props
const allPosts = await getCollection('blog')
const memberPosts = allPosts
const authorPosts = allPosts
.filter((post) => {
if (typeof post.data.author === 'string') {
return post.data.author === member.data.name && !post.data.draft
return post.data.author === author.data.name && !post.data.draft
} else if (post.data.author && 'slug' in post.data.author) {
return post.data.author.slug === member.slug && !post.data.draft
return post.data.author.slug === author.slug && !post.data.draft
}
return false
})
@ -31,10 +34,35 @@ const memberPosts = allPosts
---
<Layout
title={`${member.data.name} - Team Member`}
description={member.data.bio || `Profile of ${member.data.name}`}
title={`${author.data.name} - Author`}
description={author.data.bio || `Profile of ${author.data.name}`}
>
<section class="mx-auto flex max-w-screen-xl flex-col gap-4">
<MemberCard member={member} />
</section>
<Container>
<a href="/authors">
<Button variant="ghost" className="mb-8">&larr; Back to authors</Button>
</a>
<div class="space-y-10">
<section>
<AuthorCard author={author} />
</section>
<section class="space-y-4">
<h2 class="text-2xl font-semibold">Posts by {author.data.name}</h2>
{
authorPosts.length > 0 ? (
<ul class="not-prose flex flex-col gap-4">
{authorPosts.map((post) => (
<li>
<BlogCard entry={post} />
</li>
))}
</ul>
) : (
<p class="text-muted-foreground">
No posts available from this author.
</p>
)
}
</section>
</div>
</Container>
</Layout>

View file

@ -1,21 +1,25 @@
---
import { getCollection } from 'astro:content'
import Layout from '@layouts/Layout.astro'
import MemberCard from '@components/MemberCard.astro'
import Container from '@components/Container.astro'
import AuthorCard from '@components/AuthorCard.astro'
const authors = await getCollection('authors')
---
<Layout title="authors" description="authors">
<section>
<ul class="not-prose grid grid-cols-1 gap-4 lg:grid-cols-2 xl:grid-cols-3">
{
authors.map((member) => (
<li>
<MemberCard member={member} />
</li>
))
}
</ul>
</section>
<Layout title="Authors" description="Authors">
<Container>
<div class="space-y-10">
<h1 class="text-3xl font-semibold">Authors</h1>
<ul class="not-prose flex flex-col gap-4">
{
authors.map((author) => (
<li>
<AuthorCard author={author} />
</li>
))
}
</ul>
</div>
</Container>
</Layout>

View file

@ -1,12 +1,14 @@
---
import { type CollectionEntry, getCollection } from 'astro:content'
import { type CollectionEntry, getCollection, getEntry } from 'astro:content'
import Layout from '@layouts/Layout.astro'
import Container from '@components/Container.astro'
import FormattedDate from '@components/FormattedDate.astro'
import { readingTime } from '@lib/utils'
import BackToPrevious from '@components/BackToPrevious.astro'
import PostNavigation from '@components/PostNavigation.astro'
import TableOfContents from '@components/TableOfContents.astro'
import { Image } from 'astro:assets'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
export async function getStaticPaths() {
const posts = (await getCollection('blog'))
@ -45,47 +47,112 @@ const prevPost = getPrevPost(currentPostSlug)
const post = Astro.props
const { Content, headings } = await post.render()
let author = null
if (
post.data.author &&
typeof post.data.author === 'object' &&
'collection' in post.data.author
) {
author = await getEntry(post.data.author)
} else if (typeof post.data.author === 'string') {
author = {
data: {
name: post.data.author,
avatar: '/favicons/android-chrome-512x512.png',
},
}
}
---
<Layout title={post.data.title} description={post.data.description}>
<Container>
<div class="animate">
<BackToPrevious href="/blog">Back to blog</BackToPrevious>
</div>
<div class="my-10 space-y-4">
<div class="flex items-center gap-1.5">
<div class="font-base text-sm">
<FormattedDate date={post.data.date} />
</div>
&bull;
<div class="font-base text-sm">
{readingTime(post.body)}
<a href="/blog">
<Button variant="ghost" className="mb-8">&larr; Back to blog</Button>
</a>
{
post.data.image && (
<div class="mb-8 flex justify-center">
<Image
src={post.data.image}
alt={post.data.title}
width={1200}
height={630}
class="rounded-lg object-cover shadow-lg"
/>
</div>
)
}
<div class="mb-8 flex flex-col items-center gap-y-4">
<div class="flex items-center gap-3 text-sm text-muted-foreground">
<FormattedDate date={post.data.date} />
<span>&bull;</span>
<span>{readingTime(post.body)}</span>
</div>
<h1 class="text-4xl font-semibold">
<h1 class="text-4xl font-bold leading-tight sm:text-5xl">
{post.data.title}
</h1>
<div class="font-base text-sm">
<div class="flex flex-wrap gap-2">
{
post.data.tags && post.data.tags.length > 0 ? (
post.data.tags.map((tag) => (
<div class="my-1 inline-block">
<a
href={`/tags/${tag}`}
class="mx-1 rounded-full px-2 py-1 transition-colors duration-300 ease-in-out"
>
#{tag}
<Button variant="outline" asChild>
<a href={`/tags/${tag}`}>
<Badge variant="outline">{tag}</Badge>
</a>
</div>
</Button>
))
) : (
<span>No tags available</span>
<span class="text-sm text-muted-foreground">No tags available</span>
)
}
</div>
{
author && (
<div>
{typeof post.data.author === 'object' &&
'collection' in post.data.author ? (
<Button variant="ghost" asChild>
<a
class="group flex items-center gap-3 rounded-xl p-2 transition-colors duration-300 ease-in-out hover:bg-secondary"
href={`/authors/${post.data.author.slug}`}
>
<Image
src={author.data.avatar}
alt={`Avatar of ${author.data.name}`}
width={48}
height={48}
class="rounded-full"
/>
<div>
<span class="font-medium group-hover:text-primary">
{author.data.name}
</span>
<p class="text-sm text-muted-foreground">Author</p>
</div>
</a>
</Button>
) : (
<div class="flex items-center gap-3 rounded-xl p-2">
<Image
src={author.data.avatar || '/default-avatar.png'}
alt={`Avatar of ${author.data.name}`}
width={48}
height={48}
class="rounded-full"
/>
<div>
<span class="font-medium">{author.data.name}</span>
<p class="text-sm text-muted-foreground">Author</p>
</div>
</div>
)}
</div>
)
}
</div>
{headings.length > 0 && <TableOfContents headings={headings} />}
<article class="prose prose-neutral max-w-full dark:prose-invert prose-img:mx-auto prose-img:my-auto">
<article class="max-w-full">
<Content />
<div class="mt-24">
<PostNavigation prevPost={prevPost} nextPost={nextPost} />

View file

@ -2,7 +2,7 @@
import { type CollectionEntry, getCollection } from 'astro:content'
import Layout from '@layouts/Layout.astro'
import Container from '@components/Container.astro'
import ArrowCard from '@components/ArrowCard.astro'
import BlogCard from '@components/BlogCard.astro'
const data = (await getCollection('blog'))
.filter((post) => !post.data.draft)
@ -36,7 +36,7 @@ const years = Object.keys(posts).sort((a, b) => parseInt(b) - parseInt(a))
<ul class="not-prose flex flex-col gap-4">
{posts[year].map((post) => (
<li>
<ArrowCard entry={post} />
<BlogCard entry={post} />
</li>
))}
</ul>

View file

@ -2,7 +2,7 @@
import Layout from '@layouts/Layout.astro'
import Container from '@components/Container.astro'
import { SITE } from '@consts'
import ArrowCard from '@components/ArrowCard.astro'
import BlogCard from '@components/BlogCard.astro'
import Link from '@components/Link.astro'
import { getCollection } from 'astro:content'
@ -14,7 +14,7 @@ const blog = (await getCollection('blog'))
<Layout title="Home" description="Home">
<Container>
<section class="space-y-6">
<section class="space-y-4">
<div class="flex flex-wrap items-center justify-between gap-y-2">
<h2 class="font-semibold">Latest posts</h2>
<Link href="/blog"> See all posts </Link>
@ -23,7 +23,7 @@ const blog = (await getCollection('blog'))
{
blog.map((post) => (
<li>
<ArrowCard entry={post} />
<BlogCard entry={post} />
</li>
))
}

View file

@ -2,7 +2,7 @@
import { type CollectionEntry, getCollection } from 'astro:content'
import Layout from '@layouts/Layout.astro'
import Container from '@components/Container.astro'
import ArrowCard from '@components/ArrowCard.astro'
import BlogCard from '@components/BlogCard.astro'
type BlogPost = CollectionEntry<'blog'>
@ -36,11 +36,11 @@ export async function getStaticPaths() {
>
<Container>
<div class="space-y-10">
<div class="font-semibold">
Tag: <span
class="mx-2 rounded-full px-3 py-2 transition-colors duration-300 ease-in-out"
>#{tag}</span
>
<div class="flex items-center gap-2">
<h1 class="text-3xl font-semibold">Posts tagged with</h1>
<span class="rounded-full bg-secondary px-4 py-2 text-2xl font-medium">
{tag}
</span>
</div>
<div class="space-y-4">
{
@ -49,7 +49,7 @@ export async function getStaticPaths() {
<div>
<ul class="not-prose flex flex-col gap-4">
<li>
<ArrowCard entry={post} />
<BlogCard entry={post} />
</li>
</ul>
</div>

View file

@ -2,7 +2,7 @@
import { getCollection } from 'astro:content'
import Layout from '@layouts/Layout.astro'
import Container from '@components/Container.astro'
import Image from 'astro/components/Image.astro'
import { Badge } from '@/components/ui/badge'
const blog = (await getCollection('blog')).filter((post) => !post.data.draft)
@ -14,21 +14,18 @@ const tags = blog
<Layout title="Tags" description="Tags">
<Container>
<div class="space-y-10">
<div class="font-semibold">Tags</div>
<ul class="flex flex-wrap">
<h1 class="text-3xl font-semibold">Tags</h1>
<div class="flex flex-wrap gap-2">
{
tags.map((tag) => (
<li class="my-3">
<a
href={`/tags/${tag}`}
class="mx-2 rounded-full px-3 py-2 transition-colors duration-300 ease-in-out"
>
<a href={`/tags/${tag}`}>
<Badge variant="secondary" className="hover:bg-secondary/80">
#{tag}
</a>
</li>
</Badge>
</a>
))
}
</ul>
</div>
</div>
</Container>
</Layout>

View file

@ -93,7 +93,7 @@
html {
color-scheme: light;
scrollbar-gutter: stable;
scrollbar-gutter: stable both-edges;
}
html.dark {

View file

@ -4,6 +4,7 @@
@layer base {
article {
@apply prose prose-neutral dark:prose-invert;
@apply prose-h1:scroll-m-20 prose-h1:text-4xl prose-h1:font-extrabold prose-h1:tracking-tight prose-h1:lg:text-5xl;
@apply prose-h2:mt-10 prose-h2:scroll-m-20 prose-h2:border-b prose-h2:pb-2 prose-h2:text-3xl prose-h2:font-semibold prose-h2:tracking-tight prose-h2:transition-colors prose-h2:first:mt-0;
@apply prose-h3:mt-8 prose-h3:scroll-m-20 prose-h3:text-2xl prose-h3:font-semibold prose-h3:tracking-tight;
@ -12,5 +13,6 @@
@apply prose-blockquote:mt-6 prose-blockquote:border-l-2 prose-blockquote:pl-6 prose-blockquote:italic;
@apply prose-ul:my-6 prose-ul:ml-6 prose-ul:list-disc prose-ul:[&>li]:mt-2;
@apply prose-pre:border prose-pre:border-border;
@apply prose-img:mx-auto prose-img:my-auto;
}
}