diff --git a/2.Design/ROADMAP-M0-M1.md b/2.Design/ROADMAP-M0-M1.md index 67acaa1..ec2ad61 100644 --- a/2.Design/ROADMAP-M0-M1.md +++ b/2.Design/ROADMAP-M0-M1.md @@ -295,6 +295,7 @@ | 3.12 | 备忘录形态:新建知识一次写全(标题/类型/分类/标签/正文),不再是"填个标题就完成";抽屉加宽 + 可整屏 | 1.0 | [x] Web `KnowledgeComposer` + 安卓新建后直进编辑器;正文就是详情同款富文本编辑器(Vditor / Compose 编辑器);`Sheet` 增 `size="md/lg/xl"` 与展开按钮;截图 `2.Design/screens/knowledge-composer.png`、`knowledge-categories.png` | | 3.13 | 知识能挂附件:新建面板里直接上传(懒建草稿拿到条目 id 再传),详情里能看、能下载/打开 | 1.0 | [x] Web `AttachmentPanel` + `RichTextEditor.ensureItemId`(贴图/传附件前自动存草稿);安卓详情页附件列表 + `OpenDocument` 上传 + 浏览器打开;附件仍落在 `item` 上(`ALLOWED_KINDS` 含 knowledge) | | 3.14 | 点开一条知识默认是「看」:Markdown 渲染成正文样式;要改再点「编辑」进编辑器,取消即丢弃未保存改动 | 1.0 | [x] `KnowledgeDrawer` 分 `editing` 两态(只读用 `Markdown` + `prose-dadao`,编辑才挂 `RichTextEditor`);**已发布也能编辑**(PATCH 后自动重建检索切片),归档的要先「恢复成草稿」;e2e 断言默认 `.vditor-ir` 为 0 且已发布也有「编辑」;截图 `2.Design/screens/knowledge-read-view.png` | +| 3.15 | 知识列表 tab 顺序与编辑区高度:「全部」排第一并默认选中,正文编辑器占满剩余屏高 | 1.0 | [x] `TABS` 改 `all/draft/published/archived`,`tab` 默认 `"all"`;`useEditorHeight(reserve)` 按窗口高度算正文区(新建 420、编辑 340 留白,最低 360、最高 960),新建与编辑两处共用;截图 `2.Design/screens/knowledge-tab-all.png`、`knowledge-edit-tall.png` | ### M3 完成定义(DOD) - [x] 一件事办完 → 自动出现一份草稿 → 核对 → 发布,全程不用手动复制粘贴 diff --git a/2.Design/screens/knowledge-edit-tall.png b/2.Design/screens/knowledge-edit-tall.png new file mode 100644 index 0000000..dd95f47 Binary files /dev/null and b/2.Design/screens/knowledge-edit-tall.png differ diff --git a/2.Design/screens/knowledge-tab-all.png b/2.Design/screens/knowledge-tab-all.png new file mode 100644 index 0000000..bc88e6d Binary files /dev/null and b/2.Design/screens/knowledge-tab-all.png differ diff --git a/web/e2e/shots-knowledge-view.mjs b/web/e2e/shots-knowledge-view.mjs index c5b90fe..b6ebcc9 100644 --- a/web/e2e/shots-knowledge-view.mjs +++ b/web/e2e/shots-knowledge-view.mjs @@ -72,8 +72,10 @@ await page.addInitScript( ); await page.goto(`${WEB}/w/${ws.id}/knowledge`, { waitUntil: "networkidle" }); await page.getByRole("heading", { name: "知识" }).waitFor({ timeout: 20_000 }); -// 导入的备忘都是发布过的,切到「已发布」才看得到 -await page.getByRole("button", { name: "已发布", exact: true }).first().click(); +// 导入的备忘都是发布过的,切到「已发布」才看得到;ALL=1 时留在默认的「全部」 +if (!process.env.ALL) { + await page.getByRole("button", { name: "已发布", exact: true }).first().click(); +} if (process.env.TITLE) { // 挑一条带标题/列表的,截图里才看得出 Markdown 真的渲染了 const target = page.locator("li").filter({ hasText: process.env.TITLE }).first(); @@ -90,5 +92,13 @@ await page.locator(".prose-dadao").first().waitFor({ timeout: 20_000 }); await page.waitForTimeout(600); await page.screenshot({ path: "e2e/artifacts/memo-read.png" }); +if (process.env.EDIT) { + // 编辑态:正文编辑器要占满剩下的高度,不能只露几行 + await page.getByRole("button", { name: "编辑", exact: true }).last().click(); + await page.locator(".vditor-ir [contenteditable]").first().waitFor({ timeout: 20_000 }); + await page.waitForTimeout(800); + await page.screenshot({ path: "e2e/artifacts/memo-edit.png" }); +} + await browser.close(); console.log("OK"); diff --git a/web/src/pages/Knowledge.tsx b/web/src/pages/Knowledge.tsx index f4aa157..db4f09a 100644 --- a/web/src/pages/Knowledge.tsx +++ b/web/src/pages/Knowledge.tsx @@ -23,11 +23,12 @@ import type { SearchHit, } from "../lib/types"; +// 「全部」放第一位:进知识库第一眼是"我这儿都有些什么",而不是先看一堆待核对 const TABS = [ + { key: "all", label: "全部", statuses: [] }, { key: "draft", label: "待核对", statuses: ["draft", "in_review"] }, { key: "published", label: "已发布", statuses: ["published"] }, { key: "archived", label: "已归档", statuses: ["archived"] }, - { key: "all", label: "全部", statuses: [] }, ] as const; const DOC_TYPE_LABEL: Record = { @@ -59,9 +60,29 @@ const ACTION_LABEL: Record = { restore: "恢复成草稿", }; +/** + * 正文编辑区的高度跟着窗口走。 + * 知识是长文,编辑器默认 240px 只露出几行,写一段就得滚一次; + * 把「窗口高度减去标题/分类/标签/底部按钮」剩下的都给正文,屏幕越大写得越舒展。 + */ +function useEditorHeight(reserve: number, floor = 360, ceiling = 960) { + const measure = () => + Math.max(floor, Math.min(ceiling, (typeof window === "undefined" ? 900 : window.innerHeight) - reserve)); + const [height, setHeight] = useState(measure); + useEffect(() => { + const onResize = () => setHeight(measure()); + onResize(); + window.addEventListener("resize", onResize); + return () => window.removeEventListener("resize", onResize); + // measure 只读 window 与固定参数,不需要进依赖 + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [reserve, floor, ceiling]); + return height; +} + export function KnowledgePage() { const { wsId = "" } = useParams(); - const [tab, setTab] = useState<(typeof TABS)[number]["key"]>("draft"); + const [tab, setTab] = useState<(typeof TABS)[number]["key"]>("all"); const [q, setQ] = useState(""); const [docs, setDocs] = useState([]); const [cursor, setCursor] = useState(null); @@ -576,6 +597,8 @@ function KnowledgeComposer({ const [busy, setBusy] = useState(false); const [error, setError] = useState(null); const [notice, setNotice] = useState(null); + /** 上面还有标题、类型、分类、标签与底部按钮,剩下的高度都给正文 */ + const editorHeight = useEditorHeight(420); /** * 附件与图片都要落在具体条目上,可新建时条目还不存在。 * 解法是懒建草稿:第一次传附件/贴图时才 POST 一条草稿,之后一律 PATCH。 @@ -770,7 +793,7 @@ function KnowledgeComposer({ wsId={wsId} value={body} onChange={setBody} - minHeight={320} + minHeight={editorHeight} ensureItemId={ensureDraft} onError={setError} placeholder={"写下要点、步骤、结论…\n\n标题、类型、分类、标签都在上面,正文就是这张备忘的主体。"} @@ -831,6 +854,8 @@ function KnowledgeDrawer({ const [tags, setTags] = useState(""); const [categoryId, setCategoryId] = useState(""); const [attachments, setAttachments] = useState([]); + /** 编辑态上层还有标题、分类/标签、附件与底部按钮,正文吃掉剩下的大半屏 */ + const editorHeight = useEditorHeight(340); /** 附件 id → 带签名的直链(见 lib/attachmentUrls.ts) */ const resources = useMemo(() => { const map: Record = {}; @@ -1108,6 +1133,7 @@ function KnowledgeDrawer({ } onError={(message) => setError(message)} disabled={!canEdit} + minHeight={editorHeight} />