MultiMindChat / components /Notepad.tsx
samlax12's picture
Upload 19 files
05f86a6 verified
import React, { useState, useMemo } from 'react';
import { MessageSender } from '../types';
import { FileText, Edit3, Eye, Code, Copy, Check } from 'lucide-react';
import { marked } from 'marked';
import DOMPurify from 'dompurify';
interface NotepadProps {
content: string;
lastUpdatedBy?: MessageSender | null;
isLoading: boolean;
}
const Notepad: React.FC<NotepadProps> = ({ content, lastUpdatedBy, isLoading }) => {
const [isPreviewMode, setIsPreviewMode] = useState(false);
const [isCopied, setIsCopied] = useState(false);
const getSenderColor = (sender?: MessageSender | null) => {
if (sender === MessageSender.Cognito) return 'text-green-400';
if (sender === MessageSender.Muse) return 'text-purple-400';
return 'text-gray-400';
};
const processedHtml = useMemo(() => {
if (isPreviewMode) {
const rawHtml = marked.parse(content) as string;
return DOMPurify.sanitize(rawHtml);
}
return '';
}, [content, isPreviewMode]);
const handleCopyNotepad = async () => {
try {
await navigator.clipboard.writeText(content);
setIsCopied(true);
setTimeout(() => setIsCopied(false), 2000);
} catch (err) {
console.error('无法复制记事本内容: ', err);
// Optionally, display an error message to the user
}
};
return (
<div className="h-full flex flex-col bg-slate-800 border-l border-gray-700">
<header className="p-3 border-b border-gray-700 flex items-center justify-between bg-slate-900">
<div className="flex items-center">
<FileText size={20} className="mr-2 text-sky-400" />
<h2 className="text-lg font-semibold text-sky-400">记事本</h2>
</div>
<div className="flex items-center space-x-2">
{isLoading && <span className="text-xs text-gray-400 italic">AI 思考中...</span>}
<button
onClick={handleCopyNotepad}
className="p-1.5 text-gray-400 hover:text-sky-400 transition-colors duration-150 focus:outline-none focus:ring-1 focus:ring-sky-500 rounded-md"
title={isCopied ? "已复制!" : "复制记事本内容"}
aria-label={isCopied ? "已复制记事本内容到剪贴板" : "复制记事本内容"}
>
{isCopied ? <Check size={18} className="text-green-400" /> : <Copy size={18} />}
</button>
<button
onClick={() => setIsPreviewMode(!isPreviewMode)}
className="p-1.5 text-gray-400 hover:text-sky-400 transition-colors duration-150 focus:outline-none focus:ring-1 focus:ring-sky-500 rounded-md"
title={isPreviewMode ? "查看原始内容" : "预览 Markdown"}
aria-label={isPreviewMode ? "Switch to raw text view" : "Switch to Markdown preview"}
>
{isPreviewMode ? <Code size={18} /> : <Eye size={18} />}
</button>
</div>
</header>
<div className="flex-grow overflow-y-auto relative">
{isPreviewMode ? (
<div
className="markdown-preview" // Styles defined in index.html
dangerouslySetInnerHTML={{ __html: processedHtml }}
aria-label="Markdown 预览"
/>
) : (
<textarea
readOnly
value={content}
className="w-full h-full p-3 bg-slate-800 text-gray-300 resize-none border-none focus:ring-0 font-mono text-sm leading-relaxed"
aria-label="共享记事本内容 (原始内容)"
/>
)}
</div>
<footer className="p-2 border-t border-gray-700 text-xs text-gray-500 bg-slate-900">
{lastUpdatedBy ? (
<div className="flex items-center">
<Edit3 size={14} className={`mr-1.5 ${getSenderColor(lastUpdatedBy)}`} />
最后更新者: <span className={`font-medium ml-1 ${getSenderColor(lastUpdatedBy)}`}>{lastUpdatedBy}</span>
</div>
) : (
<span>记事本内容未被 AI 修改过。</span>
)}
</footer>
</div>
);
};
export default Notepad;