From 093e403f5f14293e4126fe66f40a695bc5d4b35a Mon Sep 17 00:00:00 2001 From: dmitriylaukhin Date: Wed, 8 Apr 2026 10:29:39 +0500 Subject: [PATCH] Enhance lesson editor: prev/next nav + richer toolbar - page.tsx: fetch sibling lessons, pass prevLesson/nextLesson props - LessonEditor: ChevronLeft/Right nav buttons with lesson title tooltip - Toolbar: added Underline, Strikethrough, inline Code, H1, Horizontal rule, Link dialog (prompt), labeled buttons for better discoverability - Install @tiptap/extension-underline Co-Authored-By: Claude Sonnet 4.6 --- package-lock.json | 1 + package.json | 1 + .../[moduleId]/lessons/[lessonId]/page.tsx | 31 ++-- src/components/admin/lesson-editor.tsx | 153 +++++++++++++----- 4 files changed, 140 insertions(+), 46 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5e7eaba..52f5638 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "@tiptap/extension-image": "^3.22.2", "@tiptap/extension-link": "^3.22.2", "@tiptap/extension-placeholder": "^3.22.2", + "@tiptap/extension-underline": "^3.22.2", "@tiptap/pm": "^3.22.2", "@tiptap/react": "^3.22.2", "@tiptap/starter-kit": "^3.22.2", diff --git a/package.json b/package.json index bce00cd..11c5385 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "@tiptap/extension-image": "^3.22.2", "@tiptap/extension-link": "^3.22.2", "@tiptap/extension-placeholder": "^3.22.2", + "@tiptap/extension-underline": "^3.22.2", "@tiptap/pm": "^3.22.2", "@tiptap/react": "^3.22.2", "@tiptap/starter-kit": "^3.22.2", diff --git a/src/app/admin/courses/[courseId]/modules/[moduleId]/lessons/[lessonId]/page.tsx b/src/app/admin/courses/[courseId]/modules/[moduleId]/lessons/[lessonId]/page.tsx index f700e0d..abde314 100644 --- a/src/app/admin/courses/[courseId]/modules/[moduleId]/lessons/[lessonId]/page.tsx +++ b/src/app/admin/courses/[courseId]/modules/[moduleId]/lessons/[lessonId]/page.tsx @@ -12,19 +12,30 @@ interface Props { export default async function LessonEditorPage({ params }: Props) { const { courseId, moduleId, lessonId } = await params; - const lesson = await prisma.lesson.findUnique({ - where: { id: lessonId }, - include: { - files: { orderBy: { createdAt: "asc" } }, - homework: true, - module: { - include: { course: { select: { title: true, slug: true } } }, + const [lesson, siblings] = await Promise.all([ + prisma.lesson.findUnique({ + where: { id: lessonId }, + include: { + files: { orderBy: { createdAt: "asc" } }, + homework: true, + module: { + include: { course: { select: { title: true, slug: true } } }, + }, }, - }, - }); + }), + prisma.lesson.findMany({ + where: { moduleId }, + orderBy: { order: "asc" }, + select: { id: true, title: true }, + }), + ]); if (!lesson || lesson.moduleId !== moduleId) notFound(); + const idx = siblings.findIndex((l) => l.id === lessonId); + const prevLesson = idx > 0 ? siblings[idx - 1] : null; + const nextLesson = idx < siblings.length - 1 ? siblings[idx + 1] : null; + return (
diff --git a/src/components/admin/lesson-editor.tsx b/src/components/admin/lesson-editor.tsx index 44fa284..3982c8a 100644 --- a/src/components/admin/lesson-editor.tsx +++ b/src/components/admin/lesson-editor.tsx @@ -5,8 +5,10 @@ import { useEditor, EditorContent } from "@tiptap/react"; import StarterKit from "@tiptap/starter-kit"; import Image from "@tiptap/extension-image"; import Link from "@tiptap/extension-link"; +import Underline from "@tiptap/extension-underline"; import Placeholder from "@tiptap/extension-placeholder"; -import { Save, Eye, FileUp } from "lucide-react"; +import { Save, Eye, FileUp, ChevronLeft, ChevronRight } from "lucide-react"; +import { useRouter } from "next/navigation"; import { saveLesson } from "@/app/admin/courses/[courseId]/modules/[moduleId]/lessons/[lessonId]/actions"; interface LessonData { @@ -17,17 +19,27 @@ interface LessonData { published: boolean; } +interface SiblingLesson { + id: string; + title: string; +} + export function LessonEditor({ lesson, courseId, moduleId, courseSlug, + prevLesson, + nextLesson, }: { lesson: LessonData; courseId: string; moduleId: string; courseSlug: string; + prevLesson?: SiblingLesson | null; + nextLesson?: SiblingLesson | null; }) { + const router = useRouter(); const [title, setTitle] = useState(lesson.title); const [kinescopeId, setKinescopeId] = useState(lesson.kinescopeId); const [published, setPublished] = useState(lesson.published); @@ -50,6 +62,7 @@ export function LessonEditor({ const editor = useEditor({ extensions: [ StarterKit, + Underline, Image.configure({ inline: false }), Link.configure({ openOnClick: false }), Placeholder.configure({ placeholder: "Начните писать текст урока..." }), @@ -108,6 +121,18 @@ export function LessonEditor({ input.click(); }, [editor]); + const addLink = useCallback(() => { + if (!editor) return; + const prev = editor.getAttributes("link").href as string | undefined; + const url = window.prompt("Ссылка:", prev ?? "https://"); + if (url === null) return; + if (url === "") { + editor.chain().focus().unsetLink().run(); + } else { + editor.chain().focus().setLink({ href: url, target: "_blank" }).run(); + } + }, [editor]); + function handleSave() { if (!editor) return; startTransition(async () => { @@ -122,44 +147,74 @@ export function LessonEditor({ }); } + function navigateTo(lessonId: string) { + router.push(`/admin/courses/${courseId}/modules/${moduleId}/lessons/${lessonId}`); + } + return (
{/* Header controls */} -
- + > + + + + {published ? "Опубликован" : "Черновик"} + + + {/* Prev / Next navigation */} +
+ + +
+
+ + {/* Right: Import / Preview / Save */}