diff --git a/2.Design/ROADMAP-M0-M1.md b/2.Design/ROADMAP-M0-M1.md index 3bf1f70..f191820 100644 --- a/2.Design/ROADMAP-M0-M1.md +++ b/2.Design/ROADMAP-M0-M1.md @@ -292,7 +292,7 @@ | 3.9 | 安卓知识列表 | 1.0 | [x] 只读列表 + 详情动作(编辑正文走安卓编辑器) | | 3.10 | 文档与 e2e | 1.0 | [x] `knowledge.mjs` 7 项 + `retrieval.mjs` 6 项 | | 3.11 | 知识分类:工作区三级小树(`knowledge_category` + 迁移 0007 + RLS),知识可归一类,列表按分类子树 / 未分类 / 标签筛选 | 1.0 | [x] 增删改移 + 重名 `7008`、超三级 `7007`、删分类要 `force` | -| 3.12 | 备忘录形态:新建知识一次写全(标题/类型/分类/标签/正文),不再是"填个标题就完成";抽屉加宽 + 可整屏 | 1.0 | [x] Web `KnowledgeComposer` + 安卓大对话框;`Sheet` 增 `size="md/lg/xl"` 与展开按钮;截图 `2.Design/screens/knowledge-composer.png`、`knowledge-categories.png` | +| 3.12 | 备忘录形态:新建知识一次写全(标题/类型/分类/标签/正文),不再是"填个标题就完成";抽屉加宽 + 可整屏 | 1.0 | [x] Web `KnowledgeComposer` + 安卓新建后直进编辑器;正文就是详情同款富文本编辑器(Vditor / Compose 编辑器),未保存前不能贴图;`Sheet` 增 `size="md/lg/xl"` 与展开按钮;截图 `2.Design/screens/knowledge-composer.png`、`knowledge-categories.png` | ### M3 完成定义(DOD) - [x] 一件事办完 → 自动出现一份草稿 → 核对 → 发布,全程不用手动复制粘贴 diff --git a/2.Design/screens/knowledge-composer.png b/2.Design/screens/knowledge-composer.png index 5d41f43..9e4b22a 100644 Binary files a/2.Design/screens/knowledge-composer.png and b/2.Design/screens/knowledge-composer.png differ diff --git a/android/app/src/main/java/com/dadao/task/ui/KnowledgeScreen.kt b/android/app/src/main/java/com/dadao/task/ui/KnowledgeScreen.kt index 34c9855..05bc40e 100644 --- a/android/app/src/main/java/com/dadao/task/ui/KnowledgeScreen.kt +++ b/android/app/src/main/java/com/dadao/task/ui/KnowledgeScreen.kt @@ -272,7 +272,16 @@ fun KnowledgeScreen( scope.launch { loadCategories() reload() - open = created + // 正文交给富文本编辑器写:先落一条草稿,再直接进去写,不让用户对着纯文本框凑排版 + onEdit( + EditRequest( + itemId = created.id, + title = created.title, + body = created.bodyMd, + version = created.version, + knowledge = true, + ), + ) } }, onFail = { fail(it) }, @@ -307,7 +316,6 @@ private fun KnowledgeComposerDialog( var docType by remember { mutableStateOf("note") } var categoryId by remember { mutableStateOf(null) } var tags by remember { mutableStateOf("") } - var body by remember { mutableStateOf("") } var saving by remember { mutableStateOf(false) } Dialog( @@ -339,7 +347,7 @@ private fun KnowledgeComposerDialog( api.createKnowledge( workspace.id, title.trim(), - body, + "", docType, parseTags(tags), categoryId, @@ -426,19 +434,11 @@ private fun KnowledgeComposerDialog( ), ) Spacer(Modifier.height(10.dp)) - SectionLabel("正文 · Markdown") - OutlinedTextField( - value = body, - onValueChange = { body = it }, - modifier = Modifier.fillMaxWidth().heightIn(min = 240.dp), - placeholder = { Text("背景、做法、结论……写清楚以后自己还能看懂", color = InkMuted) }, - shape = RoundedCornerShape(10.dp), - colors = TextFieldDefaults.colors( - focusedContainerColor = Color.White, - unfocusedContainerColor = Color.White, - focusedIndicatorColor = Primary, - unfocusedIndicatorColor = Line, - ), + SectionLabel("正文") + Text( + "点右上「保存」先把这张备忘存成草稿,随后会直接打开编辑器写正文——排版、表格、贴图都在编辑器里。", + style = MaterialTheme.typography.labelSmall, + color = InkMuted, ) Spacer(Modifier.height(12.dp)) } diff --git a/web/e2e/knowledge-notebook.mjs b/web/e2e/knowledge-notebook.mjs index 769d94d..c22075a 100644 --- a/web/e2e/knowledge-notebook.mjs +++ b/web/e2e/knowledge-notebook.mjs @@ -82,9 +82,22 @@ try { await page.getByLabel("类型").selectOption("howto"); await page.getByLabel("分类", { exact: true }).selectOption({ label: "研发 / 部署" }); await page.getByLabel("标签").fill("发布 事故"); - await page - .getByLabel("正文") - .fill("## 步骤\n1. 停掉灰度流量\n2. 回滚镜像到上一个 tag\n3. 验证关键接口"); + // 正文是富文本编辑器(Vditor),不是 textarea:先等它挂载,再用真实键盘输入 + const composerBody = page.locator('.vditor-ir [contenteditable="true"]').last(); + await composerBody.waitFor({ timeout: 20_000 }); + await composerBody.click(); + await page.keyboard.type("## 步骤", { delay: 10 }); + await page.keyboard.press("Enter"); + await page.keyboard.type("1. 停掉灰度流量", { delay: 10 }); + await page.keyboard.press("Enter"); + await page.keyboard.type("回滚镜像到上一个 tag", { delay: 10 }); + await page.keyboard.press("Enter"); + await page.keyboard.type("验证关键接口", { delay: 10 }); + check( + "新建面板的正文是富文本编辑器", + (await composerBody.getAttribute("aria-label")) === "正文", + ); + await page.screenshot({ path: "e2e/artifacts/knowledge-composer-editor.png" }); await page.getByRole("button", { name: "保存并继续写", exact: true }).click(); // 保存后直接打开详情:标题、分类路径、标签都在 diff --git a/web/src/components/RichTextEditor.tsx b/web/src/components/RichTextEditor.tsx index b417d81..ae9d00e 100644 --- a/web/src/components/RichTextEditor.tsx +++ b/web/src/components/RichTextEditor.tsx @@ -18,7 +18,8 @@ interface Props { onChange: (next: string) => void; /** 图片要落到具体条目下,所以这两个是必填 */ wsId: string; - itemId: string; + /** 新建场景还没有条目 id:正文照样能写,只是暂时不能贴图(贴图前必须先存草稿) */ + itemId?: string; placeholder?: string; disabled?: boolean; /** 编辑器初始高度(px) */ @@ -112,7 +113,8 @@ export function RichTextEditor({ "line", "code", "inline-code", - "upload", + // 没有条目 id 就没有上传落点,索性不摆这个按钮,免得点了才报错 + ...(itemId ? ["upload"] : []), "table", "|", "undo", @@ -129,6 +131,10 @@ export function RichTextEditor({ accept: "image/*", multiple: true, handler: async (files: File[]) => { + if (!itemId) { + onErrorRef.current?.("先保存这条内容,再回来贴图或传附件"); + return null; + } const images = files.filter((file) => file.type.startsWith("image/")); if (images.length === 0) { onErrorRef.current?.("只能插入图片"); @@ -211,7 +217,9 @@ export function RichTextEditor({

{uploading ? "正在上传图片…" - : "支持直接粘贴或拖入图片;内容按 Markdown 保存,模型和导出都读得懂"} + : itemId + ? "支持直接粘贴或拖入图片;内容按 Markdown 保存,模型和导出都读得懂" + : "内容按 Markdown 保存;存下来之后就能直接贴图、传附件"}

); diff --git a/web/src/pages/Knowledge.tsx b/web/src/pages/Knowledge.tsx index cbf45c1..85dae90 100644 --- a/web/src/pages/Knowledge.tsx +++ b/web/src/pages/Knowledge.tsx @@ -707,17 +707,21 @@ function KnowledgeComposer({ /> -