feat(kernel): complete tree-first graph tasks 074-080
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
export type MindMapData = {
|
||||
data: Record<string, unknown>;
|
||||
children?: unknown[];
|
||||
};
|
||||
|
||||
export type MindmapRouteMeta = {
|
||||
requestId?: string;
|
||||
traceId?: string;
|
||||
workspaceId?: string | null;
|
||||
documentId?: string;
|
||||
pageId?: string;
|
||||
mindmapId?: string;
|
||||
attachmentId?: string;
|
||||
updatedAt?: string | null;
|
||||
};
|
||||
|
||||
export type MindmapProjectionNode = {
|
||||
uid: string;
|
||||
text: string;
|
||||
depth: number;
|
||||
childCount: number;
|
||||
};
|
||||
|
||||
export type MindmapProjection = {
|
||||
projectionId: string;
|
||||
projection: "mindmap_subtree";
|
||||
documentId: string;
|
||||
mindmapId: string;
|
||||
rootNodeId: string | null;
|
||||
title: string;
|
||||
nodeCount: number;
|
||||
nodes: MindmapProjectionNode[];
|
||||
data: MindMapData;
|
||||
meta: MindmapRouteMeta | null;
|
||||
};
|
||||
|
||||
export const defaultMindmapData: MindMapData = {
|
||||
data: { text: "中心主题" },
|
||||
children: [],
|
||||
};
|
||||
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
|
||||
// simple-mind-map 在文本字段缺失时会直接崩溃,这里统一做兜底归一化。
|
||||
export const normalizeMindmapData = (input: unknown): unknown => {
|
||||
if (!input || typeof input !== "object") return defaultMindmapData;
|
||||
const root = (input as { root?: unknown }).root ?? input;
|
||||
|
||||
const walk = (node: unknown) => {
|
||||
if (!isRecord(node)) return;
|
||||
if (!isRecord(node.data)) {
|
||||
node.data = {};
|
||||
}
|
||||
const rawText = node.data.text;
|
||||
node.data.text = typeof rawText === "string" ? rawText : String(rawText ?? "");
|
||||
|
||||
const gen = node.data.generalization;
|
||||
const fixGen = (value: unknown) => {
|
||||
if (!isRecord(value)) return;
|
||||
const text = value.text;
|
||||
value.text = typeof text === "string" ? text : String(text ?? "");
|
||||
};
|
||||
|
||||
if (Array.isArray(gen)) gen.forEach(fixGen);
|
||||
else fixGen(gen);
|
||||
|
||||
if (Array.isArray(node.children)) {
|
||||
node.children.forEach(walk);
|
||||
}
|
||||
};
|
||||
|
||||
walk(root);
|
||||
return input;
|
||||
};
|
||||
|
||||
// 持久化/初始化统一使用根节点对象,避免 wrapper 误传给 simple-mind-map。
|
||||
export const canonicalizeMindmapData = (input: unknown): MindMapData => {
|
||||
const normalized = (normalizeMindmapData(input) ?? defaultMindmapData) as Record<string, unknown>;
|
||||
const root =
|
||||
normalized && typeof normalized === "object" && "root" in normalized
|
||||
? normalized.root
|
||||
: normalized;
|
||||
return (normalizeMindmapData(root) ?? defaultMindmapData) as MindMapData;
|
||||
};
|
||||
|
||||
export const extractMindmapTitle = (input: unknown): string => {
|
||||
const canonical = canonicalizeMindmapData(input);
|
||||
const text = canonical?.data?.text;
|
||||
if (typeof text === "string" && text.trim()) {
|
||||
return text.trim();
|
||||
}
|
||||
return "未命名导图";
|
||||
};
|
||||
|
||||
export const summarizeMindmapProjectionNodes = (input: unknown): MindmapProjectionNode[] => {
|
||||
const root = canonicalizeMindmapData(input);
|
||||
const queue: Array<{ node: MindMapData; depth: number }> = [{ node: root, depth: 0 }];
|
||||
const nodes: MindmapProjectionNode[] = [];
|
||||
|
||||
while (queue.length > 0) {
|
||||
const current = queue.shift();
|
||||
if (!current) break;
|
||||
const uidRaw = current.node?.data?.uid;
|
||||
const textRaw = current.node?.data?.text;
|
||||
const children = Array.isArray(current.node?.children) ? current.node.children : [];
|
||||
nodes.push({
|
||||
uid:
|
||||
typeof uidRaw === "string" && uidRaw.trim()
|
||||
? uidRaw
|
||||
: `depth:${current.depth}:index:${nodes.length}`,
|
||||
text: typeof textRaw === "string" && textRaw.trim() ? textRaw.trim() : "未命名节点",
|
||||
depth: current.depth,
|
||||
childCount: children.length,
|
||||
});
|
||||
children.forEach((child) => {
|
||||
queue.push({
|
||||
node: canonicalizeMindmapData(child),
|
||||
depth: current.depth + 1,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return nodes;
|
||||
};
|
||||
|
||||
export const buildMindmapProjection = (input: {
|
||||
documentId: string;
|
||||
mindmapId: string;
|
||||
data: unknown;
|
||||
meta?: unknown;
|
||||
}): MindmapProjection => {
|
||||
const data = canonicalizeMindmapData(input.data);
|
||||
const nodes = summarizeMindmapProjectionNodes(data);
|
||||
const meta = isRecord(input.meta) ? (input.meta as MindmapRouteMeta) : null;
|
||||
const rootNodeId = nodes[0]?.uid ?? null;
|
||||
|
||||
return {
|
||||
projectionId: `mindmap_projection:${input.documentId}:${input.mindmapId}`,
|
||||
projection: "mindmap_subtree",
|
||||
documentId: input.documentId,
|
||||
mindmapId: input.mindmapId,
|
||||
rootNodeId,
|
||||
title: extractMindmapTitle(data),
|
||||
nodeCount: nodes.length,
|
||||
nodes,
|
||||
data,
|
||||
meta,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user