feat(lesson-preparation): inline-node 展开节点容器

This commit is contained in:
SpecialX
2026-07-04 11:28:11 +08:00
parent 9b0b7ef619
commit 8583e3e387

View File

@@ -0,0 +1,108 @@
"use client";
import { useTranslations } from "next-intl";
import type { Block, LessonPlanNode } from "../../types";
import { InlineQaDialog } from "./inline-qa-dialog";
import { useLessonPlanEditor } from "../../hooks/use-lesson-plan-editor";
interface Props {
node: LessonPlanNode;
}
/**
* V4 inline-node展开到正文流的节点渲染。
* 用 Inter 字体 + 左侧细线区分正文Fraunces
*/
export function InlineNode({ node }: Props) {
const t = useTranslations("lessonPreparation");
const toggleExpand = useLessonPlanEditor((s) => s.toggleExpand);
// 师生交互节点用专门的对话体渲染
if (node.type === "interaction") {
return <InlineQaDialog node={node} />;
}
// 其他节点类型:用 InlineNodeBody 渲染只读摘要
// 完整编辑在右栏详情面板
const dotColorVar = `var(--lp-dot-${node.type})`;
return (
<div className="lp-inline-node">
<div className="lp-inline-node-head">
<span
style={{
width: 5,
height: 5,
borderRadius: "50%",
background: dotColorVar,
display: "inline-block",
}}
/>
<span>{node.type}</span>
<span style={{ marginLeft: "auto", fontFamily: "JetBrains Mono, monospace", fontSize: 9, color: "var(--lp-inline-node-meta)" }}>
{node.id.slice(-4)}
</span>
<button
type="button"
onClick={() => toggleExpand(node.id)}
className="lp-collapse-btn"
style={{
cursor: "pointer",
padding: "2px 6px",
borderRadius: 3,
color: "var(--lp-inline-node-meta)",
fontSize: 11,
background: "transparent",
border: "none",
}}
>
{t("v4.contextMenu.collapseFromPaper")}
</button>
</div>
<h4 className="lp-inline-node-title">{node.title}</h4>
<div className="lp-inline-node-body">
{/* 简化:各 block 类型的 inline 渲染在 InlineNodeBody 中处理 readonly 模式 */}
<InlineNodeBody node={node} />
</div>
</div>
);
}
/**
* 各节点类型的 inline 只读渲染。
* 这里给出最常见的几种的简化摘要,完整编辑在右栏详情面板。
*/
function InlineNodeBody({ node }: { node: Block }) {
const data = node.data;
// 按类型渲染摘要
switch (node.type) {
case "objective": {
const d = data as { objectives: { dimension: string; text: string }[] };
if (!d.objectives?.length) return <p style={{ color: "var(--muted-foreground)" }}></p>;
return (
<ul style={{ margin: "4px 0", paddingLeft: 16 }}>
{d.objectives.map((o, i) => (
<li key={i} style={{ marginBottom: 3, fontSize: 13 }}>{o.text}</li>
))}
</ul>
);
}
case "summary": {
const d = data as { summaryPoints: string[] };
if (!d.summaryPoints?.length) return <p style={{ color: "var(--muted-foreground)" }}></p>;
return (
<ul style={{ margin: "4px 0", paddingLeft: 16 }}>
{d.summaryPoints.map((s, i) => (
<li key={i} style={{ marginBottom: 3, fontSize: 13 }}>{s}</li>
))}
</ul>
);
}
case "exercise": {
const d = data as { items: unknown[] };
return <p style={{ fontSize: 13 }}>{d.items?.length ?? 0} </p>;
}
default:
return <p style={{ fontSize: 13, color: "var(--muted-foreground)" }}>{node.title}</p>;
}
}