import { useState, useEffect, useCallback, useRef } from 'react'; import { Paper, Textarea, Group, Text, Stack, Button, Box, } from '@mantine/core'; import { useDebouncedValue } from '@mantine/hooks'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import rehypeSanitize from 'rehype-sanitize'; import { IconDeviceFloppy } from '@tabler/icons-react'; import { useAppStore } from '../../stores'; interface NoteEditorProps { onSave?: () => void; } type ViewMode = 'edit' | 'preview'; export function NoteEditor({ onSave }: NoteEditorProps) { const notes = useAppStore((state) => state.notes); const updateNotesContent = useAppStore((state) => state.updateNotesContent); const saveNotes = useAppStore((state) => state.saveNotes); const fetchNotes = useAppStore((state) => state.fetchNotes); const [content, setContent] = useState(''); const [saving, setSaving] = useState(false); const [lastSaved, setLastSaved] = useState(null); const [viewMode] = useState('edit'); // Initialize content from notes useEffect(() => { if (notes) { setContent(notes.content); } }, [notes]); // Fetch notes on mount useEffect(() => { fetchNotes(); }, [fetchNotes]); // Auto-save with 3 second debounce const [debouncedContent] = useDebouncedValue(content, 3000); // 滚动条样式 - 仅在悬停时显示 const scrollbarStyle = ` .note-scroll::-webkit-scrollbar { width: 4px; height: 4px; } .note-scroll::-webkit-scrollbar-track { background: transparent; } .note-scroll::-webkit-scrollbar-thumb { background: transparent; border-radius: 2px; } .note-scroll:hover::-webkit-scrollbar-thumb { background: rgba(0, 0, 0, 0.15); } `; useEffect(() => { if (debouncedContent !== undefined && notes && debouncedContent !== content) { handleSave(debouncedContent); } }, [debouncedContent]); const handleSave = useCallback( async (value: string) => { if (!notes) return; setSaving(true); try { updateNotesContent(value); await saveNotes(value); setLastSaved(new Date()); onSave?.(); } catch (error) { console.error('Failed to save note:', error); } finally { setSaving(false); } }, [notes, updateNotesContent, saveNotes, onSave] ); const formatLastSaved = () => { if (!lastSaved) return '未保存'; const now = new Date(); const diff = now.getTime() - lastSaved.getTime(); if (diff < 1000) return '刚刚保存'; if (diff < 60000) return `${Math.floor(diff / 1000)}秒前保存`; return lastSaved.toLocaleTimeString('zh-CN'); }; const handleManualSave = () => { if (content && notes) { handleSave(content); } }; const scrollContainerRef = useRef(null); // 处理滚轮事件,实现列表独立滚动 const handleWheel = (e: React.WheelEvent) => { const container = scrollContainerRef.current; if (!container) return; const { scrollTop, scrollHeight, clientHeight } = container; // 使用 1px 缓冲避免浮点数精度问题 const isAtTop = scrollTop <= 0; const isAtBottom = scrollTop + clientHeight >= scrollHeight - 1; // 如果已经滚动到顶部且向下滚动,或者已经滚动到底部且向上滚动,则阻止事件冒泡 if ((isAtTop && e.deltaY > 0) || (isAtBottom && e.deltaY < 0)) { e.stopPropagation(); } // 如果在滚动范围内,允许事件继续传递以实现正常滚动 }; return ( {/* Header */} 便签 {saving ? '保存中...' : formatLastSaved()} {/* Editor/Preview Area */} {viewMode === 'edit' && (