93 lines
3.2 KiB
TypeScript
93 lines
3.2 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useTransition } from "react";
|
|
import { Eye, EyeOff, Save } from "lucide-react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Label } from "@/components/ui/label";
|
|
import { Md } from "@/app/learn/md";
|
|
import { extractQuiz } from "@/lib/quiz-parse";
|
|
import { updateMathPoint } from "./actions";
|
|
|
|
// xuchao: 数学课程编辑器——结构同语法编辑器:改标题/摘要/课文 markdown,可预览,保存即生效
|
|
export function EditorForm({
|
|
pointId,
|
|
initialTitle,
|
|
initialSummary,
|
|
initialMd,
|
|
}: {
|
|
pointId: string;
|
|
initialTitle: string;
|
|
initialSummary: string | null;
|
|
initialMd: string;
|
|
}) {
|
|
const [title, setTitle] = useState(initialTitle);
|
|
const [summary, setSummary] = useState(initialSummary ?? "");
|
|
const [md, setMd] = useState(initialMd);
|
|
const [preview, setPreview] = useState(false);
|
|
const [msg, setMsg] = useState<{ ok?: boolean; text: string } | null>(null);
|
|
const [pending, startTransition] = useTransition();
|
|
|
|
const quizCount = extractQuiz(md).length;
|
|
|
|
const save = () => {
|
|
setMsg(null);
|
|
startTransition(async () => {
|
|
const r = await updateMathPoint(pointId, { title, summary, md });
|
|
if (r.error) setMsg({ text: r.error });
|
|
else setMsg({ ok: true, text: "已保存,孩子端立即生效" });
|
|
});
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<div className="grid gap-3 sm:grid-cols-2">
|
|
<div className="space-y-1">
|
|
<Label>标题</Label>
|
|
<Input value={title} onChange={(e) => setTitle(e.target.value)} />
|
|
</div>
|
|
<div className="space-y-1">
|
|
<Label>学习目标(一句话)</Label>
|
|
<Input value={summary} onChange={(e) => setSummary(e.target.value)} />
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-1">
|
|
<div className="flex items-center justify-between">
|
|
<Label>课文内容(Markdown,含 ```quiz 练习块)</Label>
|
|
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
|
<span>检测到练习题 {quizCount} 道</span>
|
|
<Button variant="outline" size="sm" type="button" onClick={() => setPreview((p) => !p)}>
|
|
{preview ? <EyeOff className="size-4" /> : <Eye className="size-4" />}
|
|
{preview ? "编辑" : "预览"}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
{preview ? (
|
|
<div className="max-h-[480px] overflow-y-auto rounded-xl border border-slate-100 bg-white p-4">
|
|
<Md text={md} />
|
|
</div>
|
|
) : (
|
|
<textarea
|
|
value={md}
|
|
onChange={(e) => setMd(e.target.value)}
|
|
rows={18}
|
|
className="w-full rounded-lg border border-slate-200 bg-white p-3 font-mono text-xs leading-relaxed focus:border-indigo-500 focus:ring-2 focus:ring-indigo-200"
|
|
/>
|
|
)}
|
|
</div>
|
|
|
|
<div className="flex items-center gap-3">
|
|
<Button size="lg" disabled={pending} onClick={save}>
|
|
<Save className="size-4" /> 保存
|
|
</Button>
|
|
{msg && (
|
|
<p className={msg.ok ? "text-sm font-medium text-emerald-600" : "text-sm font-medium text-destructive"}>
|
|
{msg.text}
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|