「记一条」面板里正文是个 textarea,排版、表格、贴图都没有——创建时反而比 详情页更寒酸。现在新建面板直接用详情同款 Vditor 编辑器,创建与修改一个模样。 - `RichTextEditor` 的 `itemId` 改为可选:新建时还没有条目 id,正文照写, 只是上传按钮不摆出来;真去贴图会提示「先保存这条内容,再回来贴图」。 - `KnowledgeComposer` 的正文换用 `RichTextEditor`(minHeight 320),页脚提示 按有没有条目 id 给不同文案。 - 安卓:新建对话框不再放纯文本正文框,保存后直接进 `RichTextEditorScreen` 写正文(排版、表格、贴图都在那儿),写完回到列表并展开详情。 - e2e 增加一条断言:新建面板里的正文就是富文本编辑器(aria-label=正文 的 contenteditable),正文按真实键盘输入后 Markdown 正常落库。 验证:`tsc --noEmit` 通过、`vite build` 通过、`knowledge-notebook.mjs` 11 项 全绿(新增「新建面板的正文是富文本编辑器」)。安卓端本机仍是 JDK 25 + Gradle 8.7/AGP 8.7.3 不兼容,本次未编译验证。
This commit is contained in:
@@ -292,7 +292,7 @@
|
|||||||
| 3.9 | 安卓知识列表 | 1.0 | [x] 只读列表 + 详情动作(编辑正文走安卓编辑器) |
|
| 3.9 | 安卓知识列表 | 1.0 | [x] 只读列表 + 详情动作(编辑正文走安卓编辑器) |
|
||||||
| 3.10 | 文档与 e2e | 1.0 | [x] `knowledge.mjs` 7 项 + `retrieval.mjs` 6 项 |
|
| 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.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)
|
### M3 完成定义(DOD)
|
||||||
- [x] 一件事办完 → 自动出现一份草稿 → 核对 → 发布,全程不用手动复制粘贴
|
- [x] 一件事办完 → 自动出现一份草稿 → 核对 → 发布,全程不用手动复制粘贴
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 71 KiB |
@@ -272,7 +272,16 @@ fun KnowledgeScreen(
|
|||||||
scope.launch {
|
scope.launch {
|
||||||
loadCategories()
|
loadCategories()
|
||||||
reload()
|
reload()
|
||||||
open = created
|
// 正文交给富文本编辑器写:先落一条草稿,再直接进去写,不让用户对着纯文本框凑排版
|
||||||
|
onEdit(
|
||||||
|
EditRequest(
|
||||||
|
itemId = created.id,
|
||||||
|
title = created.title,
|
||||||
|
body = created.bodyMd,
|
||||||
|
version = created.version,
|
||||||
|
knowledge = true,
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onFail = { fail(it) },
|
onFail = { fail(it) },
|
||||||
@@ -307,7 +316,6 @@ private fun KnowledgeComposerDialog(
|
|||||||
var docType by remember { mutableStateOf("note") }
|
var docType by remember { mutableStateOf("note") }
|
||||||
var categoryId by remember { mutableStateOf<String?>(null) }
|
var categoryId by remember { mutableStateOf<String?>(null) }
|
||||||
var tags by remember { mutableStateOf("") }
|
var tags by remember { mutableStateOf("") }
|
||||||
var body by remember { mutableStateOf("") }
|
|
||||||
var saving by remember { mutableStateOf(false) }
|
var saving by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Dialog(
|
Dialog(
|
||||||
@@ -339,7 +347,7 @@ private fun KnowledgeComposerDialog(
|
|||||||
api.createKnowledge(
|
api.createKnowledge(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
title.trim(),
|
title.trim(),
|
||||||
body,
|
"",
|
||||||
docType,
|
docType,
|
||||||
parseTags(tags),
|
parseTags(tags),
|
||||||
categoryId,
|
categoryId,
|
||||||
@@ -426,19 +434,11 @@ private fun KnowledgeComposerDialog(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
Spacer(Modifier.height(10.dp))
|
Spacer(Modifier.height(10.dp))
|
||||||
SectionLabel("正文 · Markdown")
|
SectionLabel("正文")
|
||||||
OutlinedTextField(
|
Text(
|
||||||
value = body,
|
"点右上「保存」先把这张备忘存成草稿,随后会直接打开编辑器写正文——排版、表格、贴图都在编辑器里。",
|
||||||
onValueChange = { body = it },
|
style = MaterialTheme.typography.labelSmall,
|
||||||
modifier = Modifier.fillMaxWidth().heightIn(min = 240.dp),
|
color = InkMuted,
|
||||||
placeholder = { Text("背景、做法、结论……写清楚以后自己还能看懂", color = InkMuted) },
|
|
||||||
shape = RoundedCornerShape(10.dp),
|
|
||||||
colors = TextFieldDefaults.colors(
|
|
||||||
focusedContainerColor = Color.White,
|
|
||||||
unfocusedContainerColor = Color.White,
|
|
||||||
focusedIndicatorColor = Primary,
|
|
||||||
unfocusedIndicatorColor = Line,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
Spacer(Modifier.height(12.dp))
|
Spacer(Modifier.height(12.dp))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,9 +82,22 @@ try {
|
|||||||
await page.getByLabel("类型").selectOption("howto");
|
await page.getByLabel("类型").selectOption("howto");
|
||||||
await page.getByLabel("分类", { exact: true }).selectOption({ label: "研发 / 部署" });
|
await page.getByLabel("分类", { exact: true }).selectOption({ label: "研发 / 部署" });
|
||||||
await page.getByLabel("标签").fill("发布 事故");
|
await page.getByLabel("标签").fill("发布 事故");
|
||||||
await page
|
// 正文是富文本编辑器(Vditor),不是 textarea:先等它挂载,再用真实键盘输入
|
||||||
.getByLabel("正文")
|
const composerBody = page.locator('.vditor-ir [contenteditable="true"]').last();
|
||||||
.fill("## 步骤\n1. 停掉灰度流量\n2. 回滚镜像到上一个 tag\n3. 验证关键接口");
|
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();
|
await page.getByRole("button", { name: "保存并继续写", exact: true }).click();
|
||||||
|
|
||||||
// 保存后直接打开详情:标题、分类路径、标签都在
|
// 保存后直接打开详情:标题、分类路径、标签都在
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ interface Props {
|
|||||||
onChange: (next: string) => void;
|
onChange: (next: string) => void;
|
||||||
/** 图片要落到具体条目下,所以这两个是必填 */
|
/** 图片要落到具体条目下,所以这两个是必填 */
|
||||||
wsId: string;
|
wsId: string;
|
||||||
itemId: string;
|
/** 新建场景还没有条目 id:正文照样能写,只是暂时不能贴图(贴图前必须先存草稿) */
|
||||||
|
itemId?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
/** 编辑器初始高度(px) */
|
/** 编辑器初始高度(px) */
|
||||||
@@ -112,7 +113,8 @@ export function RichTextEditor({
|
|||||||
"line",
|
"line",
|
||||||
"code",
|
"code",
|
||||||
"inline-code",
|
"inline-code",
|
||||||
"upload",
|
// 没有条目 id 就没有上传落点,索性不摆这个按钮,免得点了才报错
|
||||||
|
...(itemId ? ["upload"] : []),
|
||||||
"table",
|
"table",
|
||||||
"|",
|
"|",
|
||||||
"undo",
|
"undo",
|
||||||
@@ -129,6 +131,10 @@ export function RichTextEditor({
|
|||||||
accept: "image/*",
|
accept: "image/*",
|
||||||
multiple: true,
|
multiple: true,
|
||||||
handler: async (files: File[]) => {
|
handler: async (files: File[]) => {
|
||||||
|
if (!itemId) {
|
||||||
|
onErrorRef.current?.("先保存这条内容,再回来贴图或传附件");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
const images = files.filter((file) => file.type.startsWith("image/"));
|
const images = files.filter((file) => file.type.startsWith("image/"));
|
||||||
if (images.length === 0) {
|
if (images.length === 0) {
|
||||||
onErrorRef.current?.("只能插入图片");
|
onErrorRef.current?.("只能插入图片");
|
||||||
@@ -211,7 +217,9 @@ export function RichTextEditor({
|
|||||||
<p className="mt-1 text-2xs text-ink-400">
|
<p className="mt-1 text-2xs text-ink-400">
|
||||||
{uploading
|
{uploading
|
||||||
? "正在上传图片…"
|
? "正在上传图片…"
|
||||||
: "支持直接粘贴或拖入图片;内容按 Markdown 保存,模型和导出都读得懂"}
|
: itemId
|
||||||
|
? "支持直接粘贴或拖入图片;内容按 Markdown 保存,模型和导出都读得懂"
|
||||||
|
: "内容按 Markdown 保存;存下来之后就能直接贴图、传附件"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -707,17 +707,21 @@ function KnowledgeComposer({
|
|||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label className="block">
|
<div>
|
||||||
<span className="label">正文</span>
|
<span className="label">正文</span>
|
||||||
<textarea
|
{/* 和详情抽屉同一个富文本编辑器:新建时就该能排版、写表格,而不是先存一个纯文本框 */}
|
||||||
className="input min-h-64 resize-y font-mono text-sm leading-relaxed"
|
<div className="rounded-xl border border-line-200 bg-paper-50 px-3 py-2">
|
||||||
rows={14}
|
<RichTextEditor
|
||||||
placeholder={"可以直接写 Markdown:\n\n## 步骤\n1. 停灰度\n2. 回滚镜像\n3. 验证关键接口"}
|
ariaLabel="正文"
|
||||||
|
wsId={wsId}
|
||||||
value={body}
|
value={body}
|
||||||
onChange={(e) => setBody(e.target.value)}
|
onChange={setBody}
|
||||||
aria-label="正文"
|
minHeight={320}
|
||||||
|
onError={setError}
|
||||||
|
placeholder={"写下要点、步骤、结论…\n\n标题、类型、分类、标签都在上面,正文就是这张备忘的主体。"}
|
||||||
/>
|
/>
|
||||||
</label>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{error && <InlineAlert>{error}</InlineAlert>}
|
{error && <InlineAlert>{error}</InlineAlert>}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user