Skip to content

Commit e918a96

Browse files
committed
refactor(ui): 拆 NoteListView 第 7 步 — 列表体展示件
1 parent e473c62 commit e918a96

2 files changed

Lines changed: 134 additions & 47 deletions

File tree

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/**
2+
* NoteListBody — 笔记列表体(滚动容器 + 节/行装配;批 0-C2·Task 2 自 NoteListView 抽出)。
3+
*
4+
* @ai-context: 展示适配器——只做「透传 + 一层组合」,**不含业务逻辑**(逻辑在
5+
* hooks/useNoteSections、useNoteMoves、useNoteListSelection 与
6+
* utils/noteSectionModel)。行 id / data-testid 契约由 NoteListRow 与
7+
* NoteTreeSection 承载,本文件不新增 DOM 属性、不改文案。
8+
* @ai-context: 硬点——① 行色条 accent 由 paletteHex(noteColors[id], theme) 现场解析
9+
* (与拆分前同一表达式);② 平铺分支渲染 `notes.map(renderRow)` 而
10+
* 树分支渲染 `sec.items.map(renderRow)` —— 二者今日等价(flat 节的 items
11+
* 就是 notes),**勿"顺手统一"**(`sec.items` 走的是 orderScopeNotes 短路,
12+
* 改动会牵动重排语义);③ 折叠只由 NoteTreeSection 的 folded 决定,
13+
* 本文件不裁剪 sec.items。
14+
* @ai-context: 边界——无副作用、无内部状态;空态(无节)渲染「暂无笔记」与拆分前同款。
15+
*/
16+
import type { Note } from "../types";
17+
import type { ThemeMode } from "../utils/colorPalette";
18+
import { paletteHex } from "../utils/colorPalette";
19+
import type { SectionData } from "../utils/noteSectionModel";
20+
import NoteListRow from "./NoteListRow";
21+
import NoteTreeSection from "./NoteTreeSection";
22+
23+
interface Props {
24+
/** 展示节(树=未分组+各组顺次;平铺=单 flat 节) */
25+
sections: SectionData[];
26+
/** 平铺分支的行源(经备注 ② —— 故意不用 sec.items) */
27+
notes: Note[];
28+
/** 折叠态(裸键:`String(groupId)` / `"none"`) */
29+
groupFolds: Record<string, boolean>;
30+
groupFilter: number | null;
31+
/** scope → 手排有序 ids(组头「手排 ↺」徽标判据) */
32+
manualOrders: Record<string, number[]>;
33+
selection: ReadonlySet<number>;
34+
selectedId: number | null;
35+
theme: ThemeMode;
36+
noteColors?: Record<number, string | null>;
37+
tagColors?: Record<string, string>;
38+
/** 行单击(批量模式下由 hook 改判勾选) */
39+
onOpen: (note: Note) => void;
40+
/** Ctrl/⌘=加减选;Shift=可见序区间选 */
41+
onModifierClick: (note: Note, ctrl: boolean, shift: boolean) => void;
42+
/** 行间落点(组内手动排序;钩子在父层判定启用/禁入) */
43+
onDropOnRow: (ids: number[], targetId: number, before: boolean) => void;
44+
/** 行右键(父层按多选语义分派单行菜单 / 选集菜单) */
45+
onContextMenu: (e: React.MouseEvent, note: Note) => void;
46+
onOpenSession: (sessionId: number) => void;
47+
/** 组名点击=过滤切换(父层裁决 决策 1 语义) */
48+
onGroupFilterChange?: (id: number | null) => void;
49+
/** 组折叠翻转(裸键) */
50+
onToggleFold: (key: string) => void;
51+
/** 一键回自动排序(清 note_orders scope) */
52+
onResetManual: (scope: string) => void;
53+
/** 拖入组头 → 归组 */
54+
onDropNotes: (ids: number[], groupId: number | null) => void;
55+
/** 组头空白按下 → 划选起点 */
56+
onMarqueeStart: (scope: string) => void;
57+
}
58+
59+
export default function NoteListBody({
60+
sections, notes, groupFolds, groupFilter, manualOrders, selection, selectedId, theme,
61+
noteColors, tagColors, onOpen, onModifierClick, onDropOnRow, onContextMenu, onOpenSession,
62+
onGroupFilterChange, onToggleFold, onResetManual, onDropNotes, onMarqueeStart,
63+
}: Props) {
64+
const rowAccent = (n: Note) => paletteHex(noteColors?.[n.id] ?? null, theme);
65+
66+
const renderRow = (n: Note) => (
67+
<NoteListRow
68+
key={n.id}
69+
note={n}
70+
accent={rowAccent(n)}
71+
openId={selectedId}
72+
multiSelected={selection.has(n.id)}
73+
tagColors={tagColors}
74+
onOpen={onOpen}
75+
onModifierClick={onModifierClick}
76+
dragIds={selection.size > 0 && selection.has(n.id) ? [...selection] : []}
77+
onDropOnRow={onDropOnRow}
78+
onOpenSession={onOpenSession}
79+
onContextMenu={onContextMenu}
80+
/>
81+
);
82+
83+
return (
84+
<div style={{ flex: 1, overflowY: "auto" }}>
85+
{sections.length === 0 && <p style={{ fontSize: 12, color: "#9ca3af", textAlign: "center", marginTop: 24 }}>暂无笔记</p>}
86+
{sections.map((sec) => (
87+
sec.scope === "flat" ? (
88+
<div key="flat">{notes.map(renderRow)}</div>
89+
) : (
90+
<NoteTreeSection
91+
key={sec.scope}
92+
title={sec.title}
93+
count={sec.items.length}
94+
accent={sec.accent}
95+
active={sec.groupId === null ? groupFilter === null : groupFilter === sec.groupId}
96+
folded={groupFolds[sec.groupId == null ? "none" : String(sec.groupId)] === true}
97+
onToggleFold={() => onToggleFold(sec.groupId == null ? "none" : String(sec.groupId))}
98+
onSelectTitle={() => onGroupFilterChange?.(sec.groupId === null ? null : (groupFilter === sec.groupId ? null : sec.groupId))}
99+
manual={!!manualOrders[sec.scope]}
100+
onResetManual={() => onResetManual(sec.scope)}
101+
onDropNotes={(ids) => onDropNotes(ids, sec.groupId)}
102+
onMarqueeStart={() => onMarqueeStart(sec.scope)}
103+
>
104+
{sec.items.map(renderRow)}
105+
</NoteTreeSection>
106+
)
107+
))}
108+
</div>
109+
);
110+
}

‎app/src/components/NoteListView.tsx‎

Lines changed: 24 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
*/
1616
import { useCallback, useMemo } from "react";
1717
import type { Note, NoteGroup } from "../types";
18-
import { paletteHex } from "../utils/colorPalette";
1918
import type { ThemeMode } from "../utils/colorPalette";
2019
// 批 0-C2:展示节/可见序纯函数层(含 scope 键与裸折叠键派生——纯逻辑与副作用分离)
2120
import { manualBaseIds, scopeKey } from "../utils/noteSectionModel";
@@ -29,8 +28,8 @@ import { useNoteMoves } from "../hooks/useNoteMoves";
2928
import { useNoteListSelection } from "../hooks/useNoteListSelection";
3029
// 批 0-C2:划选(组头空白起手;window 监听 + elementFromPoint 命中——整块搬迁)
3130
import { useNoteMarquee } from "../hooks/useNoteMarquee";
32-
import NoteListRow from "./NoteListRow";
33-
import NoteTreeSection from "./NoteTreeSection";
31+
// 批 0-C2:展示件(列表体 / 顶栏 / 批量栏与选集菜单)——纯透传适配器,逻辑在 hook
32+
import NoteListBody from "./NoteListBody";
3433
import NoteRowContextMenu from "./NoteRowContextMenu";
3534

3635
// 兼容既有导入面(NoteReadingView/NotesPage/parseTags.test 从此解析——v0.15 移厝 utils)
@@ -117,25 +116,6 @@ export default function NoteListView({
117116
// 划选(组头空白起手:window 四路监听 + elementFromPoint 行命中)——整块搬迁见 hooks/useNoteMarquee
118117
const { startMarquee } = useNoteMarquee({ sections, visibleOrder, groupFolds, setSelection });
119118

120-
const rowAccent = (n: Note) => paletteHex(noteColors?.[n.id] ?? null, theme);
121-
122-
const renderRow = (n: Note) => (
123-
<NoteListRow
124-
key={n.id}
125-
note={n}
126-
accent={rowAccent(n)}
127-
openId={selectedId}
128-
multiSelected={selection.has(n.id)}
129-
tagColors={tagColors}
130-
onOpen={handleOpen}
131-
onModifierClick={handleModifierClick}
132-
dragIds={selection.size > 0 && selection.has(n.id) ? [...selection] : []}
133-
onDropOnRow={handleDropOnRow}
134-
onOpenSession={onOpenSession}
135-
onContextMenu={openRowContextMenu}
136-
/>
137-
);
138-
139119
/**
140120
* 右键菜单「上移/下移」可用性(REQ-315):仅树视图 scope 上下文开放(交互矩阵:
141121
* 平铺/搜索/标签/非默认排序禁移动——移动=scope 级手排快照,过滤结果是子集非
@@ -202,31 +182,28 @@ export default function NoteListView({
202182
)}
203183
</div>
204184

205-
<div style={{ flex: 1, overflowY: "auto" }}>
206-
{sections.length === 0 && <p style={{ fontSize: 12, color: "#9ca3af", textAlign: "center", marginTop: 24 }}>暂无笔记</p>}
207-
{sections.map((sec) => (
208-
sec.scope === "flat" ? (
209-
<div key="flat">{notes.map(renderRow)}</div>
210-
) : (
211-
<NoteTreeSection
212-
key={sec.scope}
213-
title={sec.title}
214-
count={sec.items.length}
215-
accent={sec.accent}
216-
active={sec.groupId === null ? groupFilter === null : groupFilter === sec.groupId}
217-
folded={groupFolds[sec.groupId == null ? "none" : String(sec.groupId)] === true}
218-
onToggleFold={() => toggleGroupFold(sec.groupId == null ? "none" : String(sec.groupId))}
219-
onSelectTitle={() => onGroupFilterChange?.(sec.groupId === null ? null : (groupFilter === sec.groupId ? null : sec.groupId))}
220-
manual={!!manualOrders[sec.scope]}
221-
onResetManual={() => void resetOrder(sec.scope)}
222-
onDropNotes={(ids) => void moveToGroup(ids, sec.groupId)}
223-
onMarqueeStart={() => startMarquee(sec.scope)}
224-
>
225-
{sec.items.map(renderRow)}
226-
</NoteTreeSection>
227-
)
228-
))}
229-
</div>
185+
<NoteListBody
186+
sections={sections}
187+
notes={notes}
188+
groupFolds={groupFolds}
189+
groupFilter={groupFilter}
190+
manualOrders={manualOrders}
191+
selection={selection}
192+
selectedId={selectedId}
193+
theme={theme}
194+
noteColors={noteColors}
195+
tagColors={tagColors}
196+
onOpen={handleOpen}
197+
onModifierClick={handleModifierClick}
198+
onDropOnRow={handleDropOnRow}
199+
onContextMenu={openRowContextMenu}
200+
onOpenSession={onOpenSession}
201+
onGroupFilterChange={onGroupFilterChange}
202+
onToggleFold={toggleGroupFold}
203+
onResetManual={resetOrder}
204+
onDropNotes={moveToGroup}
205+
onMarqueeStart={startMarquee}
206+
/>
230207

231208
{/* 批量操作栏(去勾选框后的批量入口:删除 + 移动到组;选择模式下同样可用) */}
232209
{selection.size > 0 && (

0 commit comments

Comments
 (0)