mirror of
https://github.com/sqlchat/sqlchat.git
synced 2025-07-28 17:53:21 +08:00
feat: implement markdown codeblock
This commit is contained in:
@ -1,7 +1,9 @@
|
||||
import { marked } from "marked";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import { useUserStore } from "@/store";
|
||||
import { Message } from "@/types";
|
||||
import Icon from "../Icon";
|
||||
import { CodeBlock } from "../CodeBlock";
|
||||
|
||||
interface Props {
|
||||
message: Message;
|
||||
@ -28,10 +30,32 @@ const MessageView = (props: Props) => {
|
||||
<div className="w-10 h-10 p-1 border rounded-full flex justify-center items-center mr-2 shrink-0">
|
||||
<Icon.AiOutlineRobot className="w-6 h-6" />
|
||||
</div>
|
||||
<div
|
||||
<ReactMarkdown
|
||||
className="mt-0.5 w-auto max-w-full bg-gray-100 px-4 py-2 rounded-lg rounded-tl-none shadow prose prose-neutral"
|
||||
dangerouslySetInnerHTML={{ __html: marked.parse(message.content) }}
|
||||
></div>
|
||||
remarkPlugins={[remarkGfm]}
|
||||
components={{
|
||||
pre({ node, className, children, ...props }) {
|
||||
return (
|
||||
<pre className={`${className || ""} p-0 w-full`} {...props}>
|
||||
{children}
|
||||
</pre>
|
||||
);
|
||||
},
|
||||
code({ node, inline, className, children, ...props }) {
|
||||
const match = /language-(\w+)/.exec(className || "");
|
||||
const language = match ? match[1] : "plain";
|
||||
return !inline ? (
|
||||
<CodeBlock key={Math.random()} language={language || "plain"} value={String(children).replace(/\n$/, "")} {...props} />
|
||||
) : (
|
||||
<code className={className} {...props}>
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
},
|
||||
}}
|
||||
>
|
||||
{message.content}
|
||||
</ReactMarkdown>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
@ -10,10 +10,11 @@ const ClearDataConfirmModal = (props: Props) => {
|
||||
|
||||
const handleClearData = () => {
|
||||
window.localStorage.clear();
|
||||
close();
|
||||
toast.success("Message cleared. The page will be reloaded.");
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 300);
|
||||
}, 500);
|
||||
};
|
||||
|
||||
return (
|
||||
|
45
components/CodeBlock.tsx
Normal file
45
components/CodeBlock.tsx
Normal file
@ -0,0 +1,45 @@
|
||||
import { useState } from "react";
|
||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||
import { oneDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
|
||||
|
||||
interface Props {
|
||||
language: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export const CodeBlock = (props: Props) => {
|
||||
const { language, value } = props;
|
||||
const [isCopied, setIsCopied] = useState<Boolean>(false);
|
||||
|
||||
const copyToClipboard = () => {
|
||||
if (!navigator.clipboard || !navigator.clipboard.writeText) {
|
||||
return;
|
||||
}
|
||||
|
||||
navigator.clipboard.writeText(value).then(() => {
|
||||
setIsCopied(true);
|
||||
setTimeout(() => {
|
||||
setIsCopied(false);
|
||||
}, 2000);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full relative font-sans text-[16px]">
|
||||
<div className="flex items-center justify-between py-1.5 px-4">
|
||||
<span className="text-xs text-white font-mono">{language}</span>
|
||||
<div className="flex items-center">
|
||||
<button
|
||||
className="flex items-center rounded bg-none py-0.5 px-2 text-xs text-white bg-gray-600 hover:opacity-80"
|
||||
onClick={copyToClipboard}
|
||||
>
|
||||
{isCopied ? "Copied!" : "Copy"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<SyntaxHighlighter language={language} style={oneDark} customStyle={{ margin: 0 }}>
|
||||
{value}
|
||||
</SyntaxHighlighter>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -15,24 +15,25 @@
|
||||
"eventsource-parser": "^1.0.0",
|
||||
"highlight.js": "^11.7.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"marked": "^4.2.12",
|
||||
"next": "^13.2.4",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hot-toast": "^2.4.0",
|
||||
"react-icons": "^4.8.0",
|
||||
"react-markdown": "^8.0.6",
|
||||
"react-textarea-autosize": "^8.4.0",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"uuid": "^9.0.0",
|
||||
"zustand": "^4.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
"@types/lodash-es": "^4.17.7",
|
||||
"@types/marked": "^4.0.8",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/pg": "^8.6.6",
|
||||
"@types/react": "^18.0.26",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@types/react-syntax-highlighter": "^15.5.6",
|
||||
"@types/uuid": "^9.0.1",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"eslint": "8.20.0",
|
||||
@ -40,6 +41,7 @@
|
||||
"mysql2": "^3.2.0",
|
||||
"pg": "^8.10.0",
|
||||
"postcss": "^8.4.20",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"tailwindcss": "^3.2.4",
|
||||
"typescript": "^4.9.4"
|
||||
}
|
||||
|
798
pnpm-lock.yaml
generated
798
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user