之前抽屉一打开就是 Vditor 编辑器,正文是一堆 `##` 和 `-` 的原文, 导入的 84 条旧备忘看着尤其像草稿。现在分成「看」和「写」两态: - 默认只读:按 Markdown 渲染(markdown-it + DOMPurify),分类与标签是 chip, 附件列表只读;底部是「编辑」和状态动作,右侧显示发布于何时。 - 点「编辑」才挂编辑器;「取消」直接丢弃未保存改动(重新拉一次详情)。 - 安卓详情页同样用 Markwon 把正文排版后再显示,要改走「编辑正文」。 顺带修了 `web/e2e/knowledge.mjs` 里一条早就失效的断言:它等的是 「已发布 · 版本 N」,但界面上没有这行字(git 历史里也没有), 改成等抽屉底部的「发布于 …」。 验证:pytest 94 passed、ruff 全绿、tsc 0、vite build ✓; `knowledge-notebook.mjs` 16 项、`knowledge.mjs` 7 项 e2e 全绿。 安卓本轮仍未编译(本机 JDK 25 与 Gradle 8.7/AGP 8.7.3 不兼容)。
This commit is contained in:
+78
-18
@@ -2,8 +2,9 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { AppShell } from "../components/AppShell";
|
||||
import { AttachmentPanel } from "../components/AttachmentPanel";
|
||||
import { Markdown } from "../components/Markdown";
|
||||
import { RichTextEditor } from "../components/RichTextEditor";
|
||||
import { Link2, Paperclip, Plus, Search, Sparkle } from "../components/icons";
|
||||
import { Link2, Paperclip, Pencil, Plus, Search, Sparkle } from "../components/icons";
|
||||
import {
|
||||
Button,
|
||||
EmptyState,
|
||||
@@ -831,6 +832,8 @@ function KnowledgeDrawer({
|
||||
return map;
|
||||
}, [attachments]);
|
||||
const [busy, setBusy] = useState(false);
|
||||
/** 打开是「读」还是「写」:读的时候渲染样式,只有点「编辑」才进编辑器。 */
|
||||
const [editing, setEditing] = useState(false);
|
||||
/** 已经载入完成的是哪一条知识。重复的 effect / 慢响应不该再覆盖用户正在敲的内容。 */
|
||||
const loadedFor = useRef<string | null>(null);
|
||||
|
||||
@@ -845,6 +848,7 @@ function KnowledgeDrawer({
|
||||
setTags(data.tags.map((t) => t.name).join(" "));
|
||||
setCategoryId(data.category?.id ?? "");
|
||||
setAttachments(data.attachments ?? []);
|
||||
setEditing(false);
|
||||
loadedFor.current = itemId;
|
||||
} catch (err) {
|
||||
setError(err instanceof ApiError ? err.message : "加载失败");
|
||||
@@ -870,6 +874,7 @@ function KnowledgeDrawer({
|
||||
version: doc.version,
|
||||
});
|
||||
setDoc(next);
|
||||
setEditing(false);
|
||||
onChanged();
|
||||
} catch (err) {
|
||||
if (err instanceof ApiError && err.code === 4009) {
|
||||
@@ -932,23 +937,44 @@ function KnowledgeDrawer({
|
||||
footer={
|
||||
doc ? (
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{canEdit ? (
|
||||
<Button variant="primary" disabled={busy} onClick={() => void save()}>
|
||||
{busy ? "保存中…" : "保存"}
|
||||
</Button>
|
||||
) : null}
|
||||
{actions
|
||||
.filter((a) => a.show)
|
||||
.map((a) => (
|
||||
<Button
|
||||
key={a.key}
|
||||
variant={a.key === "publish" ? "clay" : "ghost"}
|
||||
disabled={busy}
|
||||
onClick={() => void act(a.key)}
|
||||
>
|
||||
{a.label}
|
||||
{editing ? (
|
||||
<>
|
||||
<Button variant="primary" disabled={busy} onClick={() => void save()}>
|
||||
{busy ? "保存中…" : "保存"}
|
||||
</Button>
|
||||
))}
|
||||
<Button
|
||||
variant="ghost"
|
||||
disabled={busy}
|
||||
onClick={() => {
|
||||
setEditing(false);
|
||||
void load(true);
|
||||
}}
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{canEdit ? (
|
||||
<Button variant="primary" onClick={() => setEditing(true)}>
|
||||
<Pencil size={13} />
|
||||
编辑
|
||||
</Button>
|
||||
) : null}
|
||||
{actions
|
||||
.filter((a) => a.show)
|
||||
.map((a) => (
|
||||
<Button
|
||||
key={a.key}
|
||||
variant={a.key === "publish" ? "clay" : "ghost"}
|
||||
disabled={busy}
|
||||
onClick={() => void act(a.key)}
|
||||
>
|
||||
{a.label}
|
||||
</Button>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
<span className="ml-auto text-2xs text-ink-400">
|
||||
{doc.knowledge.published_at
|
||||
? `发布于 ${new Date(doc.knowledge.published_at).toLocaleString("zh-CN")}`
|
||||
@@ -960,7 +986,7 @@ function KnowledgeDrawer({
|
||||
>
|
||||
{!doc ? (
|
||||
<p className="text-sm text-ink-400">{error ?? "加载中…"}</p>
|
||||
) : (
|
||||
) : !editing ? (
|
||||
<div className="space-y-4">
|
||||
<div className="rounded-xl border border-line-200 bg-paper-50 p-3.5 text-xs leading-relaxed text-ink-600">
|
||||
<div className="flex items-center gap-1.5">
|
||||
@@ -983,6 +1009,40 @@ function KnowledgeDrawer({
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-1.5 text-2xs">
|
||||
<span className="chip bg-paper-200 text-ink-600">
|
||||
{doc.category?.full_path ?? "未分类"}
|
||||
</span>
|
||||
{doc.tags.map((t) => (
|
||||
<span key={t.id} className="chip bg-primary-50 text-primary-700">
|
||||
#{t.name}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 看的时候就是渲染后的样子:标题、列表、表格、图片都按 Markdown 显示。
|
||||
要改再点「编辑」,进来才是编辑器——两者不该混在同一个界面里。 */}
|
||||
<div className="rounded-xl border border-line-200 bg-white px-4 py-4">
|
||||
<Markdown source={doc.body_md} resources={resources} />
|
||||
</div>
|
||||
|
||||
{attachments.length > 0 && (
|
||||
<div className="rounded-xl border border-line-200 bg-paper-50 px-3 py-2.5">
|
||||
<AttachmentPanel
|
||||
wsId={wsId}
|
||||
itemId={doc.id}
|
||||
attachments={attachments}
|
||||
onChange={setAttachments}
|
||||
onError={(message) => setError(message)}
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && <InlineAlert>{error}</InlineAlert>}
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
<input
|
||||
className="input text-base font-medium"
|
||||
value={title}
|
||||
|
||||
Reference in New Issue
Block a user