feat(knowledge): 「全部」排到 tab 第一位并默认选中,正文编辑区跟着屏高走
CI / backend (push) Canceled after 0s

- 知识列表 tab 顺序改成 全部 / 待核对 / 已发布 / 已归档,默认停在「全部」:
  进知识库先看"我这儿都有些什么",待核对是待办,不该当门面。
- 正文编辑器不再是写死的 240px:新增 useEditorHeight(reserve),
  按 window.innerHeight 减掉标题/分类/标签/附件/底部按钮算高度
  (新建留 420、编辑留 340,最低 360、最高 960,窗口 resize 时重算),
  新建面板与详情编辑两处共用,长文一屏能写完一段。
- 截图脚本 shots-knowledge-view.mjs 支持 EDIT=1 出编辑态图、ALL=1 留在默认 tab;
  新增 2.Design/screens/knowledge-tab-all.png、knowledge-edit-tall.png。
- 验证:tsc --noEmit 通过、vite build ✓、e2e knowledge.mjs 9 项与
  knowledge-notebook.mjs 17 项全绿(含"侧栏分类按树排""已发布也有编辑")。
This commit is contained in:
Steven
2026-10-31 02:25:25 +08:00
parent b29286778f
commit 50227c2114
5 changed files with 42 additions and 5 deletions
+1
View File
@@ -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] 一件事办完 → 自动出现一份草稿 → 核对 → 发布,全程不用手动复制粘贴
Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

+11 -1
View File
@@ -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 });
// 导入的备忘都是发布过的,切到「已发布」才看得到
// 导入的备忘都是发布过的,切到「已发布」才看得到;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");
+29 -3
View File
@@ -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<string, string> = {
@@ -59,9 +60,29 @@ const ACTION_LABEL: Record<string, string> = {
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<Knowledge[]>([]);
const [cursor, setCursor] = useState<string | null>(null);
@@ -576,6 +597,8 @@ function KnowledgeComposer({
const [busy, setBusy] = useState(false);
const [error, setError] = useState<string | null>(null);
const [notice, setNotice] = useState<string | null>(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<Attachment[]>([]);
/** 编辑态上层还有标题、分类/标签、附件与底部按钮,正文吃掉剩下的大半屏 */
const editorHeight = useEditorHeight(340);
/** 附件 id → 带签名的直链(见 lib/attachmentUrls.ts) */
const resources = useMemo(() => {
const map: Record<string, string> = {};
@@ -1108,6 +1133,7 @@ function KnowledgeDrawer({
}
onError={(message) => setError(message)}
disabled={!canEdit}
minHeight={editorHeight}
/>
</div>