This commit is contained in:
48
frontend/src/components/CodeBlock.jsx
Normal file
48
frontend/src/components/CodeBlock.jsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
||||
import { CopyOutlined, CheckOutlined } from '@ant-design/icons';
|
||||
import { Button, Tooltip } from 'antd';
|
||||
|
||||
const CodeBlock = ({ language, children, ...props }) => {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const handleCopy = () => {
|
||||
navigator.clipboard.writeText(String(children).replace(/\n$/, ''));
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ position: 'relative', margin: '1em 0' }}>
|
||||
<Tooltip title={copied ? "已复制" : "复制代码"}>
|
||||
<Button
|
||||
type="text"
|
||||
icon={copied ? <CheckOutlined style={{ color: '#52c41a' }} /> : <CopyOutlined style={{ color: '#fff' }} />}
|
||||
size="small"
|
||||
onClick={handleCopy}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 8,
|
||||
right: 8,
|
||||
zIndex: 1,
|
||||
color: '#fff',
|
||||
background: 'rgba(255, 255, 255, 0.1)',
|
||||
border: 'none',
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
<SyntaxHighlighter
|
||||
style={vscDarkPlus}
|
||||
language={language}
|
||||
PreTag="div"
|
||||
customStyle={{ margin: 0, borderRadius: 8, padding: '1.5em 1em 1em 1em' }}
|
||||
{...props}
|
||||
>
|
||||
{String(children).replace(/\n$/, '')}
|
||||
</SyntaxHighlighter>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CodeBlock;
|
||||
@@ -14,6 +14,8 @@ import rehypeRaw from 'rehype-raw';
|
||||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
||||
import 'katex/dist/katex.min.css';
|
||||
import styles from './ForumDetail.module.less';
|
||||
import CodeBlock from '../components/CodeBlock';
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
const { TextArea } = Input;
|
||||
@@ -133,14 +135,12 @@ const ForumDetail = () => {
|
||||
code({node, inline, className, children, ...props}) {
|
||||
const match = /language-(\w+)/.exec(className || '')
|
||||
return !inline && match ? (
|
||||
<SyntaxHighlighter
|
||||
style={vscDarkPlus}
|
||||
<CodeBlock
|
||||
language={match[1]}
|
||||
PreTag="div"
|
||||
{...props}
|
||||
>
|
||||
{String(children).replace(/\n$/, '')}
|
||||
</SyntaxHighlighter>
|
||||
</CodeBlock>
|
||||
) : (
|
||||
<code className={className} {...props}>
|
||||
{children}
|
||||
@@ -226,7 +226,7 @@ const ForumDetail = () => {
|
||||
fontSize: 16,
|
||||
lineHeight: 1.8,
|
||||
minHeight: 200,
|
||||
}} className="markdown-body">
|
||||
}} className={styles['markdown-body']}>
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkMath, remarkGfm]}
|
||||
rehypePlugins={[rehypeKatex, rehypeRaw]}
|
||||
@@ -275,7 +275,7 @@ const ForumDetail = () => {
|
||||
</Space>
|
||||
<Text style={{ color: '#444', fontSize: 12 }}>#{index + 1}</Text>
|
||||
</div>
|
||||
<div style={{ color: '#eee', fontSize: isMobile ? 14 : 16 }}>
|
||||
<div style={{ color: '#eee', fontSize: isMobile ? 14 : 16 }} className={styles['markdown-body']}>
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkMath, remarkGfm]}
|
||||
rehypePlugins={[rehypeKatex, rehypeRaw]}
|
||||
|
||||
109
frontend/src/pages/ForumDetail.module.less
Normal file
109
frontend/src/pages/ForumDetail.module.less
Normal file
@@ -0,0 +1,109 @@
|
||||
.markdown-body {
|
||||
color: #ddd;
|
||||
font-size: 16px;
|
||||
line-height: 1.8;
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
color: #fff;
|
||||
margin-top: 24px;
|
||||
margin-bottom: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
h1 { font-size: 2em; border-bottom: 1px solid rgba(255, 255, 255, 0.1); padding-bottom: 0.3em; }
|
||||
h2 { font-size: 1.5em; border-bottom: 1px solid rgba(255, 255, 255, 0.1); padding-bottom: 0.3em; }
|
||||
h3 { font-size: 1.25em; }
|
||||
h4 { font-size: 1em; }
|
||||
h5 { font-size: 0.875em; }
|
||||
h6 { font-size: 0.85em; color: #888; }
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #1890ff;
|
||||
text-decoration: none;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
margin-top: 0;
|
||||
margin-bottom: 16px;
|
||||
padding-left: 2em;
|
||||
}
|
||||
|
||||
li {
|
||||
word-wrap: break-all;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 16px;
|
||||
padding: 0 1em;
|
||||
color: #8b949e;
|
||||
border-left: 0.25em solid #30363d;
|
||||
}
|
||||
|
||||
/* Table Styles */
|
||||
table {
|
||||
display: block;
|
||||
width: 100%;
|
||||
width: max-content;
|
||||
max-width: 100%;
|
||||
overflow: auto;
|
||||
border-spacing: 0;
|
||||
border-collapse: collapse;
|
||||
margin-top: 0;
|
||||
margin-bottom: 16px;
|
||||
|
||||
thead {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
tr {
|
||||
background-color: transparent;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||
|
||||
&:nth-child(2n) {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 6px 13px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
th {
|
||||
font-weight: 600;
|
||||
text-align: left;
|
||||
/* Ensure text color is readable */
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
td {
|
||||
color: #ddd;
|
||||
}
|
||||
}
|
||||
|
||||
/* Inline Code */
|
||||
code:not([class*="language-"]) {
|
||||
padding: 0.2em 0.4em;
|
||||
margin: 0;
|
||||
font-size: 85%;
|
||||
background-color: rgba(110, 118, 129, 0.4);
|
||||
border-radius: 6px;
|
||||
font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
|
||||
}
|
||||
|
||||
/* Images */
|
||||
img {
|
||||
max-width: 100%;
|
||||
box-sizing: content-box;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user