2023-06-02 15:33:48 -04:00
|
|
|
'use client'
|
2023-06-11 15:39:04 +04:00
|
|
|
|
2023-06-02 15:33:48 -04:00
|
|
|
import { FC, memo } from 'react'
|
|
|
|
|
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
|
|
|
|
|
import { coldarkDark } from 'react-syntax-highlighter/dist/cjs/styles/prism'
|
2023-05-19 12:33:56 -04:00
|
|
|
|
2023-06-13 23:58:59 +04:00
|
|
|
import { useCopyToClipboard } from '@/lib/hooks/use-copy-to-clipboard'
|
2023-06-13 16:02:07 +04:00
|
|
|
import { IconCheck, IconCopy, IconDownload } from '@/components/ui/icons'
|
2023-06-11 15:39:04 +04:00
|
|
|
|
2023-05-19 12:33:56 -04:00
|
|
|
interface Props {
|
2023-06-02 15:33:48 -04:00
|
|
|
language: string
|
|
|
|
|
value: string
|
2023-05-19 12:33:56 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface languageMap {
|
2023-06-02 15:33:48 -04:00
|
|
|
[key: string]: string | undefined
|
2023-05-19 12:33:56 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const programmingLanguages: languageMap = {
|
2023-06-02 15:33:48 -04:00
|
|
|
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'
|
2023-05-19 12:33:56 -04:00
|
|
|
// add more file extensions here, make sure the key is same as language prop in CodeBlock.tsx component
|
2023-06-02 15:33:48 -04:00
|
|
|
}
|
2023-05-19 12:33:56 -04:00
|
|
|
|
|
|
|
|
export const generateRandomString = (length: number, lowercase = false) => {
|
2023-06-02 15:33:48 -04:00
|
|
|
const chars = 'ABCDEFGHJKLMNPQRSTUVWXY3456789' // excluding similar looking characters like Z, 2, I, 1, O, 0
|
|
|
|
|
let result = ''
|
2023-05-19 12:33:56 -04:00
|
|
|
for (let i = 0; i < length; i++) {
|
2023-06-02 15:33:48 -04:00
|
|
|
result += chars.charAt(Math.floor(Math.random() * chars.length))
|
2023-05-19 12:33:56 -04:00
|
|
|
}
|
2023-06-02 15:33:48 -04:00
|
|
|
return lowercase ? result.toLowerCase() : result
|
|
|
|
|
}
|
2023-05-19 12:33:56 -04:00
|
|
|
const CodeBlock: FC<Props> = memo(({ language, value }) => {
|
2023-06-02 15:33:48 -04:00
|
|
|
const { isCopied, copyToClipboard } = useCopyToClipboard({ timeout: 2000 })
|
2023-05-19 12:33:56 -04:00
|
|
|
const downloadAsFile = () => {
|
2023-06-02 15:33:48 -04:00
|
|
|
if (typeof window === 'undefined') {
|
|
|
|
|
return
|
2023-05-19 12:33:56 -04:00
|
|
|
}
|
2023-06-02 15:33:48 -04:00
|
|
|
const fileExtension = programmingLanguages[language] || '.file'
|
2023-05-19 12:33:56 -04:00
|
|
|
const suggestedFileName = `file-${generateRandomString(
|
|
|
|
|
3,
|
|
|
|
|
true
|
2023-06-02 15:33:48 -04:00
|
|
|
)}${fileExtension}`
|
|
|
|
|
const fileName = window.prompt('Enter file name' || '', suggestedFileName)
|
2023-05-19 12:33:56 -04:00
|
|
|
|
|
|
|
|
if (!fileName) {
|
|
|
|
|
// user pressed cancel on prompt
|
2023-06-02 15:33:48 -04:00
|
|
|
return
|
2023-05-19 12:33:56 -04:00
|
|
|
}
|
|
|
|
|
|
2023-06-02 15:33:48 -04:00
|
|
|
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)
|
|
|
|
|
}
|
2023-05-19 12:33:56 -04:00
|
|
|
return (
|
|
|
|
|
<div className="codeblock relative w-full font-sans text-[16px]">
|
|
|
|
|
<div className="flex w-full items-center justify-between px-4 py-1.5">
|
|
|
|
|
<span className="text-xs lowercase text-white">{language}</span>
|
|
|
|
|
|
|
|
|
|
<div className="flex items-center">
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
className="flex items-center gap-1.5 rounded bg-none p-1 text-xs text-white"
|
|
|
|
|
onClick={() => copyToClipboard(value)}
|
|
|
|
|
>
|
2023-06-13 16:02:07 +04:00
|
|
|
{isCopied ? <IconCheck /> : <IconCopy />}
|
2023-06-02 15:33:48 -04:00
|
|
|
{isCopied ? 'Copied!' : 'Copy code'}
|
2023-05-19 12:33:56 -04:00
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
className="flex items-center rounded bg-none p-1 text-xs text-white"
|
|
|
|
|
onClick={downloadAsFile}
|
|
|
|
|
>
|
2023-06-13 16:02:07 +04:00
|
|
|
<IconDownload />
|
2023-05-19 12:33:56 -04:00
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<SyntaxHighlighter
|
|
|
|
|
language={language}
|
|
|
|
|
style={coldarkDark}
|
|
|
|
|
customStyle={{
|
|
|
|
|
margin: 0,
|
2023-06-02 15:33:48 -04:00
|
|
|
width: '100%',
|
|
|
|
|
background: 'transparent'
|
2023-05-19 12:33:56 -04:00
|
|
|
}}
|
|
|
|
|
codeTagProps={{
|
|
|
|
|
style: {
|
2023-06-02 15:33:48 -04:00
|
|
|
fontSize: '0.9rem',
|
|
|
|
|
fontFamily: 'var(--font-mono)'
|
|
|
|
|
}
|
2023-05-19 12:33:56 -04:00
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{value}
|
|
|
|
|
</SyntaxHighlighter>
|
|
|
|
|
</div>
|
2023-06-02 15:33:48 -04:00
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
CodeBlock.displayName = 'CodeBlock'
|
2023-05-19 12:33:56 -04:00
|
|
|
|
2023-06-11 15:08:59 +04:00
|
|
|
export { CodeBlock }
|