chore: sync local changes (2026-03-12)
This commit is contained in:
@@ -1,96 +1,96 @@
|
||||
import React from 'react';
|
||||
|
||||
class ErrorBoundary extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { hasError: false, error: null, errorInfo: null };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error) {
|
||||
return { hasError: true };
|
||||
}
|
||||
|
||||
componentDidCatch(error, errorInfo) {
|
||||
console.error('[MengyaNote] Error Boundary 捕获错误:', error, errorInfo);
|
||||
this.setState({
|
||||
error,
|
||||
errorInfo
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return (
|
||||
<div style={{
|
||||
padding: '40px',
|
||||
textAlign: 'center',
|
||||
maxWidth: '600px',
|
||||
margin: '100px auto',
|
||||
backgroundColor: '#fff3cd',
|
||||
border: '1px solid #ffc107',
|
||||
borderRadius: '8px'
|
||||
}}>
|
||||
<h1 style={{ color: '#856404', marginBottom: '20px' }}>⚠️ 应用出现错误</h1>
|
||||
<p style={{ color: '#856404', marginBottom: '15px' }}>
|
||||
抱歉,应用遇到了一个问题。这可能是由于浏览器权限设置导致的。
|
||||
</p>
|
||||
|
||||
<div style={{
|
||||
backgroundColor: '#f8f9fa',
|
||||
padding: '15px',
|
||||
borderRadius: '4px',
|
||||
marginBottom: '20px',
|
||||
textAlign: 'left',
|
||||
fontFamily: 'monospace',
|
||||
fontSize: '12px',
|
||||
overflow: 'auto'
|
||||
}}>
|
||||
<strong>错误信息:</strong>
|
||||
<pre style={{ margin: '10px 0', whiteSpace: 'pre-wrap' }}>
|
||||
{this.state.error && this.state.error.toString()}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: '20px' }}>
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
style={{
|
||||
padding: '10px 20px',
|
||||
backgroundColor: '#007bff',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer',
|
||||
fontSize: '14px'
|
||||
}}
|
||||
>
|
||||
刷新页面重试
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
marginTop: '30px',
|
||||
padding: '15px',
|
||||
backgroundColor: '#e7f3ff',
|
||||
border: '1px solid #b3d9ff',
|
||||
borderRadius: '4px',
|
||||
textAlign: 'left',
|
||||
fontSize: '13px'
|
||||
}}>
|
||||
<strong>💡 可能的解决方案:</strong>
|
||||
<ul style={{ marginTop: '10px', paddingLeft: '20px' }}>
|
||||
<li>尝试在浏览器设置中允许此网站访问存储</li>
|
||||
<li>清除浏览器缓存和Cookie后重试</li>
|
||||
<li>尝试使用无痕/隐私模式访问</li>
|
||||
<li>更换其他浏览器(推荐 Chrome、Edge、Firefox)</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
export default ErrorBoundary;
|
||||
import React from 'react';
|
||||
|
||||
class ErrorBoundary extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { hasError: false, error: null, errorInfo: null };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error) {
|
||||
return { hasError: true };
|
||||
}
|
||||
|
||||
componentDidCatch(error, errorInfo) {
|
||||
console.error('[MengyaNote] Error Boundary 捕获错误:', error, errorInfo);
|
||||
this.setState({
|
||||
error,
|
||||
errorInfo
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return (
|
||||
<div style={{
|
||||
padding: '40px',
|
||||
textAlign: 'center',
|
||||
maxWidth: '600px',
|
||||
margin: '100px auto',
|
||||
backgroundColor: '#fff3cd',
|
||||
border: '1px solid #ffc107',
|
||||
borderRadius: '8px'
|
||||
}}>
|
||||
<h1 style={{ color: '#856404', marginBottom: '20px' }}>⚠️ 应用出现错误</h1>
|
||||
<p style={{ color: '#856404', marginBottom: '15px' }}>
|
||||
抱歉,应用遇到了一个问题。这可能是由于浏览器权限设置导致的。
|
||||
</p>
|
||||
|
||||
<div style={{
|
||||
backgroundColor: '#f8f9fa',
|
||||
padding: '15px',
|
||||
borderRadius: '4px',
|
||||
marginBottom: '20px',
|
||||
textAlign: 'left',
|
||||
fontFamily: 'monospace',
|
||||
fontSize: '12px',
|
||||
overflow: 'auto'
|
||||
}}>
|
||||
<strong>错误信息:</strong>
|
||||
<pre style={{ margin: '10px 0', whiteSpace: 'pre-wrap' }}>
|
||||
{this.state.error && this.state.error.toString()}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: '20px' }}>
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
style={{
|
||||
padding: '10px 20px',
|
||||
backgroundColor: '#007bff',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer',
|
||||
fontSize: '14px'
|
||||
}}
|
||||
>
|
||||
刷新页面重试
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
marginTop: '30px',
|
||||
padding: '15px',
|
||||
backgroundColor: '#e7f3ff',
|
||||
border: '1px solid #b3d9ff',
|
||||
borderRadius: '4px',
|
||||
textAlign: 'left',
|
||||
fontSize: '13px'
|
||||
}}>
|
||||
<strong>💡 可能的解决方案:</strong>
|
||||
<ul style={{ marginTop: '10px', paddingLeft: '20px' }}>
|
||||
<li>尝试在浏览器设置中允许此网站访问存储</li>
|
||||
<li>清除浏览器缓存和Cookie后重试</li>
|
||||
<li>尝试使用无痕/隐私模式访问</li>
|
||||
<li>更换其他浏览器(推荐 Chrome、Edge、Firefox)</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
export default ErrorBoundary;
|
||||
|
||||
@@ -1,321 +1,528 @@
|
||||
/* ========================================
|
||||
Markdown 渲染器样式文件
|
||||
用途:定义 Markdown 内容渲染容器的布局和样式
|
||||
======================================== */
|
||||
|
||||
/* ========================================
|
||||
主容器样式
|
||||
======================================== */
|
||||
|
||||
.markdown-renderer {
|
||||
flex: 1;
|
||||
height: 100vh;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
background: #ffffff;
|
||||
|
||||
/* 隐藏滚动条但保留滚动功能 */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
}
|
||||
|
||||
.markdown-renderer::-webkit-scrollbar {
|
||||
display: none; /* Chrome, Safari, Opera */
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
内容容器
|
||||
======================================== */
|
||||
|
||||
.markdown-container {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 45px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
GitHub Markdown Body 样式增强
|
||||
======================================== */
|
||||
|
||||
.markdown-body {
|
||||
box-sizing: border-box;
|
||||
min-width: 200px;
|
||||
font-family: 'Maple Mono CN', ui-monospace, -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji" !important;
|
||||
font-size: 20.8px; /* 16px * 1.3 = 20.8px */
|
||||
line-height: 1.6;
|
||||
word-wrap: break-word;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/* 覆盖 github-markdown-css 的字体设置 */
|
||||
.markdown-body,
|
||||
.markdown-body p,
|
||||
.markdown-body li,
|
||||
.markdown-body td,
|
||||
.markdown-body th,
|
||||
.markdown-body div,
|
||||
.markdown-body span {
|
||||
font-family: 'Maple Mono CN', ui-monospace, -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif !important;
|
||||
}
|
||||
|
||||
/* 代码字体保持等宽 */
|
||||
.markdown-body code,
|
||||
.markdown-body pre,
|
||||
.markdown-body tt {
|
||||
font-family: 'Maple Mono CN', ui-monospace, 'SFMono-Regular', 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace !important;
|
||||
}
|
||||
|
||||
/* 图片可点击放大效果 */
|
||||
.markdown-body img {
|
||||
transition: transform 0.3s ease;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.markdown-body img.zoomed {
|
||||
transform: scale(1.5);
|
||||
cursor: zoom-out;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* 表格优化 */
|
||||
.markdown-body table {
|
||||
display: block;
|
||||
width: max-content;
|
||||
max-width: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
加载状态样式
|
||||
======================================== */
|
||||
|
||||
.loading-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 400px;
|
||||
color: #57606a;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid #f0f0f0;
|
||||
border-top: 4px solid #0969da;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading-state p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
错误状态样式
|
||||
======================================== */
|
||||
|
||||
.error-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 400px;
|
||||
color: #cf222e;
|
||||
text-align: center;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.error-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.error-state h2 {
|
||||
margin: 0 0 12px 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #24292f;
|
||||
}
|
||||
|
||||
.error-state p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: #57606a;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
空状态样式
|
||||
======================================== */
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 400px;
|
||||
color: #57606a;
|
||||
text-align: center;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 64px;
|
||||
margin-bottom: 20px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.empty-state h2 {
|
||||
margin: 0 0 12px 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #24292f;
|
||||
}
|
||||
|
||||
.empty-state p {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
color: #57606a;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
响应式设计
|
||||
======================================== */
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.markdown-container {
|
||||
padding: 20px 16px;
|
||||
}
|
||||
|
||||
.markdown-body {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 移动端图片不支持放大 */
|
||||
.markdown-body img {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.markdown-body img.zoomed {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.markdown-container {
|
||||
padding: 16px 12px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
数学公式样式优化
|
||||
======================================== */
|
||||
|
||||
.markdown-body .katex {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.markdown-body .katex-display {
|
||||
margin: 1em 0;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
代码块滚动条样式
|
||||
======================================== */
|
||||
|
||||
.markdown-body pre {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.markdown-body pre::-webkit-scrollbar {
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.markdown-body pre::-webkit-scrollbar-track {
|
||||
background: #f6f8fa;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.markdown-body pre::-webkit-scrollbar-thumb {
|
||||
background: #d0d7de;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.markdown-body pre::-webkit-scrollbar-thumb:hover {
|
||||
background: #afb8c1;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
文件元数据样式
|
||||
======================================== */
|
||||
|
||||
.file-metadata {
|
||||
margin-top: 60px;
|
||||
padding-top: 32px;
|
||||
}
|
||||
|
||||
.metadata-divider {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background: linear-gradient(to right, transparent, #d0d7de 20%, #d0d7de 80%, transparent);
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.metadata-content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px 32px;
|
||||
padding: 20px 24px;
|
||||
background: #f6f8fa;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #d0d7de;
|
||||
}
|
||||
|
||||
.metadata-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.metadata-label {
|
||||
color: #57606a;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.metadata-value {
|
||||
color: #24292f;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 768px) {
|
||||
.file-metadata {
|
||||
margin-top: 40px;
|
||||
padding-top: 24px;
|
||||
}
|
||||
|
||||
.metadata-content {
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.metadata-item {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
Markdown 渲染器样式文件
|
||||
用途:定义 Markdown 内容渲染容器的布局和样式
|
||||
======================================== */
|
||||
|
||||
/* ========================================
|
||||
主容器样式
|
||||
======================================== */
|
||||
|
||||
.markdown-renderer {
|
||||
flex: 1;
|
||||
height: 100vh;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
background: #ffffff;
|
||||
|
||||
/* 隐藏滚动条但保留滚动功能 */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
}
|
||||
|
||||
.markdown-renderer::-webkit-scrollbar {
|
||||
display: none; /* Chrome, Safari, Opera */
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
内容容器
|
||||
======================================== */
|
||||
|
||||
.markdown-container {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 45px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
文件头部样式
|
||||
======================================== */
|
||||
|
||||
.file-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 32px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #d0d7de;
|
||||
}
|
||||
|
||||
.file-title {
|
||||
margin: 0;
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: #24292f;
|
||||
font-family: 'LXGWWenKaiMono', ui-monospace, var(--font-family-base);
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
||||
.file-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
min-width: 32px;
|
||||
height: 32px;
|
||||
padding: 0 10px;
|
||||
background: #f6f8fa;
|
||||
color: #57606a;
|
||||
border: 1px solid #d0d7de;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0 1px 3px rgba(31, 35, 40, 0.04);
|
||||
white-space: nowrap;
|
||||
font-family: 'LXGWWenKaiMono', ui-monospace, var(--font-family-base);
|
||||
}
|
||||
|
||||
.action-button:hover {
|
||||
background: #ffffff;
|
||||
border-color: #d0d7de;
|
||||
color: #24292f;
|
||||
box-shadow: 0 2px 6px rgba(31, 35, 40, 0.08);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.action-button:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 1px 3px rgba(31, 35, 40, 0.06);
|
||||
}
|
||||
|
||||
.copy-button.success {
|
||||
background: #2da44e;
|
||||
border-color: #2da44e;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
GitHub Markdown Body 样式增强
|
||||
======================================== */
|
||||
|
||||
.markdown-body {
|
||||
box-sizing: border-box;
|
||||
min-width: 200px;
|
||||
font-family: 'LXGWWenKaiMono', ui-monospace, -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji" !important;
|
||||
font-size: 20.8px; /* 16px * 1.3 = 20.8px */
|
||||
line-height: 1.6;
|
||||
word-wrap: break-word;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/* 覆盖 github-markdown-css 的字体设置 */
|
||||
.markdown-body,
|
||||
.markdown-body p,
|
||||
.markdown-body li,
|
||||
.markdown-body td,
|
||||
.markdown-body th,
|
||||
.markdown-body div,
|
||||
.markdown-body span {
|
||||
font-family: 'LXGWWenKaiMono', ui-monospace, -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif !important;
|
||||
}
|
||||
|
||||
/* 代码字体保持等宽 */
|
||||
.markdown-body code,
|
||||
.markdown-body pre,
|
||||
.markdown-body tt {
|
||||
font-family: 'LXGWWenKaiMono', ui-monospace, 'SFMono-Regular', 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace !important;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
DeepSeek 风格代码块样式
|
||||
======================================== */
|
||||
|
||||
.code-block-wrapper {
|
||||
margin: 16px 0;
|
||||
border: 1px solid #d0d7de;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
background: #f6f8fa;
|
||||
}
|
||||
|
||||
.code-block-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 12px;
|
||||
background: #f6f8fa;
|
||||
border-bottom: 1px solid #d0d7de;
|
||||
}
|
||||
|
||||
.code-language {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #57606a;
|
||||
text-transform: lowercase;
|
||||
font-family: 'LXGWWenKaiMono', ui-monospace, monospace;
|
||||
}
|
||||
|
||||
.code-actions {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.code-action-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: #57606a;
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
font-family: 'LXGWWenKaiMono', ui-monospace, var(--font-family-base);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.code-action-btn:hover {
|
||||
background: #ffffff;
|
||||
border-color: #d0d7de;
|
||||
color: #24292f;
|
||||
}
|
||||
|
||||
.code-action-btn:active {
|
||||
background: #f6f8fa;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
/* 代码块内的 pre 标签样式覆盖 */
|
||||
.code-block-wrapper pre {
|
||||
margin: 0 !important;
|
||||
padding: 16px !important;
|
||||
background: #ffffff !important;
|
||||
border: none !important;
|
||||
border-radius: 0 !important;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.code-block-wrapper pre code {
|
||||
background: transparent !important;
|
||||
padding: 0 !important;
|
||||
font-size: 14px !important;
|
||||
line-height: 1.6 !important;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
原有图片和表格样式
|
||||
======================================== */
|
||||
.markdown-body img {
|
||||
transition: transform 0.3s ease;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.markdown-body img.zoomed {
|
||||
transform: scale(1.5);
|
||||
cursor: zoom-out;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* 表格优化 */
|
||||
.markdown-body table {
|
||||
display: block;
|
||||
width: max-content;
|
||||
max-width: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
加载状态样式
|
||||
======================================== */
|
||||
|
||||
.loading-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 400px;
|
||||
color: #57606a;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid #f0f0f0;
|
||||
border-top: 4px solid #0969da;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading-state p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
错误状态样式
|
||||
======================================== */
|
||||
|
||||
.error-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 400px;
|
||||
color: #cf222e;
|
||||
text-align: center;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.error-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.error-state h2 {
|
||||
margin: 0 0 12px 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #24292f;
|
||||
}
|
||||
|
||||
.error-state p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: #57606a;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
空状态样式
|
||||
======================================== */
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 400px;
|
||||
color: #57606a;
|
||||
text-align: center;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 64px;
|
||||
margin-bottom: 20px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.empty-state h2 {
|
||||
margin: 0 0 12px 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #24292f;
|
||||
}
|
||||
|
||||
.empty-state p {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
color: #57606a;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
响应式设计
|
||||
======================================== */
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.markdown-container {
|
||||
padding: 20px 16px;
|
||||
}
|
||||
|
||||
.markdown-body {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.file-header {
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.file-title {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
min-width: 28px;
|
||||
height: 28px;
|
||||
padding: 0 8px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
/* 移动端代码块样式调整 */
|
||||
.code-block-header {
|
||||
padding: 6px 10px;
|
||||
}
|
||||
|
||||
.code-language {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.code-action-btn {
|
||||
padding: 3px 6px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.code-block-wrapper pre {
|
||||
padding: 12px !important;
|
||||
}
|
||||
|
||||
.code-block-wrapper pre code {
|
||||
font-size: 13px !important;
|
||||
}
|
||||
|
||||
/* 移动端图片不支持放大 */
|
||||
.markdown-body img {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.markdown-body img.zoomed {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.markdown-container {
|
||||
padding: 16px 12px;
|
||||
}
|
||||
|
||||
.file-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.file-title {
|
||||
padding-right: 0;
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.file-actions {
|
||||
align-self: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
数学公式样式优化
|
||||
======================================== */
|
||||
|
||||
.markdown-body .katex {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.markdown-body .katex-display {
|
||||
margin: 1em 0;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
代码块滚动条样式
|
||||
======================================== */
|
||||
|
||||
.markdown-body pre {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.markdown-body pre::-webkit-scrollbar {
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.markdown-body pre::-webkit-scrollbar-track {
|
||||
background: #f6f8fa;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.markdown-body pre::-webkit-scrollbar-thumb {
|
||||
background: #d0d7de;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.markdown-body pre::-webkit-scrollbar-thumb:hover {
|
||||
background: #afb8c1;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
文件元数据样式
|
||||
======================================== */
|
||||
|
||||
.file-metadata {
|
||||
margin-top: 60px;
|
||||
padding-top: 32px;
|
||||
}
|
||||
|
||||
.metadata-divider {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background: linear-gradient(to right, transparent, #d0d7de 20%, #d0d7de 80%, transparent);
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.metadata-content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px 32px;
|
||||
padding: 20px 24px;
|
||||
background: #f6f8fa;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #d0d7de;
|
||||
}
|
||||
|
||||
.metadata-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.metadata-label {
|
||||
color: #57606a;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.metadata-value {
|
||||
color: #24292f;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 768px) {
|
||||
.file-metadata {
|
||||
margin-top: 40px;
|
||||
padding-top: 24px;
|
||||
}
|
||||
|
||||
.metadata-content {
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.metadata-item {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,157 +1,312 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import remarkMath from 'remark-math';
|
||||
import remarkBreaks from 'remark-breaks';
|
||||
import rehypeKatex from 'rehype-katex';
|
||||
import rehypeHighlight from 'rehype-highlight';
|
||||
import rehypeRaw from 'rehype-raw';
|
||||
import { useApp } from '../context/AppContext';
|
||||
import './MarkdownRenderer.css';
|
||||
import 'github-markdown-css/github-markdown-light.css';
|
||||
import 'katex/dist/katex.min.css';
|
||||
import 'highlight.js/styles/github.css';
|
||||
|
||||
function MarkdownRenderer() {
|
||||
const { currentContent, currentFile, currentMetadata, isLoading, error } = useApp();
|
||||
const contentRef = useRef(null);
|
||||
|
||||
// 格式化文件大小
|
||||
const formatFileSize = (bytes) => {
|
||||
if (!bytes) return '0 B';
|
||||
const kb = bytes / 1024;
|
||||
if (kb < 1024) return `${kb.toFixed(2)} KB`;
|
||||
const mb = kb / 1024;
|
||||
if (mb < 1024) return `${mb.toFixed(2)} MB`;
|
||||
const gb = mb / 1024;
|
||||
return `${gb.toFixed(2)} GB`;
|
||||
};
|
||||
|
||||
// 当内容变化时,滚动到顶部
|
||||
useEffect(() => {
|
||||
if (contentRef.current) {
|
||||
contentRef.current.scrollTop = 0;
|
||||
}
|
||||
}, [currentFile]);
|
||||
|
||||
return (
|
||||
<div className="markdown-renderer" ref={contentRef}>
|
||||
<div className="markdown-container">
|
||||
{isLoading && (
|
||||
<div className="loading-state">
|
||||
<div className="loading-spinner"></div>
|
||||
<p>加载中...</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && !isLoading && (
|
||||
<div className="error-state">
|
||||
<div className="error-icon">⚠️</div>
|
||||
<h2>加载失败</h2>
|
||||
<p>{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isLoading && !error && !currentContent && (
|
||||
<div className="empty-state" style={{ padding: '50px', textAlign: 'center', color: '#888' }}>
|
||||
<p>请选择左侧文件查看内容</p>
|
||||
<p style={{ fontSize: '0.8em', marginTop: '10px' }}>Select a file to view content</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isLoading && !error && currentContent && (
|
||||
<article className="markdown-body">
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[
|
||||
remarkGfm, // GitHub Flavored Markdown
|
||||
remarkMath, // 数学公式支持
|
||||
remarkBreaks, // 自动换行
|
||||
]}
|
||||
rehypePlugins={[
|
||||
rehypeRaw, // 支持HTML标签
|
||||
rehypeKatex, // 数学公式渲染
|
||||
rehypeHighlight, // 代码高亮
|
||||
]}
|
||||
components={{
|
||||
// 自定义图片渲染,支持点击放大
|
||||
img: ({ node, ...props }) => (
|
||||
<img
|
||||
{...props}
|
||||
loading="lazy"
|
||||
onClick={(e) => {
|
||||
// 可以添加图片预览功能
|
||||
e.target.classList.toggle('zoomed');
|
||||
}}
|
||||
style={{ cursor: 'pointer' }}
|
||||
/>
|
||||
),
|
||||
// 自定义链接渲染,外部链接在新标签页打开
|
||||
a: ({ node, href, children, ...props }) => {
|
||||
const isExternal = href?.startsWith('http');
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
target={isExternal ? '_blank' : undefined}
|
||||
rel={isExternal ? 'noopener noreferrer' : undefined}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
},
|
||||
// 自定义代码块渲染
|
||||
code: ({ node, inline, className, children, ...props }) => {
|
||||
if (inline) {
|
||||
return <code className={className} {...props}>{children}</code>;
|
||||
}
|
||||
return (
|
||||
<code className={className} {...props}>
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{currentContent}
|
||||
</ReactMarkdown>
|
||||
|
||||
{/* 显示文件元数据 */}
|
||||
{currentMetadata && (
|
||||
<div className="file-metadata">
|
||||
<div className="metadata-divider"></div>
|
||||
<div className="metadata-content">
|
||||
<div className="metadata-item" title="字数统计">
|
||||
<span className="metadata-icon">📝</span>
|
||||
<span className="metadata-label">字数:</span>
|
||||
<span className="metadata-value">{currentMetadata.wordCount}</span>
|
||||
</div>
|
||||
<div className="metadata-item" title="文件大小">
|
||||
<span className="metadata-icon">💾</span>
|
||||
<span className="metadata-label">大小:</span>
|
||||
<span className="metadata-value">{formatFileSize(currentMetadata.fileSize)}</span>
|
||||
</div>
|
||||
{currentMetadata.createdTime && (
|
||||
<div className="metadata-item" title="创建时间">
|
||||
<span className="metadata-icon">📅</span>
|
||||
<span className="metadata-label">创建于:</span>
|
||||
<span className="metadata-value">{currentMetadata.createdTime}</span>
|
||||
</div>
|
||||
)}
|
||||
{currentMetadata.modifiedTime && (
|
||||
<div className="metadata-item" title="修改时间">
|
||||
<span className="metadata-icon">🕒</span>
|
||||
<span className="metadata-label">最后修改于:</span>
|
||||
<span className="metadata-value">{currentMetadata.modifiedTime}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</article>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import remarkMath from 'remark-math';
|
||||
import remarkBreaks from 'remark-breaks';
|
||||
import rehypeKatex from 'rehype-katex';
|
||||
import rehypeHighlight from 'rehype-highlight';
|
||||
import rehypeRaw from 'rehype-raw';
|
||||
import { useApp } from '../context/AppContext';
|
||||
import './MarkdownRenderer.css';
|
||||
import 'github-markdown-css/github-markdown-light.css';
|
||||
import 'katex/dist/katex.min.css';
|
||||
import 'highlight.js/styles/github.css';
|
||||
|
||||
function MarkdownRenderer() {
|
||||
const { currentContent, currentFile, currentMetadata, isLoading, error } = useApp();
|
||||
const contentRef = useRef(null);
|
||||
const [copySuccess, setCopySuccess] = useState(false);
|
||||
const [codeCopySuccess, setCodeCopySuccess] = useState({});
|
||||
|
||||
// 语言到文件扩展名的映射
|
||||
const languageExtensions = {
|
||||
javascript: 'js',
|
||||
typescript: 'ts',
|
||||
python: 'py',
|
||||
java: 'java',
|
||||
cpp: 'cpp',
|
||||
c: 'c',
|
||||
csharp: 'cs',
|
||||
go: 'go',
|
||||
rust: 'rs',
|
||||
php: 'php',
|
||||
ruby: 'rb',
|
||||
swift: 'swift',
|
||||
kotlin: 'kt',
|
||||
scala: 'scala',
|
||||
html: 'html',
|
||||
css: 'css',
|
||||
sql: 'sql',
|
||||
bash: 'sh',
|
||||
shell: 'sh',
|
||||
json: 'json',
|
||||
xml: 'xml',
|
||||
yaml: 'yaml',
|
||||
markdown: 'md',
|
||||
dart: 'dart',
|
||||
r: 'r',
|
||||
matlab: 'm',
|
||||
};
|
||||
|
||||
// 复制代码块
|
||||
const handleCodeCopy = async (code, index) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(code);
|
||||
setCodeCopySuccess({ ...codeCopySuccess, [index]: true });
|
||||
setTimeout(() => {
|
||||
setCodeCopySuccess({ ...codeCopySuccess, [index]: false });
|
||||
}, 2000);
|
||||
} catch (err) {
|
||||
console.error('复制失败:', err);
|
||||
}
|
||||
};
|
||||
|
||||
// 下载代码块
|
||||
const handleCodeDownload = (code, language) => {
|
||||
const extension = languageExtensions[language?.toLowerCase()] || 'txt';
|
||||
const blob = new Blob([code], { type: 'text/plain' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `code.${extension}`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
// 格式化文件大小
|
||||
const formatFileSize = (bytes) => {
|
||||
if (!bytes) return '0 B';
|
||||
const kb = bytes / 1024;
|
||||
if (kb < 1024) return `${kb.toFixed(2)} KB`;
|
||||
const mb = kb / 1024;
|
||||
if (mb < 1024) return `${mb.toFixed(2)} MB`;
|
||||
const gb = mb / 1024;
|
||||
return `${gb.toFixed(2)} GB`;
|
||||
};
|
||||
|
||||
// 获取文件名(不带.md后缀)
|
||||
const getFileName = () => {
|
||||
if (!currentFile) return '';
|
||||
const fileName = currentFile.split('/').pop();
|
||||
return fileName.replace(/\.md$/i, '');
|
||||
};
|
||||
|
||||
// 复制内容到剪贴板
|
||||
const handleCopy = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(currentContent);
|
||||
setCopySuccess(true);
|
||||
setTimeout(() => setCopySuccess(false), 2000);
|
||||
} catch (err) {
|
||||
console.error('复制失败:', err);
|
||||
}
|
||||
};
|
||||
|
||||
// 下载Markdown文件
|
||||
const handleDownload = () => {
|
||||
const blob = new Blob([currentContent], { type: 'text/markdown' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `${getFileName()}.md`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
// 当内容变化时,滚动到顶部
|
||||
useEffect(() => {
|
||||
if (contentRef.current) {
|
||||
contentRef.current.scrollTop = 0;
|
||||
}
|
||||
// 重置复制状态
|
||||
setCopySuccess(false);
|
||||
setCodeCopySuccess({});
|
||||
}, [currentFile]);
|
||||
|
||||
return (
|
||||
<div className="markdown-renderer" ref={contentRef}>
|
||||
<div className="markdown-container">
|
||||
{isLoading && (
|
||||
<div className="loading-state">
|
||||
<div className="loading-spinner"></div>
|
||||
<p>加载中...</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && !isLoading && (
|
||||
<div className="error-state">
|
||||
<div className="error-icon">⚠️</div>
|
||||
<h2>加载失败</h2>
|
||||
<p>{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isLoading && !error && !currentContent && (
|
||||
<div className="empty-state" style={{ padding: '50px', textAlign: 'center', color: '#888' }}>
|
||||
<p>请选择左侧文件查看内容</p>
|
||||
<p style={{ fontSize: '0.8em', marginTop: '10px' }}>Select a file to view content</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isLoading && !error && currentContent && (
|
||||
<>
|
||||
{/* 文件头部:文件名 + 操作按钮 */}
|
||||
<div className="file-header">
|
||||
<h1 className="file-title">{getFileName()}</h1>
|
||||
<div className="file-actions">
|
||||
<button
|
||||
className="action-button copy-button"
|
||||
onClick={handleCopy}
|
||||
title="复制内容"
|
||||
>
|
||||
{copySuccess ? '✓ 已复制' : '⧉ 复制'}
|
||||
</button>
|
||||
<button
|
||||
className="action-button download-button"
|
||||
onClick={handleDownload}
|
||||
title="下载文件"
|
||||
>
|
||||
⇩ 下载
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<article className="markdown-body">
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[
|
||||
remarkGfm, // GitHub Flavored Markdown
|
||||
remarkMath, // 数学公式支持
|
||||
remarkBreaks, // 自动换行
|
||||
]}
|
||||
rehypePlugins={[
|
||||
rehypeRaw, // 支持HTML标签
|
||||
rehypeKatex, // 数学公式渲染
|
||||
rehypeHighlight, // 代码高亮
|
||||
]}
|
||||
components={{
|
||||
// 自定义图片渲染,支持点击放大
|
||||
img: ({ node, ...props }) => (
|
||||
<img
|
||||
{...props}
|
||||
loading="lazy"
|
||||
onClick={(e) => {
|
||||
// 可以添加图片预览功能
|
||||
e.target.classList.toggle('zoomed');
|
||||
}}
|
||||
style={{ cursor: 'pointer' }}
|
||||
/>
|
||||
),
|
||||
// 自定义链接渲染,外部链接在新标签页打开
|
||||
a: ({ node, href, children, ...props }) => {
|
||||
const isExternal = href?.startsWith('http');
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
target={isExternal ? '_blank' : undefined}
|
||||
rel={isExternal ? 'noopener noreferrer' : undefined}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
},
|
||||
// 自定义代码块渲染 - DeepSeek 风格
|
||||
pre: ({ node, children, ...props }) => {
|
||||
// 获取 code 元素
|
||||
const codeElement = children?.props;
|
||||
const className = codeElement?.className || '';
|
||||
const code = String(codeElement?.children || '').trim();
|
||||
|
||||
// 提取语言
|
||||
const match = /language-(\w+)/.exec(className);
|
||||
const language = match ? match[1] : 'text';
|
||||
|
||||
// 生成唯一索引
|
||||
const codeIndex = `${language}-${code.substring(0, 20)}`;
|
||||
|
||||
return (
|
||||
<div className="code-block-wrapper">
|
||||
<div className="code-block-header">
|
||||
<span className="code-language">{language}</span>
|
||||
<div className="code-actions">
|
||||
<button
|
||||
className="code-action-btn"
|
||||
onClick={() => handleCodeCopy(code, codeIndex)}
|
||||
title="复制代码"
|
||||
>
|
||||
{codeCopySuccess[codeIndex] ? '✓ 已复制' : '⧉ 复制'}
|
||||
</button>
|
||||
<button
|
||||
className="code-action-btn"
|
||||
onClick={() => handleCodeDownload(code, language)}
|
||||
title="下载代码"
|
||||
>
|
||||
⇩ 下载
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<pre {...props}>{children}</pre>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
// 自定义行内代码
|
||||
code: ({ node, inline, className, children, ...props }) => {
|
||||
if (inline) {
|
||||
return <code className={className} {...props}>{children}</code>;
|
||||
}
|
||||
return (
|
||||
<code className={className} {...props}>
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{currentContent}
|
||||
</ReactMarkdown>
|
||||
|
||||
{/* 显示文件元数据 */}
|
||||
{currentMetadata && (
|
||||
<div className="file-metadata">
|
||||
<div className="metadata-divider"></div>
|
||||
<div className="metadata-content">
|
||||
<div className="metadata-item" title="字数统计">
|
||||
<span className="metadata-icon">📝</span>
|
||||
<span className="metadata-label">字数:</span>
|
||||
<span className="metadata-value">{currentMetadata.wordCount}</span>
|
||||
</div>
|
||||
<div className="metadata-item" title="文件大小">
|
||||
<span className="metadata-icon">💾</span>
|
||||
<span className="metadata-label">大小:</span>
|
||||
<span className="metadata-value">{formatFileSize(currentMetadata.fileSize)}</span>
|
||||
</div>
|
||||
{currentMetadata.createdTime && (
|
||||
<div className="metadata-item" title="创建时间">
|
||||
<span className="metadata-icon">📅</span>
|
||||
<span className="metadata-label">创建于:</span>
|
||||
<span className="metadata-value">{currentMetadata.createdTime}</span>
|
||||
</div>
|
||||
)}
|
||||
{currentMetadata.modifiedTime && (
|
||||
<div className="metadata-item" title="修改时间">
|
||||
<span className="metadata-icon">🕒</span>
|
||||
<span className="metadata-label">最后修改于:</span>
|
||||
<span className="metadata-value">{currentMetadata.modifiedTime}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</article>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default MarkdownRenderer;
|
||||
@@ -22,7 +22,7 @@
|
||||
color: var(--color-text);
|
||||
box-shadow: none;
|
||||
overflow: hidden; /* 防止内容溢出 */
|
||||
font-family: 'Maple Mono CN', ui-monospace, var(--font-family-base);
|
||||
font-family: 'LXGWWenKaiMono', ui-monospace, var(--font-family-base);
|
||||
}
|
||||
|
||||
/* 侧边栏关闭状态 - 完全隐藏 */
|
||||
@@ -54,7 +54,7 @@
|
||||
letter-spacing: 0.02em; /* 字母间距 */
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
font-family: 'Maple Mono CN', ui-monospace, var(--font-family-base);
|
||||
font-family: 'LXGWWenKaiMono', ui-monospace, var(--font-family-base);
|
||||
flex-shrink: 0; /* 标题不收缩 */
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
cursor: pointer;
|
||||
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
box-shadow: 0 1px 3px rgba(31, 35, 40, 0.06);
|
||||
font-family: 'Maple Mono CN', ui-monospace, var(--font-family-base);
|
||||
font-family: 'LXGWWenKaiMono', ui-monospace, var(--font-family-base);
|
||||
flex-shrink: 0; /* 按钮不收缩 */
|
||||
margin-left: auto; /* 按钮靠右 */
|
||||
opacity: 0.8;
|
||||
@@ -222,7 +222,7 @@
|
||||
padding: 2.5rem 1rem;
|
||||
color: var(--color-muted);
|
||||
font-size: 0.9rem;
|
||||
font-family: 'Maple Mono CN', ui-monospace, var(--font-family-base);
|
||||
font-family: 'LXGWWenKaiMono', ui-monospace, var(--font-family-base);
|
||||
}
|
||||
|
||||
/* 加载动画 - 旋转圆圈 - GitHub 风格 */
|
||||
|
||||
Reference in New Issue
Block a user