Spaces:
Sleeping
Sleeping
| // Inspired by Chatbot-UI and modified to fit the needs of this project | |
| // @see https://github.com/mckaywrigley/chatbot-ui/blob/main/components/Markdown/CodeBlock.tsx | |
| '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/useCopyToClipboard'; | |
| 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', | |
| vim: '.txt', | |
| 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', | |
| // custom titles | |
| print: '.txt', | |
| error: '.txt', | |
| // add more file extensions here, make sure the key is same as language prop in CodeBlock.tsx component | |
| }; | |
| const customSyntax: languageMap = { | |
| print: 'vim', | |
| output: 'vim', | |
| error: 'vim', | |
| }; | |
| export const generateRandomString = (length: number, lowercase = false) => { | |
| const chars = 'ABCDEFGHJKLMNPQRSTUVWXY3456789'; // excluding similar looking characters like Z, 2, I, 1, O, 0 | |
| 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) { | |
| // User pressed cancel on prompt. | |
| 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="relative w-full codeblock bg-zinc-900 overflow-hidden"> | |
| <div className="flex items-center justify-between w-full px-4 pt-2 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={customSyntax[language] ?? language} | |
| style={coldarkDark} | |
| PreTag="div" | |
| showLineNumbers | |
| customStyle={{ | |
| margin: 0, | |
| width: '100%', | |
| background: 'transparent', | |
| padding: '0.5rem 1rem 1rem 1rem', | |
| }} | |
| lineNumberStyle={{ | |
| userSelect: 'none', | |
| }} | |
| codeTagProps={{ | |
| style: { | |
| fontSize: '0.9rem', | |
| fontFamily: 'var(--font-geist-mono)', | |
| }, | |
| }} | |
| > | |
| {value} | |
| </SyntaxHighlighter> | |
| </div> | |
| ); | |
| }); | |
| CodeBlock.displayName = 'CodeBlock'; | |
| export { CodeBlock }; | |