feat(content): new blog post
This commit is contained in:
parent
8e6df747e0
commit
28bd68c09e
17 changed files with 118 additions and 40 deletions
|
@ -24,7 +24,7 @@ export default defineConfig({
|
||||||
site: 'https://astro-erudite.vercel.app',
|
site: 'https://astro-erudite.vercel.app',
|
||||||
integrations: [
|
integrations: [
|
||||||
expressiveCode({
|
expressiveCode({
|
||||||
themes: ['min-light', 'min-dark'],
|
themes: ['github-light', 'github-dark'],
|
||||||
plugins: [pluginCollapsibleSections(), pluginLineNumbers()],
|
plugins: [pluginCollapsibleSections(), pluginLineNumbers()],
|
||||||
useDarkModeMediaQuery: false,
|
useDarkModeMediaQuery: false,
|
||||||
themeCssSelector: (theme) => `.${theme.name.split('-')[1]}`,
|
themeCssSelector: (theme) => `.${theme.name.split('-')[1]}`,
|
||||||
|
@ -44,6 +44,7 @@ export default defineConfig({
|
||||||
codeBackground:
|
codeBackground:
|
||||||
'color-mix(in oklab, var(--secondary) 25%, transparent)',
|
'color-mix(in oklab, var(--secondary) 25%, transparent)',
|
||||||
frames: {
|
frames: {
|
||||||
|
editorActiveTabForeground: 'var(--muted-foreground)',
|
||||||
editorActiveTabBackground:
|
editorActiveTabBackground:
|
||||||
'color-mix(in oklab, var(--secondary) 25%, transparent)',
|
'color-mix(in oklab, var(--secondary) 25%, transparent)',
|
||||||
editorActiveTabIndicatorBottomColor: 'transparent',
|
editorActiveTabIndicatorBottomColor: 'transparent',
|
||||||
|
@ -95,8 +96,8 @@ export default defineConfig({
|
||||||
rehypePrettyCode,
|
rehypePrettyCode,
|
||||||
{
|
{
|
||||||
theme: {
|
theme: {
|
||||||
light: 'min-light',
|
light: 'github-light',
|
||||||
dark: 'min-dark',
|
dark: 'github-dark',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -4,9 +4,9 @@ import { Badge } from '@/components/ui/badge'
|
||||||
import { Separator } from '@/components/ui/separator'
|
import { Separator } from '@/components/ui/separator'
|
||||||
import { parseAuthors } from '@/lib/server-utils'
|
import { parseAuthors } from '@/lib/server-utils'
|
||||||
import { formatDate, readingTime } from '@/lib/utils'
|
import { formatDate, readingTime } from '@/lib/utils'
|
||||||
|
import { Icon } from 'astro-icon/components'
|
||||||
import { Image } from 'astro:assets'
|
import { Image } from 'astro:assets'
|
||||||
import type { CollectionEntry } from 'astro:content'
|
import type { CollectionEntry } from 'astro:content'
|
||||||
import { Icon } from 'astro-icon/components'
|
|
||||||
import Link from './Link.astro'
|
import Link from './Link.astro'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
|
@ -7,7 +7,7 @@ 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 sm:grid-cols-2 gap-4">
|
<div class="col-start-2 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||||
<Link
|
<Link
|
||||||
href={nextPost ? `/blog/${nextPost.id}` : '#'}
|
href={nextPost ? `/blog/${nextPost.id}` : '#'}
|
||||||
class={cn(
|
class={cn(
|
||||||
|
|
|
@ -43,9 +43,7 @@ const { project } = Astro.props
|
||||||
project.data.tags && (
|
project.data.tags && (
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
{project.data.tags.map((tag: string) => (
|
{project.data.tags.map((tag: string) => (
|
||||||
<Badge variant="secondary">
|
<Badge variant="secondary">{tag}</Badge>
|
||||||
{tag}
|
|
||||||
</Badge>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -140,7 +140,7 @@ const PaginationComponent: React.FC<PaginationProps> = ({
|
||||||
<Pagination>
|
<Pagination>
|
||||||
<PaginationContent className="flex-wrap">
|
<PaginationContent className="flex-wrap">
|
||||||
<PaginationItem>
|
<PaginationItem>
|
||||||
<PaginationPrevious
|
<PaginationPrevious
|
||||||
href={currentPage > 1 ? getPageUrl(currentPage - 1) : undefined}
|
href={currentPage > 1 ? getPageUrl(currentPage - 1) : undefined}
|
||||||
isDisabled={currentPage === 1}
|
isDisabled={currentPage === 1}
|
||||||
/>
|
/>
|
||||||
|
@ -148,7 +148,7 @@ const PaginationComponent: React.FC<PaginationProps> = ({
|
||||||
|
|
||||||
{pages.map((page) => (
|
{pages.map((page) => (
|
||||||
<PaginationItem key={page}>
|
<PaginationItem key={page}>
|
||||||
<PaginationLink
|
<PaginationLink
|
||||||
href={getPageUrl(page)}
|
href={getPageUrl(page)}
|
||||||
isActive={page === currentPage}
|
isActive={page === currentPage}
|
||||||
>
|
>
|
||||||
|
@ -165,7 +165,9 @@ const PaginationComponent: React.FC<PaginationProps> = ({
|
||||||
|
|
||||||
<PaginationItem>
|
<PaginationItem>
|
||||||
<PaginationNext
|
<PaginationNext
|
||||||
href={currentPage < totalPages ? getPageUrl(currentPage + 1) : undefined}
|
href={
|
||||||
|
currentPage < totalPages ? getPageUrl(currentPage + 1) : undefined
|
||||||
|
}
|
||||||
isDisabled={currentPage === totalPages}
|
isDisabled={currentPage === totalPages}
|
||||||
/>
|
/>
|
||||||
</PaginationItem>
|
</PaginationItem>
|
||||||
|
@ -182,7 +184,6 @@ interface PaginationProps {
|
||||||
|
|
||||||
export default PaginationComponent
|
export default PaginationComponent
|
||||||
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Pagination,
|
Pagination,
|
||||||
PaginationContent,
|
PaginationContent,
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 90 KiB |
9
src/content/blog/2022-post/index.mdx
vendored
9
src/content/blog/2022-post/index.mdx
vendored
|
@ -1,9 +0,0 @@
|
||||||
---
|
|
||||||
title: '2022 Post'
|
|
||||||
description: 'This a dummy post written in the year 2022.'
|
|
||||||
date: 2022-06-01
|
|
||||||
tags: ['dummy', 'placeholder']
|
|
||||||
image: './2022.png'
|
|
||||||
---
|
|
||||||
|
|
||||||
This is a dummy post written in the year 2022.
|
|
2
src/content/blog/2023-post/index.mdx
vendored
2
src/content/blog/2023-post/index.mdx
vendored
|
@ -2,7 +2,7 @@
|
||||||
title: '2023 Post'
|
title: '2023 Post'
|
||||||
description: 'This a dummy post written in the year 2023.'
|
description: 'This a dummy post written in the year 2023.'
|
||||||
date: 2023-06-01
|
date: 2023-06-01
|
||||||
tags: ['dummy', 'placeholder']
|
tags: ['v1.0.0']
|
||||||
image: './2023.png'
|
image: './2023.png'
|
||||||
authors: ['enscribe']
|
authors: ['enscribe']
|
||||||
---
|
---
|
||||||
|
|
2
src/content/blog/2024-post/index.mdx
vendored
2
src/content/blog/2024-post/index.mdx
vendored
|
@ -2,7 +2,7 @@
|
||||||
title: '2024 Post'
|
title: '2024 Post'
|
||||||
description: 'This a dummy post written in the year 2024 (with multiple authors).'
|
description: 'This a dummy post written in the year 2024 (with multiple authors).'
|
||||||
date: 2024-06-01
|
date: 2024-06-01
|
||||||
tags: ['dummy', 'placeholder']
|
tags: ['v1.0.0']
|
||||||
image: './2024.png'
|
image: './2024.png'
|
||||||
authors: ['enscribe', 'jktrn']
|
authors: ['enscribe', 'jktrn']
|
||||||
---
|
---
|
||||||
|
|
BIN
src/content/blog/rehype-patch/1200x630.png
Normal file
BIN
src/content/blog/rehype-patch/1200x630.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 77 KiB |
89
src/content/blog/rehype-patch/index.mdx
vendored
Normal file
89
src/content/blog/rehype-patch/index.mdx
vendored
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
---
|
||||||
|
title: 'v1.3.0: “Patches in Production”'
|
||||||
|
description: 'Whenever you depend on Node packages with missing maintainers, patching becomes a necessary evil.'
|
||||||
|
date: 2025-03-21
|
||||||
|
tags: ['v1.3.0']
|
||||||
|
image: './1200x630.png'
|
||||||
|
authors: ['enscribe']
|
||||||
|
---
|
||||||
|
|
||||||
|
## A problem (about dead maintainers)
|
||||||
|
|
||||||
|
This post talks about changes I've made to astro-erudite in v1.3.0!
|
||||||
|
|
||||||
|
I recently found myself caught between two syntax highlighting packages that I absolutely needed for astro-erudite. On one hand, the current template uses [rehype-pretty-code](https://rehype-pretty.pages.dev/) as its main syntax highlighting solution, but due to issues with its inherent implementation and missing features that I needed, I had created a bunch of custom transformers to make it do what I wanted, and the whole setup was getting unwieldy. I then discovered [Expressive Code](https://expressive-code.com/), which had everything I wanted out of the box—collapsible code sections, terminal and editor frames, gutter comments—it was perfect! Well, almost perfect.
|
||||||
|
|
||||||
|
The primary issue was that Expressive Code doesn't support inline syntax highlighting, which is non-negotiable for me since I need my inline code snippets to look as good as my code blocks (so I could do stuff like `console.log("Hello, world!".split('').reverse().join('')){:js}`). So I opened a feature request at [expressive-code/expressive-code#250](https://github.com/expressive-code/expressive-code/issues/250) and the maintainer seemed interested, saying they'd get around to it eventually. Implementing this feature is a lot easier said than done though, and I summarized it well in another thread:
|
||||||
|
|
||||||
|
> [@jktrn](https://github.com/rehype-pretty/rehype-pretty-code/issues/247#issuecomment-2619869436): [...] expressive-code is already interested in implementing inline code support, but it would be a bit nuanced to add since it has to:
|
||||||
|
>
|
||||||
|
> - allow existing plugins to continue working normally with block-level code (without breaking changes),
|
||||||
|
> - enable new plugins to explicitly declare support for inline code,
|
||||||
|
> - and provide ways for plugins to distinguish between inline and block-level code processing.
|
||||||
|
|
||||||
|
However, I needed a solution immediately. My first thought was to use both packages together—Expressive Code for block code and rehype-pretty-code for inline code. However, importing both at the same time caused everything to break spectacularly.
|
||||||
|
|
||||||
|
## The hunt for a solution
|
||||||
|
|
||||||
|
Digging into the rehype-pretty-code docs, I noticed they had a `bypassInlineCode{:js}` option that lets you skip inline code highlighting (it was actually added in a really recent update). But what I needed was the opposite, which would be a way to make it only handle inline code and bypass blocks entirely.
|
||||||
|
|
||||||
|
So I opened a feature request at [rehype-pretty/rehype-pretty-code#247](https://github.com/rehype-pretty/rehype-pretty-code/issues/247) for a theoretical `bypassBlockCode{:js}` option. I got no response, since the repository seemed unmaintained for a bit since it seems like the maintainer has moved onto other projects.
|
||||||
|
|
||||||
|
Fast forward a few months, and user [@kelvindecosta](https://github.com/kelvindecosta) comments on my issue:
|
||||||
|
|
||||||
|
> [[@kelvindecosta]](https://github.com/rehype-pretty/rehype-pretty-code/issues/247#issuecomment-2610536000): Hey [@jktrn](https://github.com/jktrn), did you figure out a workaround for this? I'm interested in setting this up alongside expressive-code.
|
||||||
|
|
||||||
|
After I replied that I hadn't figured out a workaround yet, they sent me a brilliantly hacky solution a couple days later:
|
||||||
|
|
||||||
|
> [[@kelvindecosta]](https://github.com/rehype-pretty/rehype-pretty-code/issues/247#issuecomment-2619666231): Hey again @jktrn, I have found an unconventional way to achieve this.
|
||||||
|
>
|
||||||
|
> If you're using pnpm or bun, you can use their patch functionality to customize the contents of the `node_modules/rehype-pretty-code` package.
|
||||||
|
>
|
||||||
|
> I only recently learned about this feature, and it is a good workaround for the time being. Here are the steps:
|
||||||
|
>
|
||||||
|
> 1. Run `pnpm patch rehype-pretty-code`. This will instruct you to edit the files in a certain directory.
|
||||||
|
> 2. Patch out the `isBlockCode{:js}` function to always return `false{:js}`. This will instruct the plugin to not process any block code elements.
|
||||||
|
> 3. Run `pnpm patch-commit <path/to/files>`. This will create a nice patches folder with the right changes.
|
||||||
|
|
||||||
|
## Performing surgery on node_modules
|
||||||
|
|
||||||
|
This happened to be exactly what I needed! I went into my `node_modules` directory and made the changes manually:
|
||||||
|
|
||||||
|
```js title="node_modules > rehype-pretty-code > dist > index.js" startLineNumber=18 ins={9} del={8}
|
||||||
|
function isInlineCode(element, parent, bypass = false) {
|
||||||
|
if (bypass) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return element.tagName === "code" && isElement(parent) && parent.tagName !== "pre" || element.tagName === "inlineCode";
|
||||||
|
}
|
||||||
|
function isBlockCode(element) {
|
||||||
|
return element.tagName === "pre" && Array.isArray(element.children) && element.children.length === 1 && isElement(element.children[0]) && element.children[0].tagName === "code";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
From here, I ran `npx patch-package rehype-pretty-code`, which created a `patches/rehype-pretty-code+0.14.1.patch` file with the changes I made:
|
||||||
|
|
||||||
|
```diff title="patches > rehype-pretty-code+0.14.1.patch"
|
||||||
|
--- a/node_modules/rehype-pretty-code/dist/index.js
|
||||||
|
+++ b/node_modules/rehype-pretty-code/dist/index.js
|
||||||
|
@@ -22,7 +22,7 @@ function isInlineCode(element, parent, bypass = false) {
|
||||||
|
return element.tagName === "code" && isElement(parent) && parent.tagName !== "pre" || element.tagName === "inlineCode";
|
||||||
|
}
|
||||||
|
function isBlockCode(element) {
|
||||||
|
- return element.tagName === "pre" && Array.isArray(element.children) && element.children.length === 1 && isElement(element.children[0]) && element.children[0].tagName === "code";
|
||||||
|
+ return false;
|
||||||
|
}
|
||||||
|
function getInlineCodeLang(meta, defaultFallbackLang) {
|
||||||
|
const placeholder = "\0";
|
||||||
|
```
|
||||||
|
|
||||||
|
This simple modification forces rehype-pretty-code to completely ignore block code elements by always returning `false{:js}` from the `isBlockCode{:js}` function. Now Expressive Code handles all block code formatting, while rehype-pretty-code still beautifully handles my inline code. And just like that, they're working in perfect harmony!
|
||||||
|
|
||||||
|
## Please don't perform surgery on your node_modules
|
||||||
|
|
||||||
|
Absolutely do not do this for production sites (your personal blog does not count = ̄ω ̄=). Directly patching node modules is generally discouraged because patches can break with updates and create maintenance headaches down the road.
|
||||||
|
|
||||||
|
But sometimes, when you're working at the bleeding edge of web development, temporary solutions like this become necessary. The better approach would be to just wait for Expressive Code to implement inline syntax highlighting. But, since it'll take a while for reasons aforementioned, I'll stick with my janky solution. This patch buys me time until either rehype-pretty-code gets maintained again and implements the feature properly, or Expressive Code adds inline code support.
|
||||||
|
|
||||||
|
In the meantime, astro-erudite now has both beautiful code blocks and inline syntax highlighting. And now it's available for all of you to use!
|
|
@ -2,7 +2,7 @@
|
||||||
title: 'The State of Static Blogs in 2024'
|
title: 'The State of Static Blogs in 2024'
|
||||||
description: 'There should not be a single reason why you would need a command palette search bar to find a blog post on your own site.'
|
description: 'There should not be a single reason why you would need a command palette search bar to find a blog post on your own site.'
|
||||||
date: 2024-07-25
|
date: 2024-07-25
|
||||||
tags: ['webdev', 'opinion']
|
tags: ['v1.0.0']
|
||||||
image: './1200x630.png'
|
image: './1200x630.png'
|
||||||
authors: ['enscribe']
|
authors: ['enscribe']
|
||||||
---
|
---
|
||||||
|
@ -123,7 +123,7 @@ This is a non-exhaustive list of features I believe are essential for a friction
|
||||||
|
|
||||||
- The `cn(){:js}` function is a utility function which combines [clsx](https://www.npmjs.com/package/clsx) and [tailwind-merge](https://www.npmjs.com/package/tailwind-merge), two packages which allow painless conditional class addition and concatenation:
|
- The `cn(){:js}` function is a utility function which combines [clsx](https://www.npmjs.com/package/clsx) and [tailwind-merge](https://www.npmjs.com/package/tailwind-merge), two packages which allow painless conditional class addition and concatenation:
|
||||||
|
|
||||||
```tsx title="src/lib/utils.ts" caption="A utility function for class name concatenation" showLineNumbers
|
```tsx title="src > lib > utils.ts" caption="A utility function for class name concatenation" showLineNumbers
|
||||||
import { type ClassValue, clsx } from 'clsx'
|
import { type ClassValue, clsx } from 'clsx'
|
||||||
import { twMerge } from 'tailwind-merge'
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
|
||||||
|
@ -134,7 +134,7 @@ This is a non-exhaustive list of features I believe are essential for a friction
|
||||||
|
|
||||||
This needs to be in every single template. This is an example of it being used in my `<Link>{:html}` component:
|
This needs to be in every single template. This is an example of it being used in my `<Link>{:html}` component:
|
||||||
|
|
||||||
```astro showLineNumbers title="src/components/Link.astro" caption="A custom Link component with tailwind-merge and clsx" {10-15} "cn"
|
```astro showLineNumbers title="src > components > Link.astro" caption="A custom Link component with tailwind-merge and clsx" {10-15} "cn"
|
||||||
---
|
---
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
|
|
@ -17,9 +17,7 @@ import { cn } from '@/lib/utils'
|
||||||
>
|
>
|
||||||
<div class="max-w-md">
|
<div class="max-w-md">
|
||||||
<h1 class="mb-4 text-3xl font-medium">404: Page not found</h1>
|
<h1 class="mb-4 text-3xl font-medium">404: Page not found</h1>
|
||||||
<p class="prose">
|
<p class="prose">Oops! The page you're looking for doesn't exist.</p>
|
||||||
Oops! The page you're looking for doesn't exist.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<Link
|
<Link
|
||||||
href="/"
|
href="/"
|
||||||
|
|
|
@ -152,9 +152,7 @@ const authors = await parseAuthors(post.data.authors ?? [])
|
||||||
|
|
||||||
{headings.length > 0 && <TableOfContents headings={headings} />}
|
{headings.length > 0 && <TableOfContents headings={headings} />}
|
||||||
|
|
||||||
<article
|
<article class="prose col-start-2 max-w-none">
|
||||||
class="prose col-start-2 max-w-none"
|
|
||||||
>
|
|
||||||
<Content />
|
<Content />
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
|
|
|
@ -18,8 +18,10 @@ const blog = (await getCollection('blog'))
|
||||||
<section>
|
<section>
|
||||||
<div class="rounded-lg border">
|
<div class="rounded-lg border">
|
||||||
<div class="flex flex-col space-y-1.5 p-6">
|
<div class="flex flex-col space-y-1.5 p-6">
|
||||||
<h3 class="text-3xl font-medium leading-none">er·u·dite</h3>
|
<h3 class="text-3xl leading-none font-medium">er·u·dite</h3>
|
||||||
<p class="text-sm text-muted-foreground">/ˈer(y)əˌdīt/ • <span class="font-medium">adjective</span></p>
|
<p class="text-muted-foreground text-sm">
|
||||||
|
/ˈer(y)əˌdīt/ • <span class="font-medium">adjective</span>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-6 pt-0">
|
<div class="p-6 pt-0">
|
||||||
<p class="text-muted-foreground mb-2 text-sm">
|
<p class="text-muted-foreground mb-2 text-sm">
|
||||||
|
|
|
@ -47,7 +47,7 @@
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Geist Mono';
|
font-family: 'Geist Mono';
|
||||||
src: url('/fonts/GeistMonoVF.woff2') format('woff2-variations');
|
src: url('/fonts/GeistMonoVF.woff2') format('woff2-variations');
|
||||||
font-weight: 100 500;
|
font-weight: 100 600;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
@apply text-foreground text-base leading-8 [&>*]:first:mt-0 [&>*]:last:mb-0;
|
@apply text-foreground text-base leading-8 [&>*]:first:mt-0 [&>*]:last:mb-0;
|
||||||
|
|
||||||
p {
|
p {
|
||||||
@apply text-foreground/80 my-5 leading-7 [&:not(:first-child)]:mt-5;
|
@apply text-foreground/80 my-5 leading-7 not-first:mt-5;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
|
@ -41,11 +41,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
@apply marker:text-foreground/30 my-5 ml-6 list-disc [&>li]:mt-4;
|
@apply marker:text-foreground/30 my-5 ml-6 list-disc [&>li]:mt-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
ol {
|
ol {
|
||||||
@apply marker:text-foreground/30 my-5 ml-6 list-decimal [&>li]:mt-4;
|
@apply marker:text-foreground/30 my-5 ml-6 list-decimal [&>li]:mt-2;
|
||||||
@apply [&[type='A']]:list-[upper-alpha] [&[type='I']]:list-[upper-roman] [&[type='a']]:list-[lower-alpha] [&[type='i']]:list-[lower-roman];
|
@apply [&[type='A']]:list-[upper-alpha] [&[type='I']]:list-[upper-roman] [&[type='a']]:list-[lower-alpha] [&[type='i']]:list-[lower-roman];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,11 +66,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.expressive-code {
|
.expressive-code {
|
||||||
@apply my-6;
|
@apply my-6 [&_.title]:font-medium!;
|
||||||
}
|
}
|
||||||
|
|
||||||
blockquote {
|
blockquote {
|
||||||
@apply my-6 border-l-2 pl-6 text-sm;
|
@apply [&_*]:text-muted-foreground my-6 border-l-2 pl-6;
|
||||||
}
|
}
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
|
|
Loading…
Add table
Reference in a new issue