From ef1be6d04f4039527d1231a562d8d5a189cdbaea Mon Sep 17 00:00:00 2001 From: SpecialX <47072643+wangxiner55@users.noreply.github.com> Date: Sat, 4 Jul 2026 11:16:05 +0800 Subject: [PATCH] =?UTF-8?q?refactor(lesson-preparation):=20editor-slice=20?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E7=94=BB=E5=B8=83=E4=BE=9D=E8=B5=96=EF=BC=88?= =?UTF-8?q?V4=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lesson-preparation/hooks/editor-slice.ts | 137 +++--------------- 1 file changed, 20 insertions(+), 117 deletions(-) diff --git a/src/modules/lesson-preparation/hooks/editor-slice.ts b/src/modules/lesson-preparation/hooks/editor-slice.ts index 70a10e3..a0aa423 100644 --- a/src/modules/lesson-preparation/hooks/editor-slice.ts +++ b/src/modules/lesson-preparation/hooks/editor-slice.ts @@ -1,12 +1,9 @@ import type { StateCreator } from "zustand"; import { createId } from "@paralleldrive/cuid2"; import type { - AnchorEdge, AnchorType, - AnyLessonPlanEdge, Block, BlockType, - FlowEdge, LessonPlanDocument, LessonPlanNode, NodeAnchor, @@ -14,7 +11,6 @@ import type { TextbookContentNodeData, } from "../types"; import { defaultDataForType } from "../lib/document-migration"; -import { computeAutoLayout } from "../lib/auto-layout"; import type { EditorState } from "./use-lesson-plan-editor"; export interface EditorSlice { @@ -25,24 +21,27 @@ export interface EditorSlice { setPlanId: (planId: string) => void; addNode: (type: BlockType, position?: { x: number; y: number }, title?: string) => string; updateNode: (id: string, patch: Omit, "type">) => void; - updateNodePosition: (id: string, position: { x: number; y: number }) => void; removeNode: (id: string) => void; updateTextbookContent: (data: Partial) => void; getTextbookContentNode: () => TextbookContentNode | undefined; + /** + * V4:添加锚点(不再传 start/end,锚点位置由 Tiptap Mark 内嵌)。 + * + * 兼容说明:旧调用方可能传入 { nodeId, type, start, end, textPreview }, + * 这些 v3 字段会被忽略,等阶段 H 改造调用方时再清理。 + */ addAnchor: (params: { nodeId: string; type: AnchorType; - start: number; + /** @deprecated v3 字段,v4 已由 Tiptap Mark 承载,忽略 */ + start?: number; + /** @deprecated v3 字段,v4 忽略 */ end?: number; + /** @deprecated v3 字段,v4 忽略 */ textPreview?: string; }) => string; removeAnchor: (anchorId: string) => void; updateAnchor: (anchorId: string, patch: Partial) => void; - connect: (source: string, target: string) => void; - disconnect: (edgeId: string) => void; - setEdges: (edges: AnyLessonPlanEdge[]) => void; - /** V5-8:自动布局,使用 dagre 计算并应用新位置 */ - autoLayout: (direction?: "TB" | "LR") => void; } function reindex(nodes: LessonPlanNode[]): LessonPlanNode[] { @@ -58,11 +57,12 @@ export const createEditorSlice: StateCreator< planId: "", title: "", doc: { - version: 3, + version: 4, textbookContentNodeId: "", nodes: [], edges: [], anchors: [], + expandedNodeIds: [], }, setTitle: (title) => { @@ -71,7 +71,7 @@ export const createEditorSlice: StateCreator< }, setPlanId: (planId) => set({ planId }), - addNode: (type, position, title) => { + addNode: (type, _position, title) => { const id = createId(); const state = get(); state.pushHistory(); // V5-2:撤销/重做 @@ -85,10 +85,7 @@ export const createEditorSlice: StateCreator< title: title ?? type, data: defaultDataForType(type), order: nodeCount, - position: position ?? { - x: 80 + (nodeCount % 4) * 280, - y: 80 + Math.floor(nodeCount / 4) * 200, - }, + position: { x: 0, y: 0 }, // V4:不再使用,保留字段兼容 }; set((s) => ({ doc: { ...s.doc, nodes: [...s.doc.nodes, node] }, @@ -115,24 +112,6 @@ export const createEditorSlice: StateCreator< })); }, - updateNodePosition: (id, position) => { - // V5-2 注:拖拽过程中会产生大量 position 更新,仅在拖拽开始时推一次 history。 - // 调用方应在 onDragStart 时调用 pushHistory,此处不重复推入。 - set((s) => ({ - doc: { - ...s.doc, - nodes: s.doc.nodes.map((n) => - n.id === id - ? n.type === "textbook_content" - ? ({ ...n, position } as TextbookContentNode) - : ({ ...n, position } as LessonPlanNode) - : n, - ), - }, - isDirty: true, - })); - }, - removeNode: (id) => { get().pushHistory(); // V5-2:撤销/重做 set((s) => { @@ -184,38 +163,19 @@ export const createEditorSlice: StateCreator< ); }, - addAnchor: ({ nodeId, type, start, end, textPreview }) => { + addAnchor: ({ nodeId, type }) => { const anchorId = createId(); - const state = get(); - state.pushHistory(); // V5-2:撤销/重做 - const textbookNodeId = state.doc.textbookContentNodeId; - - const anchor: NodeAnchor = { - id: anchorId, - nodeId, - type, - start, - ...(end !== undefined ? { end } : {}), - ...(textPreview ? { textPreview } : {}), - }; - - const edge: AnchorEdge = { - id: `ae_${nodeId}_${textbookNodeId}_${anchorId.slice(0, 6)}`, - source: nodeId, - target: textbookNodeId, - type: "anchor", - anchorId, - }; - + get().pushHistory(); // V5-2:撤销/重做 set((s) => ({ doc: { ...s.doc, - anchors: [...s.doc.anchors, anchor], - edges: [...s.doc.edges, edge], + anchors: [ + ...s.doc.anchors, + { id: anchorId, nodeId, type }, + ], }, isDirty: true, })); - return anchorId; }, @@ -245,61 +205,4 @@ export const createEditorSlice: StateCreator< isDirty: true, })); }, - - connect: (source, target) => { - get().pushHistory(); // V5-2:撤销/重做 - set((s) => { - if ( - s.doc.edges.some((e) => e.source === source && e.target === target) - ) - return s; - const edge: FlowEdge = { - id: `e_${source}_${target}_${createId().slice(0, 6)}`, - source, - target, - type: "flow", - }; - return { - doc: { ...s.doc, edges: [...s.doc.edges, edge] }, - isDirty: true, - }; - }); - }, - - disconnect: (edgeId) => { - get().pushHistory(); // V5-2:撤销/重做 - set((s) => ({ - doc: { - ...s.doc, - edges: s.doc.edges.filter((e) => e.id !== edgeId), - }, - isDirty: true, - })); - }, - - setEdges: (edges) => { - get().pushHistory(); // V5-2:撤销/重做 - set((s) => ({ doc: { ...s.doc, edges }, isDirty: true })); - }, - - autoLayout: (direction = "TB") => { - const state = get(); - const positions = computeAutoLayout(state.doc.nodes, state.doc.edges, { direction }); - if (positions.size === 0) return; - state.pushHistory(); // V5-2:撤销/重做 - set((s) => ({ - doc: { - ...s.doc, - nodes: s.doc.nodes.map((n) => { - const p = positions.get(n.id); - return p - ? n.type === "textbook_content" - ? ({ ...n, position: p } as TextbookContentNode) - : ({ ...n, position: p } as LessonPlanNode) - : n; - }), - }, - isDirty: true, - })); - }, });