From e341a56fb221ddd659f6fd4ec24084025f51b072 Mon Sep 17 00:00:00 2001 From: Tianyang Date: Sat, 1 Aug 2026 02:47:29 +0800 Subject: [PATCH] feat: add DocumentEditor with dirty tracking and save --- frontend/src/components/DocumentEditor.tsx | 71 ++++++++++++++++++ .../tests/components/DocumentEditor.test.tsx | 72 +++++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 frontend/src/components/DocumentEditor.tsx create mode 100644 frontend/tests/components/DocumentEditor.test.tsx diff --git a/frontend/src/components/DocumentEditor.tsx b/frontend/src/components/DocumentEditor.tsx new file mode 100644 index 0000000..141c168 --- /dev/null +++ b/frontend/src/components/DocumentEditor.tsx @@ -0,0 +1,71 @@ +import { useEffect, useState } from 'react'; +import Editor from '@monaco-editor/react'; +import { getDocument, saveDocument } from '../api/client'; + +interface DocumentEditorProps { + documentKey: string; +} + +export default function DocumentEditor({ documentKey }: DocumentEditorProps) { + const [content, setContent] = useState(''); + const [originalContent, setOriginalContent] = useState(''); + const [contentType, setContentType] = useState('text/markdown'); + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); + const [error, setError] = useState(null); + + useEffect(() => { + setLoading(true); + setError(null); + getDocument(documentKey) + .then((doc) => { + setContent(doc.content); + setOriginalContent(doc.content); + setContentType(doc.contentType); + setLoading(false); + }) + .catch(() => { + setError('无法加载文档'); + setLoading(false); + }); + }, [documentKey]); + + const isDirty = content !== originalContent; + + function handleChange(value: string | undefined) { + setContent(value ?? ''); + } + + function handleSave() { + setSaving(true); + saveDocument(documentKey, content, contentType) + .then(() => { + setOriginalContent(content); + setSaving(false); + }) + .catch(() => { + setError('保存失败'); + setSaving(false); + }); + } + + if (loading) { + return
加载中…
; + } + + if (error) { + return
{error}
; + } + + return ( +
+
+ {isDirty ? '未保存' : '已归档'} + +
+ +
+ ); +} diff --git a/frontend/tests/components/DocumentEditor.test.tsx b/frontend/tests/components/DocumentEditor.test.tsx new file mode 100644 index 0000000..e3cfcf5 --- /dev/null +++ b/frontend/tests/components/DocumentEditor.test.tsx @@ -0,0 +1,72 @@ +import { render, screen } from '@testing-library/react'; +import { vi, test, expect, beforeEach } from 'vitest'; +import DocumentEditor from '../../src/components/DocumentEditor'; +import { getDocument, saveDocument } from '../../src/api/client'; + +vi.mock('../../src/api/client'); + +vi.mock('@monaco-editor/react', () => ({ + default: ({ value, onChange }: { value: string; onChange: (value: string | undefined) => void }) => ( +