feat(lesson-preparation): inline-node 展开节点容器
This commit is contained in:
@@ -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>;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user