import { FC, memo, useEffect, useState } from "react"; import { Prism, Light } from "react-syntax-highlighter"; import { useCopyToClipboard } from "../../hooks/use-copy-to-clipboard"; import { CheckIcon, CopyIcon, DownloadIcon } from "./Icons"; interface CodeActionButtonProps { onClick: () => void; children: React.ReactNode; } 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", // add more file extensions here, make sure the key is same as language prop in CodeBlock.tsx component }; 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 = memo(({ language, value }) => { const { isCopied, copyToClipboard } = useCopyToClipboard({ timeout: 2000 }); const [SyntaxHighlighter, setSyntaxHighlighter] = useState< typeof Light | typeof Prism >(() => Light); useEffect(() => { try { new RegExp("(?<=#)\\w+"); setSyntaxHighlighter(() => Prism); } catch { setSyntaxHighlighter(() => Light); } }, []); 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 (
{language}
{value}
); }); CodeBlock.displayName = "CodeBlock"; export { CodeBlock }; // import { vscDarkPlus as highlightStyle } from "react-syntax-highlighter/dist/esm/styles/prism"; // As a workaround, we inline the vscDarkPlus from react-syntax-highlighter. // Importing it as recommended in the documentation leads to build errors in the non app router // (Next.js classic) setup. const highlightStyle: any = { 'pre[class*="language-"]': { color: "#d4d4d4", fontSize: "13px", textShadow: "none", fontFamily: 'Menlo, Monaco, Consolas, "Andale Mono", "Ubuntu Mono", "Courier New", monospace', direction: "ltr", textAlign: "left", whiteSpace: "pre", wordSpacing: "normal", wordBreak: "normal", lineHeight: "1.5", MozTabSize: "4", OTabSize: "4", tabSize: "4", WebkitHyphens: "none", MozHyphens: "none", msHyphens: "none", hyphens: "none", padding: "1em", margin: ".5em 0", overflow: "auto", background: "#1e1e1e", }, 'code[class*="language-"]': { color: "#d4d4d4", fontSize: "13px", textShadow: "none", fontFamily: 'Menlo, Monaco, Consolas, "Andale Mono", "Ubuntu Mono", "Courier New", monospace', direction: "ltr", textAlign: "left", whiteSpace: "pre", wordSpacing: "normal", wordBreak: "normal", lineHeight: "1.5", MozTabSize: "4", OTabSize: "4", tabSize: "4", WebkitHyphens: "none", MozHyphens: "none", msHyphens: "none", hyphens: "none", }, 'pre[class*="language-"]::selection': { textShadow: "none", background: "#264F78", }, 'code[class*="language-"]::selection': { textShadow: "none", background: "#264F78", }, 'pre[class*="language-"] *::selection': { textShadow: "none", background: "#264F78", }, 'code[class*="language-"] *::selection': { textShadow: "none", background: "#264F78", }, ':not(pre) > code[class*="language-"]': { padding: ".1em .3em", borderRadius: ".3em", color: "#db4c69", background: "#1e1e1e", }, ".namespace": { Opacity: ".7", }, "doctype.doctype-tag": { color: "#569CD6", }, "doctype.name": { color: "#9cdcfe", }, comment: { color: "#6a9955", }, prolog: { color: "#6a9955", }, punctuation: { color: "#d4d4d4", }, ".language-html .language-css .token.punctuation": { color: "#d4d4d4", }, ".language-html .language-javascript .token.punctuation": { color: "#d4d4d4", }, property: { color: "#9cdcfe", }, tag: { color: "#569cd6", }, boolean: { color: "#569cd6", }, number: { color: "#b5cea8", }, constant: { color: "#9cdcfe", }, symbol: { color: "#b5cea8", }, inserted: { color: "#b5cea8", }, unit: { color: "#b5cea8", }, selector: { color: "#d7ba7d", }, "attr-name": { color: "#9cdcfe", }, string: { color: "#ce9178", }, char: { color: "#ce9178", }, builtin: { color: "#ce9178", }, deleted: { color: "#ce9178", }, ".language-css .token.string.url": { textDecoration: "underline", }, operator: { color: "#d4d4d4", }, entity: { color: "#569cd6", }, "operator.arrow": { color: "#569CD6", }, atrule: { color: "#ce9178", }, "atrule.rule": { color: "#c586c0", }, "atrule.url": { color: "#9cdcfe", }, "atrule.url.function": { color: "#dcdcaa", }, "atrule.url.punctuation": { color: "#d4d4d4", }, keyword: { color: "#569CD6", }, "keyword.module": { color: "#c586c0", }, "keyword.control-flow": { color: "#c586c0", }, function: { color: "#dcdcaa", }, "function.maybe-class-name": { color: "#dcdcaa", }, regex: { color: "#d16969", }, important: { color: "#569cd6", }, italic: { fontStyle: "italic", }, "class-name": { color: "#4ec9b0", }, "maybe-class-name": { color: "#4ec9b0", }, console: { color: "#9cdcfe", }, parameter: { color: "#9cdcfe", }, interpolation: { color: "#9cdcfe", }, "punctuation.interpolation-punctuation": { color: "#569cd6", }, variable: { color: "#9cdcfe", }, "imports.maybe-class-name": { color: "#9cdcfe", }, "exports.maybe-class-name": { color: "#9cdcfe", }, escape: { color: "#d7ba7d", }, "tag.punctuation": { color: "#808080", }, cdata: { color: "#808080", }, "attr-value": { color: "#ce9178", }, "attr-value.punctuation": { color: "#ce9178", }, "attr-value.punctuation.attr-equals": { color: "#d4d4d4", }, namespace: { color: "#4ec9b0", }, 'pre[class*="language-javascript"]': { color: "#9cdcfe", }, 'code[class*="language-javascript"]': { color: "#9cdcfe", }, 'pre[class*="language-jsx"]': { color: "#9cdcfe", }, 'code[class*="language-jsx"]': { color: "#9cdcfe", }, 'pre[class*="language-typescript"]': { color: "#9cdcfe", }, 'code[class*="language-typescript"]': { color: "#9cdcfe", }, 'pre[class*="language-tsx"]': { color: "#9cdcfe", }, 'code[class*="language-tsx"]': { color: "#9cdcfe", }, 'pre[class*="language-css"]': { color: "#ce9178", }, 'code[class*="language-css"]': { color: "#ce9178", }, 'pre[class*="language-html"]': { color: "#d4d4d4", }, 'code[class*="language-html"]': { color: "#d4d4d4", }, ".language-regex .token.anchor": { color: "#dcdcaa", }, ".language-html .token.punctuation": { color: "#808080", }, 'pre[class*="language-"] > code[class*="language-"]': { position: "relative", zIndex: "1", }, ".line-highlight.line-highlight": { background: "#f7ebc6", boxShadow: "inset 5px 0 0 #f7d87c", zIndex: "0", }, };