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 { useUserStore } from "@/store";
|
||||||
import { Message } from "@/types";
|
import { Message } from "@/types";
|
||||||
import Icon from "../Icon";
|
import Icon from "../Icon";
|
||||||
|
import { CodeBlock } from "../CodeBlock";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
message: Message;
|
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">
|
<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" />
|
<Icon.AiOutlineRobot className="w-6 h-6" />
|
||||||
</div>
|
</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"
|
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) }}
|
remarkPlugins={[remarkGfm]}
|
||||||
></div>
|
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>
|
</div>
|
||||||
|
@ -10,10 +10,11 @@ const ClearDataConfirmModal = (props: Props) => {
|
|||||||
|
|
||||||
const handleClearData = () => {
|
const handleClearData = () => {
|
||||||
window.localStorage.clear();
|
window.localStorage.clear();
|
||||||
|
close();
|
||||||
toast.success("Message cleared. The page will be reloaded.");
|
toast.success("Message cleared. The page will be reloaded.");
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}, 300);
|
}, 500);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
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",
|
"eventsource-parser": "^1.0.0",
|
||||||
"highlight.js": "^11.7.0",
|
"highlight.js": "^11.7.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"marked": "^4.2.12",
|
|
||||||
"next": "^13.2.4",
|
"next": "^13.2.4",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-hot-toast": "^2.4.0",
|
"react-hot-toast": "^2.4.0",
|
||||||
"react-icons": "^4.8.0",
|
"react-icons": "^4.8.0",
|
||||||
|
"react-markdown": "^8.0.6",
|
||||||
"react-textarea-autosize": "^8.4.0",
|
"react-textarea-autosize": "^8.4.0",
|
||||||
|
"remark-gfm": "^3.0.1",
|
||||||
"uuid": "^9.0.0",
|
"uuid": "^9.0.0",
|
||||||
"zustand": "^4.3.6"
|
"zustand": "^4.3.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/typography": "^0.5.9",
|
"@tailwindcss/typography": "^0.5.9",
|
||||||
"@types/lodash-es": "^4.17.7",
|
"@types/lodash-es": "^4.17.7",
|
||||||
"@types/marked": "^4.0.8",
|
|
||||||
"@types/node": "^18.11.18",
|
"@types/node": "^18.11.18",
|
||||||
"@types/pg": "^8.6.6",
|
"@types/pg": "^8.6.6",
|
||||||
"@types/react": "^18.0.26",
|
"@types/react": "^18.0.26",
|
||||||
"@types/react-dom": "^18.0.11",
|
"@types/react-dom": "^18.0.11",
|
||||||
|
"@types/react-syntax-highlighter": "^15.5.6",
|
||||||
"@types/uuid": "^9.0.1",
|
"@types/uuid": "^9.0.1",
|
||||||
"autoprefixer": "^10.4.13",
|
"autoprefixer": "^10.4.13",
|
||||||
"eslint": "8.20.0",
|
"eslint": "8.20.0",
|
||||||
@ -40,6 +41,7 @@
|
|||||||
"mysql2": "^3.2.0",
|
"mysql2": "^3.2.0",
|
||||||
"pg": "^8.10.0",
|
"pg": "^8.10.0",
|
||||||
"postcss": "^8.4.20",
|
"postcss": "^8.4.20",
|
||||||
|
"react-syntax-highlighter": "^15.5.0",
|
||||||
"tailwindcss": "^3.2.4",
|
"tailwindcss": "^3.2.4",
|
||||||
"typescript": "^4.9.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