Mark lesson complete on homework submit; add copy button + styling to code blocks
This commit is contained in:
@@ -190,6 +190,33 @@
|
||||
.prose :where(ul ul) {
|
||||
list-style-type: circle;
|
||||
}
|
||||
/* Код-блоки урока (с кнопкой копирования) */
|
||||
.prose pre {
|
||||
background: var(--color-surface, #f3f3ee);
|
||||
border: 1.5px solid var(--border);
|
||||
padding: 2.5rem 1rem 1rem; /* верхний отступ — под кнопку «Копировать» */
|
||||
margin: 0;
|
||||
overflow-x: auto;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.85em;
|
||||
line-height: 1.55;
|
||||
}
|
||||
.prose pre code {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
white-space: pre;
|
||||
}
|
||||
/* Инлайновый код */
|
||||
.prose :not(pre) > code {
|
||||
background: var(--color-surface, #f3f3ee);
|
||||
border: 1px solid var(--border);
|
||||
padding: 0.1em 0.35em;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
/* Admin sidebar (dark) */
|
||||
.admin-sidebar {
|
||||
|
||||
@@ -1,13 +1,61 @@
|
||||
"use client";
|
||||
|
||||
import { useEditor, EditorContent, NodeViewWrapper, ReactNodeViewRenderer } from "@tiptap/react";
|
||||
import { useState } from "react";
|
||||
import { useEditor, EditorContent, NodeViewWrapper, NodeViewContent, ReactNodeViewRenderer } from "@tiptap/react";
|
||||
import { Node, mergeAttributes } from "@tiptap/core";
|
||||
import StarterKit from "@tiptap/starter-kit";
|
||||
import CodeBlock from "@tiptap/extension-code-block";
|
||||
import Image from "@tiptap/extension-image";
|
||||
import Link from "@tiptap/extension-link";
|
||||
import Underline from "@tiptap/extension-underline";
|
||||
import { Copy, Check } from "lucide-react";
|
||||
import { KinescopePlayer } from "@/components/player/kinescope-player";
|
||||
|
||||
function CodeBlockStudentView({ node }: { node: { textContent: string } }) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const handleCopy = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(node.textContent);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 1500);
|
||||
} catch {
|
||||
// clipboard недоступен (нет https / нет прав) — тихо игнорируем
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<NodeViewWrapper className="relative group my-4">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCopy}
|
||||
contentEditable={false}
|
||||
aria-label="Скопировать код"
|
||||
className="absolute top-2 right-2 z-10 flex items-center gap-1 px-2 py-1 text-xs select-none transition-opacity"
|
||||
style={{
|
||||
border: "1.5px solid var(--border)",
|
||||
background: "var(--background)",
|
||||
color: copied ? "var(--foreground)" : "var(--muted-foreground)",
|
||||
fontFamily: "var(--font-mono, monospace)",
|
||||
}}
|
||||
>
|
||||
{copied ? <Check size={13} strokeWidth={3} /> : <Copy size={13} />}
|
||||
{copied ? "Скопировано" : "Копировать"}
|
||||
</button>
|
||||
<pre>
|
||||
<NodeViewContent<"code"> as="code" />
|
||||
</pre>
|
||||
</NodeViewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
const CodeBlockWithCopy = CodeBlock.extend({
|
||||
addNodeView() {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return ReactNodeViewRenderer(CodeBlockStudentView as any);
|
||||
},
|
||||
});
|
||||
|
||||
function KinescopeVideoStudentView({
|
||||
node,
|
||||
extension,
|
||||
@@ -56,7 +104,8 @@ const KinescopeVideoExtension = Node.create<{ poster?: string }>({
|
||||
export function LessonContent({ content, coverImage }: { content: object; coverImage?: string | null }) {
|
||||
const editor = useEditor({
|
||||
extensions: [
|
||||
StarterKit,
|
||||
StarterKit.configure({ codeBlock: false }),
|
||||
CodeBlockWithCopy,
|
||||
Underline,
|
||||
Image.configure({ inline: false }),
|
||||
Link.configure({ openOnClick: true }),
|
||||
|
||||
@@ -96,7 +96,16 @@ export async function submitHomework(
|
||||
}
|
||||
}
|
||||
|
||||
// Отметить урок пройденным по факту сдачи ДЗ (идемпотентно)
|
||||
await prisma.lessonProgress.upsert({
|
||||
where: { userId_lessonId: { userId: session.user.id, lessonId } },
|
||||
create: { userId: session.user.id, lessonId },
|
||||
update: {},
|
||||
});
|
||||
|
||||
revalidatePath(`/courses/${slug}/lessons/${lessonId}`);
|
||||
revalidatePath(`/courses/${slug}`);
|
||||
revalidatePath("/dashboard");
|
||||
}
|
||||
|
||||
// ── Quiz ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user