Skip to content

Commit

Permalink
support CodeGroup component
Browse files Browse the repository at this point in the history
  • Loading branch information
zhangyu1818 committed Jun 2, 2024
1 parent 18b2c95 commit 8b0ab5a
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 71 deletions.
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
},
"dependencies": {
"@discublog/api": "latest",
"@formkit/auto-animate": "^0.8.2",
"@giscus/react": "^3.0.0",
"@tabler/icons-react": "^3.5.0",
"clsx": "^2.1.1",
Expand Down
8 changes: 0 additions & 8 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/app/(article)/posts/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export default async function Page({ params }: PageProps) {
</span>
</div>
</header>
<article className='prose prose-slate max-w-none dark:prose-invert has-[img]:prose-p:-mx-4 prose-code:break-words prose-pre:-mx-4 prose-pre:px-5 dark:prose-img:brightness-75 max-xl:col-start-2 max-sm:prose-pre:rounded-none sm:prose-img:rounded md:has-[img]:prose-p:-mx-8 md:prose-pre:-mx-8'>
<article className='prose prose-slate max-w-none dark:prose-invert has-[img]:prose-p:-mx-4 prose-code:break-words prose-pre:-mx-4 prose-pre:px-5 dark:prose-img:brightness-75 max-xl:col-start-2 max-sm:prose-pre:rounded-none sm:prose-img:rounded md:has-[img]:prose-p:-mx-8 md:prose-pre:-mx-8 [&:not(.code-group)]:prose-pre:my-0'>
<Markdown
source={body!}
useMDXComponents={() => ({
Expand Down
49 changes: 46 additions & 3 deletions src/markdown/components/alert.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,56 @@
import {
IconAlertTriangle,
IconBulb,
IconInfoSquare,
IconMessageReport,
IconUrgent,
} from '@tabler/icons-react'
import clsx from 'clsx'

interface AlertProps {
type: 'note' | 'tip' | 'important' | 'warning ' | 'caution'
children: React.ReactNode
}

const icons = {
note: IconInfoSquare,
tip: IconBulb,
important: IconMessageReport,
warning: IconAlertTriangle,
caution: IconUrgent,
}

export const Alert = (props: AlertProps) => {
const { type, children } = props

const Icon = icons[type]

const textClses = {
note: 'text-blue-500',
tip: 'text-green-600',
important: 'text-purple-600',
warning: 'text-yellow-600',
caution: 'text-red-500',
}

const borderClses = {
note: 'border-blue-500',
tip: 'border-green-600',
important: 'border-purple-600',
warning: 'border-yellow-600',
caution: 'border-red-500',
}

const textCls = textClses[type]
const borderCls = borderClses[type]

return (
<blockquote>
<p className='first-letter:uppercase'>{type}</p>
<div className={clsx('not-prose mb-4 border-l-4 pl-4', borderCls)}>
<p className={clsx('flex items-center gap-2 py-2 font-bold', textCls)}>
<Icon className='size-5' />
<span className='block first-letter:uppercase'>{type}</span>
</p>
{children}
</blockquote>
</div>
)
}
50 changes: 28 additions & 22 deletions src/markdown/components/code-group.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,49 @@
'use client'

import React, { useState } from 'react'
import { useState, Children, isValidElement } from 'react'

import { useAutoAnimate } from '@formkit/auto-animate/react'
import clsx from 'clsx'

interface CodeGroupProps {
'data-children-meta': string
children: React.ReactNode
}
const CodeGroup = (props: CodeGroupProps) => {
const { children } = props
const childrenArray = React.Children.toArray(children)
const metas = JSON.parse(props['data-children-meta']) as string[]
const tabs = metas.map(meta => {
const matches = meta.match(/\[(.+)]/)
if (!matches) {
throw new Error('[markdown:CodeGroup] Meta format error')
const [index, setIndex] = useState(0)

const childrenArray = Children.toArray(children)
const fileNames = childrenArray.map(child => {
if (isValidElement(child) && isValidElement(child.props.children)) {
return child.props.children.props['data-file']
}
return matches[1]
})
if (tabs.length !== childrenArray.length) {
throw new Error('[markdown:CodeGroup] Meta and children length mismatch')
}

const [index, setIndex] = useState(0)
const [ref] = useAutoAnimate(/* optional config */)
if (fileNames.length !== childrenArray.length) {
return children
}

const current = childrenArray[index]

return (
<div>
<div>
{tabs.map((tab, index) => (
<span key={tab} role='button' onClick={() => setIndex(index)}>
{tab}
</span>
<div className='code-group -mx-4 border bg-surface-1 md:-mx-8 md:rounded'>
<header className='flex border-b px-4'>
{fileNames.map((fileName, i) => (
<button
className={clsx(
'flex items-center justify-center border-b-2 px-4 py-2',
i === index
? 'border-brand text-brand'
: 'border-transparent text-color-3',
)}
key={i}
onClick={() => setIndex(i)}
>
{fileName}
</button>
))}
</div>
<div ref={ref}>{current}</div>
</header>
<div className='px-4 md:px-8'>{current}</div>
</div>
)
}
Expand Down
13 changes: 11 additions & 2 deletions src/markdown/markdown.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import path from 'node:path'

import rehypeShiki, { type RehypeShikiOptions } from '@shikijs/rehype'
import {
transformerNotationDiff,
Expand All @@ -16,7 +18,7 @@ import remarkDirective from 'remark-directive'
import remarkGfm from 'remark-gfm'
import { MDX, type MDXProps } from 'rsc-mdx'

import { remarkDirectiveContainer, rehypeGithubAlert } from './plugins'
import { rehypeGithubAlert } from './plugins'
import { rendererMdx } from './twoslash/renderMdx'

interface MarkdownProps {
Expand All @@ -30,7 +32,7 @@ export async function Markdown(props: MarkdownProps) {
<MDX
source={source}
useMDXComponents={useMDXComponents}
remarkPlugins={[remarkDirective, remarkDirectiveContainer, remarkGfm]}
remarkPlugins={[remarkDirective, remarkGfm]}
rehypePlugins={[
rehypeGithubAlert,
rehypeSlug,
Expand All @@ -44,6 +46,13 @@ export async function Markdown(props: MarkdownProps) {
[
rehypeShiki,
{
parseMetaString: meta => {
const metaData = meta.split(' ')
const fileName = metaData.find(item => path.extname(item) !== '')
return {
'data-file': fileName,
}
},
themes: {
light: 'github-light',
dark: 'dracula-soft',
Expand Down
34 changes: 0 additions & 34 deletions src/markdown/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,7 @@ import { isElement } from 'hast-util-is-element'
import { visit } from 'unist-util-visit'

import type { Text } from 'hast'
import type { Directives } from 'mdast-util-directive'
import type { Plugin } from 'unified'
import type { Node } from 'unist'

const isDirectiveNode = (node: Node): node is Directives => {
const { type } = node
return (
type === 'textDirective' ||
type === 'leafDirective' ||
type === 'containerDirective'
)
}

export const remarkDirectiveContainer: Plugin = () => tree =>
visit(tree, node => {
if (isDirectiveNode(node)) {
if (node.name === 'code-group') {
const childrenMeta = node.children.map(child => child.meta)
node.data = {
hName: 'CodeGroup',
hProperties: {
...node.attributes,
'data-children-meta': JSON.stringify(childrenMeta),
},
}
} else if (node.name === 'details') {
node.data = {
hName: 'Details',
hProperties: {
...node.attributes,
},
}
}
}
})

export const rehypeGithubAlert: Plugin = () => tree =>
visit(tree, node => {
Expand Down

0 comments on commit 8b0ab5a

Please sign in to comment.