| 'use client' |
|
|
| import { FC, memo } from 'react' |
| import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter' |
| import { coldarkDark } from 'react-syntax-highlighter/dist/cjs/styles/prism' |
|
|
| import { useCopyToClipboard } from '@/lib/hooks/use-copy-to-clipboard' |
| import { IconCheck, IconCopy, IconDownload } from '@/components/ui/icons' |
| import { Button } from '@/components/ui/button' |
|
|
| interface Props { |
| language: string |
| value: string |
| } |
|
|
| interface languageMap { |
| [key: string]: string | undefined |
| } |
|
|
| export const programmingLanguages: languageMap = { |
| javascript: '.js', |
| python: '.py', |
| java: '.java', |
| c: '.c', |
| cpp: '.cpp', |
| 'c++': '.cpp', |
| 'c#': '.cs', |
| ruby: '.rb', |
| php: '.php', |
| swift: '.swift', |
| 'objective-c': '.m', |
| kotlin: '.kt', |
| typescript: '.ts', |
| go: '.go', |
| perl: '.pl', |
| rust: '.rs', |
| scala: '.scala', |
| haskell: '.hs', |
| lua: '.lua', |
| shell: '.sh', |
| sql: '.sql', |
| html: '.html', |
| css: '.css' |
| |
| } |
|
|
| export const generateRandomString = (length: number, lowercase = false) => { |
| const chars = 'ABCDEFGHJKLMNPQRSTUVWXY3456789' |
| let result = '' |
| for (let i = 0; i < length; i++) { |
| result += chars.charAt(Math.floor(Math.random() * chars.length)) |
| } |
| return lowercase ? result.toLowerCase() : result |
| } |
|
|
| const CodeBlock: FC<Props> = memo(({ language, value }) => { |
| const { isCopied, copyToClipboard } = useCopyToClipboard({ timeout: 2000 }) |
|
|
| const downloadAsFile = () => { |
| if (typeof window === 'undefined') { |
| return |
| } |
| const fileExtension = programmingLanguages[language] || '.file' |
| const suggestedFileName = `file-${generateRandomString( |
| 3, |
| true |
| )}${fileExtension}` |
| const fileName = window.prompt('Enter file name' || '', suggestedFileName) |
|
|
| if (!fileName) { |
| |
| return |
| } |
|
|
| const blob = new Blob([value], { type: 'text/plain' }) |
| const url = URL.createObjectURL(blob) |
| const link = document.createElement('a') |
| link.download = fileName |
| link.href = url |
| link.style.display = 'none' |
| document.body.appendChild(link) |
| link.click() |
| document.body.removeChild(link) |
| URL.revokeObjectURL(url) |
| } |
|
|
| const onCopy = () => { |
| if (isCopied) return |
| copyToClipboard(value) |
| } |
|
|
| return ( |
| <div className="codeblock relative w-full bg-zinc-950 font-sans"> |
| <div className="flex w-full items-center justify-between bg-zinc-800 px-6 py-2 pr-4 text-zinc-100"> |
| <span className="text-xs lowercase">{language}</span> |
| <div className="flex items-center space-x-1"> |
| <Button |
| variant="ghost" |
| className="hover:bg-zinc-800 focus-visible:ring-1 focus-visible:ring-slate-700 focus-visible:ring-offset-0" |
| onClick={downloadAsFile} |
| size="icon" |
| > |
| <IconDownload /> |
| <span className="sr-only">Download</span> |
| </Button> |
| <Button |
| variant="ghost" |
| size="icon" |
| className="text-xs hover:bg-zinc-800 focus-visible:ring-1 focus-visible:ring-slate-700 focus-visible:ring-offset-0" |
| onClick={onCopy} |
| > |
| {isCopied ? <IconCheck /> : <IconCopy />} |
| <span className="sr-only">Copy code</span> |
| </Button> |
| </div> |
| </div> |
| <SyntaxHighlighter |
| language={language} |
| style={coldarkDark} |
| PreTag="div" |
| showLineNumbers |
| customStyle={{ |
| margin: 0, |
| width: '100%', |
| background: 'transparent', |
| padding: '1.5rem 1rem' |
| }} |
| codeTagProps={{ |
| style: { |
| fontSize: '0.9rem', |
| fontFamily: 'var(--font-mono)' |
| } |
| }} |
| > |
| {value} |
| </SyntaxHighlighter> |
| </div> |
| ) |
| }) |
| CodeBlock.displayName = 'CodeBlock' |
|
|
| export { CodeBlock } |
|
|