feat(lesson-preparation): V3→V4 迁移函数(锚点失效标记 + expandedNodeIds)
This commit is contained in:
@@ -9,6 +9,8 @@ import type {
|
|||||||
LessonPlanDocument,
|
LessonPlanDocument,
|
||||||
LessonPlanDocumentV1,
|
LessonPlanDocumentV1,
|
||||||
LessonPlanDocumentV2,
|
LessonPlanDocumentV2,
|
||||||
|
LessonPlanDocumentV3,
|
||||||
|
LessonPlanDocumentV4,
|
||||||
LessonPlanEdge,
|
LessonPlanEdge,
|
||||||
LessonPlanNode,
|
LessonPlanNode,
|
||||||
NodeAnchor,
|
NodeAnchor,
|
||||||
@@ -80,7 +82,7 @@ export function migrateV2ToV3(
|
|||||||
doc: LessonPlanDocumentV2,
|
doc: LessonPlanDocumentV2,
|
||||||
chapterId?: string | null,
|
chapterId?: string | null,
|
||||||
chapterContent?: string | null,
|
chapterContent?: string | null,
|
||||||
): LessonPlanDocument {
|
): LessonPlanDocumentV3 {
|
||||||
const textbookContentNodeId = createId();
|
const textbookContentNodeId = createId();
|
||||||
const textbookNode: TextbookContentNode = {
|
const textbookNode: TextbookContentNode = {
|
||||||
id: textbookContentNodeId,
|
id: textbookContentNodeId,
|
||||||
@@ -111,8 +113,63 @@ export function migrateV2ToV3(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* V3 → V4 迁移:
|
||||||
|
* - 锚点的 start/end/textPreview/invalid 标记为弃用(保留字段用于识别旧锚点)
|
||||||
|
* - 新增 expandedNodeIds(默认空数组)
|
||||||
|
* - 旧 v3 锚点在 v4 中视为失效,编辑器需显示 banner 提示重新锚定
|
||||||
|
*
|
||||||
|
* @returns V4 文档 + 是否含旧锚点(用于触发 banner)
|
||||||
|
*/
|
||||||
|
export function migrateV3ToV4(
|
||||||
|
doc: LessonPlanDocumentV3,
|
||||||
|
): { doc: LessonPlanDocumentV4; hasLegacyAnchors: boolean } {
|
||||||
|
const hasLegacyAnchors = doc.anchors.length > 0;
|
||||||
|
return {
|
||||||
|
doc: {
|
||||||
|
version: 4,
|
||||||
|
textbookContentNodeId: doc.textbookContentNodeId,
|
||||||
|
nodes: doc.nodes,
|
||||||
|
edges: doc.edges,
|
||||||
|
anchors: doc.anchors.map((a) => ({
|
||||||
|
id: a.id,
|
||||||
|
nodeId: a.nodeId,
|
||||||
|
type: a.type,
|
||||||
|
// 保留旧字段用于识别(标记失效)
|
||||||
|
start: a.start,
|
||||||
|
end: a.end,
|
||||||
|
textPreview: a.textPreview,
|
||||||
|
invalid: true,
|
||||||
|
})),
|
||||||
|
expandedNodeIds: [],
|
||||||
|
},
|
||||||
|
hasLegacyAnchors,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 类型守卫:判断是否为 v4 文档 ----
|
||||||
|
function isV4Document(content: unknown): content is LessonPlanDocumentV4 {
|
||||||
|
if (!content || typeof content !== "object") return false;
|
||||||
|
const c = content as {
|
||||||
|
version?: unknown;
|
||||||
|
textbookContentNodeId?: unknown;
|
||||||
|
nodes?: unknown;
|
||||||
|
edges?: unknown;
|
||||||
|
anchors?: unknown;
|
||||||
|
expandedNodeIds?: unknown;
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
c.version === 4 &&
|
||||||
|
typeof c.textbookContentNodeId === "string" &&
|
||||||
|
Array.isArray(c.nodes) &&
|
||||||
|
Array.isArray(c.edges) &&
|
||||||
|
Array.isArray(c.anchors) &&
|
||||||
|
Array.isArray(c.expandedNodeIds)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// ---- 类型守卫:判断是否为 v3 文档 ----
|
// ---- 类型守卫:判断是否为 v3 文档 ----
|
||||||
function isV3Document(content: unknown): content is LessonPlanDocument {
|
function isV3Document(content: unknown): content is LessonPlanDocumentV3 {
|
||||||
if (!content || typeof content !== "object") return false;
|
if (!content || typeof content !== "object") return false;
|
||||||
const c = content as {
|
const c = content as {
|
||||||
version?: unknown;
|
version?: unknown;
|
||||||
@@ -148,20 +205,23 @@ function isV1Document(content: unknown): content is LessonPlanDocumentV1 {
|
|||||||
return c.version === 1 && Array.isArray(c.blocks);
|
return c.version === 1 && Array.isArray(c.blocks);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- 规范化:确保 content 是 v3 格式(兼容 v1/v2 旧数据)----
|
// ---- 规范化:确保 content 是 v4 格式(兼容 v1/v2/v3 旧数据)----
|
||||||
export function normalizeDocument(
|
export function normalizeDocument(
|
||||||
content: unknown,
|
content: unknown,
|
||||||
chapterId?: string | null,
|
chapterId?: string | null,
|
||||||
chapterContent?: string | null,
|
chapterContent?: string | null,
|
||||||
): LessonPlanDocument {
|
): LessonPlanDocument {
|
||||||
if (isV3Document(content)) return content;
|
if (isV4Document(content)) return content;
|
||||||
|
if (isV3Document(content)) return migrateV3ToV4(content).doc;
|
||||||
if (isV2Document(content)) {
|
if (isV2Document(content)) {
|
||||||
return migrateV2ToV3(content, chapterId, chapterContent);
|
return migrateV3ToV4(migrateV2ToV3(content, chapterId, chapterContent)).doc;
|
||||||
}
|
}
|
||||||
if (isV1Document(content)) {
|
if (isV1Document(content)) {
|
||||||
return migrateV2ToV3(migrateV1ToV2(content), chapterId, chapterContent);
|
return migrateV3ToV4(
|
||||||
|
migrateV2ToV3(migrateV1ToV2(content), chapterId, chapterContent),
|
||||||
|
).doc;
|
||||||
}
|
}
|
||||||
// 空文档:创建一个无正文的 v3
|
// 空文档:创建一个无正文的 v4
|
||||||
const textbookContentNodeId = createId();
|
const textbookContentNodeId = createId();
|
||||||
const textbookNode: TextbookContentNode = {
|
const textbookNode: TextbookContentNode = {
|
||||||
id: textbookContentNodeId,
|
id: textbookContentNodeId,
|
||||||
@@ -173,16 +233,17 @@ export function normalizeDocument(
|
|||||||
draggable: false,
|
draggable: false,
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
version: 3,
|
version: 4,
|
||||||
textbookContentNodeId,
|
textbookContentNodeId,
|
||||||
nodes: [textbookNode],
|
nodes: [textbookNode],
|
||||||
edges: [],
|
edges: [],
|
||||||
anchors: [],
|
anchors: [],
|
||||||
|
expandedNodeIds: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- 模板初始化:根据骨架生成初始 content(v3,无正文节点)----
|
// ---- 模板初始化:根据骨架生成初始 content(v4,无正文节点)----
|
||||||
// 注意:此函数不创建正文节点,调用方应使用 buildDefaultSkeleton 创建完整 v3 文档
|
// 注意:此函数不创建正文节点,调用方应使用 buildDefaultSkeleton 创建完整 v4 文档
|
||||||
export function buildInitialContent(
|
export function buildInitialContent(
|
||||||
blocks: TemplateBlockSkeleton[],
|
blocks: TemplateBlockSkeleton[],
|
||||||
): LessonPlanDocument {
|
): LessonPlanDocument {
|
||||||
@@ -217,11 +278,12 @@ export function buildInitialContent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
version: 3,
|
version: 4,
|
||||||
textbookContentNodeId,
|
textbookContentNodeId,
|
||||||
nodes: [textbookNode, ...nodes],
|
nodes: [textbookNode, ...nodes],
|
||||||
edges,
|
edges,
|
||||||
anchors: [],
|
anchors: [],
|
||||||
|
expandedNodeIds: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,11 +345,12 @@ export function buildDefaultSkeleton(
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
version: 3,
|
version: 4,
|
||||||
textbookContentNodeId,
|
textbookContentNodeId,
|
||||||
nodes: [textbookNode, ...nodes],
|
nodes: [textbookNode, ...nodes],
|
||||||
edges,
|
edges,
|
||||||
anchors: [],
|
anchors: [],
|
||||||
|
expandedNodeIds: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user