feat(lesson-preparation): add anchor canvas design, new blocks, and textbook content node
- Add anchor injector for canvas-based anchor positioning - Add new block components: blackboard, homework, import, key-point, new-teaching, objective, summary - Add textbook content node for React Flow canvas - Update actions (kp, publish, main), data-access (templates, versions, main) - Update editor, node-editor, block-renderer, and picker components - Update schema, types, hooks, and lib utilities (document-migration, node-summary, rf-mappers)
This commit is contained in:
@@ -60,23 +60,25 @@ export async function createLessonPlanVersion(input: {
|
||||
isAuto: boolean;
|
||||
label?: string;
|
||||
}): Promise<{ versionNo: number }> {
|
||||
// 取当前最大 versionNo
|
||||
const maxRow = await db
|
||||
.select({ maxNo: max(lessonPlanVersions.versionNo) })
|
||||
.from(lessonPlanVersions)
|
||||
.where(eq(lessonPlanVersions.planId, input.planId));
|
||||
const nextNo = (maxRow[0]?.maxNo ?? 0) + 1;
|
||||
// P0 修复:max(versionNo)+1 必须在事务内完成,避免并发产生重复版本号
|
||||
return await db.transaction(async (tx) => {
|
||||
const maxRow = await tx
|
||||
.select({ maxNo: max(lessonPlanVersions.versionNo) })
|
||||
.from(lessonPlanVersions)
|
||||
.where(eq(lessonPlanVersions.planId, input.planId));
|
||||
const nextNo = (maxRow[0]?.maxNo ?? 0) + 1;
|
||||
|
||||
await db.insert(lessonPlanVersions).values({
|
||||
id: createId(),
|
||||
planId: input.planId,
|
||||
versionNo: nextNo,
|
||||
label: input.label ?? null,
|
||||
content: input.content,
|
||||
isAuto: input.isAuto,
|
||||
creatorId: input.userId,
|
||||
await tx.insert(lessonPlanVersions).values({
|
||||
id: createId(),
|
||||
planId: input.planId,
|
||||
versionNo: nextNo,
|
||||
label: input.label ?? null,
|
||||
content: input.content,
|
||||
isAuto: input.isAuto,
|
||||
creatorId: input.userId,
|
||||
});
|
||||
return { versionNo: nextNo };
|
||||
});
|
||||
return { versionNo: nextNo };
|
||||
}
|
||||
|
||||
export async function getVersionContent(
|
||||
@@ -116,22 +118,33 @@ export async function revertToVersion(
|
||||
const content = await getVersionContent(planId, versionNo, userId);
|
||||
if (!content) return null;
|
||||
|
||||
// 用该版本 content 覆盖当前 + 生成新版本
|
||||
await db
|
||||
.update(lessonPlans)
|
||||
.set({ content, lastSavedAt: new Date() })
|
||||
.where(
|
||||
and(eq(lessonPlans.id, planId), eq(lessonPlans.creatorId, userId)),
|
||||
);
|
||||
// P0 修复:update 与 createLessonPlanVersion 必须在同一事务内,
|
||||
// 避免回退后 content 已覆盖但版本记录未生成的不一致状态
|
||||
return await db.transaction(async (tx) => {
|
||||
await tx
|
||||
.update(lessonPlans)
|
||||
.set({ content, lastSavedAt: new Date() })
|
||||
.where(
|
||||
and(eq(lessonPlans.id, planId), eq(lessonPlans.creatorId, userId)),
|
||||
);
|
||||
|
||||
const { versionNo: newNo } = await createLessonPlanVersion({
|
||||
planId,
|
||||
content,
|
||||
userId,
|
||||
isAuto: false,
|
||||
label: `回退到 v${versionNo}`,
|
||||
const maxRow = await tx
|
||||
.select({ maxNo: max(lessonPlanVersions.versionNo) })
|
||||
.from(lessonPlanVersions)
|
||||
.where(eq(lessonPlanVersions.planId, planId));
|
||||
const newNo = (maxRow[0]?.maxNo ?? 0) + 1;
|
||||
|
||||
await tx.insert(lessonPlanVersions).values({
|
||||
id: createId(),
|
||||
planId,
|
||||
versionNo: newNo,
|
||||
label: `回退到 v${versionNo}`,
|
||||
content,
|
||||
isAuto: false,
|
||||
creatorId: userId,
|
||||
});
|
||||
return { newVersionNo: newNo };
|
||||
});
|
||||
return { newVersionNo: newNo };
|
||||
}
|
||||
|
||||
export async function pruneAutoVersions(
|
||||
|
||||
Reference in New Issue
Block a user