diff --git a/01_content/src/client/locales.ts b/01_content/src/client/locales.ts index 2f0d599..89b05a8 100644 --- a/01_content/src/client/locales.ts +++ b/01_content/src/client/locales.ts @@ -65,6 +65,10 @@ export const zh = { 'pane.openLater': '该类型暂不支持预览', 'file.loading': '加载中…', 'file.fail': '文件读取失败', + 'file.preview': '预览', + 'file.edit': '编辑', + 'file.save': '保存', + 'file.saveFail': '保存失败', 'pane.gitNotRepo': '当前目录不是 git 仓库', 'pane.gitClean': '工作区干净', 'pane.jobsEmpty': '当前会话暂无后台任务', @@ -144,6 +148,10 @@ export const en = { 'pane.openLater': 'Preview not supported for this type', 'file.loading': 'Loading…', 'file.fail': 'Failed to read file', + 'file.preview': 'Preview', + 'file.edit': 'Edit', + 'file.save': 'Save', + 'file.saveFail': 'Save failed', 'pane.gitNotRepo': 'Not a git repository', 'pane.gitClean': 'Working tree clean', 'pane.jobsEmpty': 'No background jobs in the current session', diff --git a/01_content/src/client/split.tsx b/01_content/src/client/split.tsx index 1e0a5e6..4be6edd 100644 --- a/01_content/src/client/split.tsx +++ b/01_content/src/client/split.tsx @@ -1121,10 +1121,14 @@ function FileViewer(props: { path: string }) { return } -/** MD/TXT 文本预览(fetch 原文 → MD 渲染或
 等宽展示) */
+/** MD/TXT 文本预览(fetch 原文 → MD 渲染或 
 等宽展示);MD 支持编辑/预览自由切换并可保存回磁盘 */
 function TextViewer(props: { path: string; fileUrl: string; isMd: boolean }) {
   const [text, setText] = useState(null)
   const [error, setError] = useState('')
+  const [mode, setMode] = useState<'preview' | 'edit'>('preview')
+  const [draft, setDraft] = useState('')
+  const [saving, setSaving] = useState(false)
+  const [saveFail, setSaveFail] = useState(false)
   useEffect(() => {
     let dead = false
     setText(null)
@@ -1138,6 +1142,25 @@ function TextViewer(props: { path: string; fileUrl: string; isMd: boolean }) {
       .catch((e) => { if (!dead) setError(String(e)) })
     return () => { dead = true }
   }, [props.fileUrl])
+  const enterEdit = () => { setDraft(text ?? ''); setSaveFail(false); setMode('edit') }
+  const save = async () => {
+    setSaving(true)
+    setSaveFail(false)
+    try {
+      const r = await fetch('/api/worktable/write', {
+        method: 'POST',
+        headers: { 'content-type': 'application/json' },
+        body: JSON.stringify({ path: props.path, content: draft }),
+      })
+      if (!r.ok) throw new Error('HTTP ' + r.status)
+      setText(draft)
+      setMode('preview')
+    } catch {
+      setSaveFail(true)
+    } finally {
+      setSaving(false)
+    }
+  }
   if (error) {
     return 
{T('file.fail')}:{error}
} @@ -1146,19 +1169,33 @@ function TextViewer(props: { path: string; fileUrl: string; isMd: boolean }) { } if (props.isMd) { return ( -
-
{ - const a = e.target && e.target.closest ? (e.target.closest('a') as HTMLAnchorElement | null) : null - if (!a) return - e.preventDefault() - const href = a.getAttribute('href') || '' - if (/^(https?:|mailto:)/i.test(href)) window.open(href, '_blank', 'noopener') - }} - /> -
+ <> +
+ + + {mode === 'edit' && ( + + )} + {saveFail && {T('file.saveFail')}} +
+ {mode === 'edit' + ?