Skip to content

Commit

Permalink
feat: shiki code highlighter
Browse files Browse the repository at this point in the history
  • Loading branch information
wkylin committed Dec 30, 2024
1 parent 39c5817 commit 8fe534c
Show file tree
Hide file tree
Showing 6 changed files with 271 additions and 1 deletion.
131 changes: 131 additions & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@
"resize-observer-polyfill": "^1.5.1",
"sanitize.css": "^13.0.0",
"screenfull": "^6.0.2",
"shiki": "^1.24.4",
"sse.js": "^2.5.0",
"styled-components": "^6.1.13",
"three": "^0.171.0",
Expand Down
50 changes: 50 additions & 0 deletions src/components/stateless/AdvancedCodeBlock/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from 'react'

Check failure on line 1 in src/components/stateless/AdvancedCodeBlock/index.tsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

ESLint

ESLint: Install the 'eslint' package
import type { HTMLAttributes } from 'react'
import type { BundledLanguage, BundledTheme } from 'shiki'
import ShikiCode from '@stateless/CodeHighlighter'
import CopyToClipboard from '@stateless/CopyToClipboard'
import clsx from 'clsx'

type AdvancedBlockProps = {
code: string
fileName?: string
lang?: BundledLanguage
theme?: BundledTheme
className?: string
}

export const AdvancedCodeBlock = ({
code,
fileName,
lang = 'typescript',
theme = 'github-light',
className,
...props
}: AdvancedBlockProps & HTMLAttributes<HTMLDivElement>) => {
return (
<figure
className={clsx(
'-mt-1 relative flex w-full flex-col flex-wrap rounded-2xl bg-neutral-200/70 text-white leading-6 dark:bg-[rgb(33,33,38)]',
className
)}
{...props}
>
<div className="flex items-center justify-between px-5 py-3">
<figcaption className="mr-[-48px] max-w-full whitespace-nowrap font-medium text-neutral-600 text-xs dark:text-neutral-200">
<span>{fileName ?? <br />}</span>
</figcaption>
<CopyToClipboard code={code} />
</div>
<div className="w-full px-1 pb-1">
<div className="relative overflow-hidden border-t shadow-sm isolate rounded-xl border-white/80 shadow-black/5 dark:border-white/15 dark:shadow-black/10">
<pre
className="py-4 overflow-x-auto overflow-y-auto text-sm leading-6 text-white bg-white/50 dark:bg-zinc-700/50"
style={{ paddingRight: '10px' }}
>
<ShikiCode code={code} lang={lang} theme={theme} />
</pre>
</div>
</div>
</figure>
)
}
38 changes: 38 additions & 0 deletions src/components/stateless/CodeHighlighter/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React, { useState, useEffect } from 'react'

Check failure on line 1 in src/components/stateless/CodeHighlighter/index.tsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

ESLint

ESLint: Install the 'eslint' package
import { type BundledLanguage, type BundledTheme, codeToHtml } from 'shiki'
import clsx from 'clsx'
import type { HTMLAttributes } from 'react'

type ShikiProps = {
code: string
lang: BundledLanguage
theme: BundledTheme
className?: string
}

export default function ShikiCode({
code,
lang,
theme,
className,
...props
}: Readonly<ShikiProps> & HTMLAttributes<HTMLDivElement>) {
const [html, setHtml] = useState<string>('')

useEffect(() => {
const loadHtml = async () => {
const result = await codeToHtml(code, { lang, theme })
setHtml(result)
}

loadHtml()

Check notice on line 28 in src/components/stateless/CodeHighlighter/index.tsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

Result of method call returning a promise is ignored

Promise returned from loadHtml is ignored
}, [code, lang, theme])

return (
<div
className={clsx('text-sm hue-rotate-0 invert-0 *:bg-transparent dark:hue-rotate-180 dark:invert', className)}
{...props}
dangerouslySetInnerHTML={{ __html: html }}
/>
)
}
37 changes: 37 additions & 0 deletions src/components/stateless/CopyToClipboard/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'use client'

Check failure on line 1 in src/components/stateless/CopyToClipboard/index.tsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

ESLint

ESLint: Install the 'eslint' package
import React from 'react'
import { CheckIcon, CopyIcon } from 'lucide-react'
import { useState } from 'react'

const CopyToClipboard = ({ code }: Readonly<{ code: string }>) => {
const [isCopied, setIsCopied] = useState(false)
const copyToClipboard = async () => {
try {
await navigator.clipboard.writeText(code)
setIsCopied(true)
} catch (_error) {
setIsCopied(false)
} finally {
setTimeout(() => {
setIsCopied(false)
}, 2000)
}
}

return (
<button
aria-label="Copy code to clipboard"
className="relative flex text-base font-normal leading-6 transition-colors duration-150 ease-in-out bg-transparent cursor-pointer size-4 text-zinc-500 dark:text-zinc-400"
onClick={copyToClipboard}
type="button"
>
{isCopied ? (
<CheckIcon className="block align-middle size-4" />
) : (
<CopyIcon className="block align-middle size-4" />
)}
</button>
)
}

export default CopyToClipboard
15 changes: 14 additions & 1 deletion src/pages/home/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,23 @@ import DynamicBackground from '@stateless/DynamicBackground'
import ContentPlaceholder from '@stateless/ContentPlaceholder'
import SkeletonFix from '@stateless/SkeletonFix'
import { ReactSignature } from '@stateless/ReactSignature'
// import AdvancedCodeBlock from '@stateless/AdvancedCodeBlock'
import { oneApiChat, prettyObject, randomNum } from '@utils/aidFn'

Check warning on line 41 in src/pages/home/index.jsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

Unused import

Unused import specifier randomNum
import { fireConfetti } from '@utils/confetti'

import styles from './index.module.less'

const boxCount = Array.apply(null, Array(10))

Check warning on line 46 in src/pages/home/index.jsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

Unused local symbol

Unused constant boxCount

const code = {

Check warning on line 48 in src/pages/home/index.jsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

Unused local symbol

Unused constant code
fileName: './explanations.ts',
code: `export const = explanations = {
main : "This component needs more than the default code block to be displayed"
detailed : "For now, if you want the exact same behaviour, please check the github"
}`,
lang: 'typescript',
}

const preCode = `
const GroceryItem: React.FC<GroceryItemProps> = ({ item }) => {
return (
Expand Down Expand Up @@ -399,7 +409,10 @@ const Home = () => {
<section style={{ position: 'relative' }}>
<AnimateWave />
</section>
<section style={{ position: 'relative' }}>
{/* <section style={{ margin: 20 }}>
<AdvancedCodeBlock code={code.code} fileName={code.fileName} lang={code.lang} />
</section> */}
<section style={{ margin: 20 }}>
<ReactSignature />
</section>
<section style={{ margin: 20 }}>
Expand Down

0 comments on commit 8fe534c

Please sign in to comment.