"use client"; import type { ReactElement } from "react"; import type { BlockData, BlockType, } from "../types"; import { isBlackboardBlockData, isExerciseBlockData, isHomeworkBlockData, isImportBlockData, isKeyPointBlockData, isNewTeachingBlockData, isObjectiveBlockData, isReflectionBlockData, isRichTextBlockData, isSummaryBlockData, isTextStudyBlockData, } from "../lib/type-guards"; import { RichTextBlock } from "../components/blocks/rich-text-block"; import { ExerciseBlock } from "../components/blocks/exercise-block"; import { TextStudyBlock } from "../components/blocks/text-study-block"; import { ObjectiveBlock } from "../components/blocks/objective-block"; import { KeyPointBlock } from "../components/blocks/key-point-block"; import { ImportBlock } from "../components/blocks/import-block"; import { NewTeachingBlock } from "../components/blocks/new-teaching-block"; import { SummaryBlock } from "../components/blocks/summary-block"; import { HomeworkBlock } from "../components/blocks/homework-block"; import { BlackboardBlock } from "../components/blocks/blackboard-block"; import { ReflectionBlock } from "../components/blocks/reflection-block"; /** * Block 注册表:配置驱动渲染。 * 新增 Block 类型只需在此注册,无需修改 NodeEditPanel。 */ export interface BlockRenderProps { blockId: string; data: BlockData; textbookId?: string; chapterId?: string; classes?: { id: string; name: string }[]; /** V5-5:当前课案 ID(仅 RichTextBlock 用于素材库 picker 关联) */ planId?: string; onUpdate: (data: BlockData) => void; } export interface BlockRegistryEntry { /** 是否为富文本类(共享 RichTextBlock 编辑器) */ isRichText?: boolean; } /** * Block 注册表元数据(单一数据源,P0-9 修复)。 * isRichText 属性是判断富文本 block 的唯一权威来源。 * 组件渲染由 BlockRenderer 统一处理,避免在 render 中动态获取组件引用。 */ export const BLOCK_REGISTRY: Record = { objective: {}, key_point: {}, import: {}, new_teaching: {}, consolidation: { isRichText: true }, summary: {}, homework: {}, blackboard: {}, text_study: {}, exercise: {}, rich_text: { isRichText: true }, reflection: {}, }; /** * 判断某 BlockType 是否为富文本类。 * P0-9 修复:统一以 BLOCK_REGISTRY.isRichText 为唯一数据源, * 删除 constants.ts 中的 RICH_TEXT_BLOCK_TYPES 和此处的 RICH_TEXT_TYPES 冗余定义。 */ export function isRichTextBlock(type: BlockType): boolean { return BLOCK_REGISTRY[type]?.isRichText === true; } /** * 静态 Block 渲染组件。 * 根据 type 从注册表查找并渲染对应 Block,所有组件引用均为模块顶层静态声明, * 满足 react-hooks/static-components 规则。 * 新增 Block 类型时,在此 switch 中添加对应 case 即可。 * * V3 修复:使用类型守卫替代 `as` 断言,安全收窄 BlockData 联合类型。 * 类型守卫失败时返回 null(理论上不会发生,因为 type 与 data 由调用方保证一致)。 */ export function BlockRenderer(props: BlockRenderProps & { type: BlockType }): ReactElement | null { const { type, ...rest } = props; switch (type) { case "objective": { if (!isObjectiveBlockData(rest.data)) return null; return ( rest.onUpdate(d)} /> ); } case "key_point": { if (!isKeyPointBlockData(rest.data)) return null; return ( rest.onUpdate(d)} /> ); } case "import": { if (!isImportBlockData(rest.data)) return null; return ( rest.onUpdate(d)} /> ); } case "new_teaching": { if (!isNewTeachingBlockData(rest.data)) return null; return ( rest.onUpdate(d)} /> ); } case "summary": { if (!isSummaryBlockData(rest.data)) return null; return ( rest.onUpdate(d)} /> ); } case "homework": { if (!isHomeworkBlockData(rest.data)) return null; return ( rest.onUpdate(d)} /> ); } case "blackboard": { if (!isBlackboardBlockData(rest.data)) return null; return ( rest.onUpdate(d)} /> ); } case "reflection": { if (!isReflectionBlockData(rest.data)) return null; return ( rest.onUpdate(d)} /> ); } case "exercise": { if (!isExerciseBlockData(rest.data)) return null; return ( ); } case "text_study": { if (!isTextStudyBlockData(rest.data)) return null; return ; } case "rich_text": case "consolidation": { if (!isRichTextBlockData(rest.data)) return null; return ( rest.onUpdate(d)} /> ); } default: return null; } }