project init

This commit is contained in:
2026-02-06 02:17:59 +03:00
commit b9d9b9ed17
129 changed files with 22835 additions and 0 deletions

View File

@ -0,0 +1,70 @@
'use client';
import { useEditor, EditorContent } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import Underline from '@tiptap/extension-underline';
import Link from '@tiptap/extension-link';
import Image from '@tiptap/extension-image';
import mermaid from 'mermaid';
import { useEffect, useRef } from 'react';
mermaid.initialize({ startOnLoad: false, theme: 'neutral' });
const emptyDoc = { type: 'doc', content: [] };
interface LessonContentViewerProps {
content: Record<string, unknown> | null;
className?: string;
}
export function LessonContentViewer({ content, className }: LessonContentViewerProps) {
const containerRef = useRef<HTMLDivElement>(null);
const editor = useEditor({
extensions: [
StarterKit.configure({
heading: { levels: [1, 2, 3] },
codeBlock: {
HTMLAttributes: (node: { attrs: { language?: string } }) =>
node.attrs.language === 'mermaid'
? { class: 'mermaid rounded-lg p-4 bg-muted min-h-[80px]', 'data-language': 'mermaid' }
: { class: 'rounded-lg bg-muted p-4 font-mono text-sm', 'data-language': node.attrs.language || '' },
},
}),
Underline,
Link.configure({
openOnClick: true,
HTMLAttributes: { class: 'text-primary underline underline-offset-2' },
}),
Image.configure({ HTMLAttributes: { class: 'rounded-lg max-w-full h-auto' } }),
],
content: content ?? emptyDoc,
editable: false,
editorProps: {
attributes: {
class:
'prose prose-lg dark:prose-invert max-w-none focus:outline-none leading-relaxed [&_.ProseMirror]:outline-none [&_.ProseMirror]:text-foreground [&_.ProseMirror_p]:leading-7 [&_.ProseMirror_h1]:text-3xl [&_.ProseMirror_h2]:text-2xl [&_.ProseMirror_h3]:text-xl [&_.ProseMirror_pre]:rounded-lg [&_.ProseMirror_pre]:bg-muted [&_.ProseMirror_pre]:p-4 [&_.ProseMirror_pre]:font-mono [&_.ProseMirror_pre]:text-sm [&_.ProseMirror_pre]:border [&_.ProseMirror_blockquote]:border-primary [&_.ProseMirror_blockquote]:bg-muted/30',
},
},
});
useEffect(() => {
if (editor && content) {
editor.commands.setContent(content);
}
}, [content, editor]);
useEffect(() => {
if (!containerRef.current || !content) return;
const mermaidNodes = containerRef.current.querySelectorAll('pre[data-language="mermaid"]');
if (mermaidNodes.length === 0) return;
mermaid.run({ nodes: Array.from(mermaidNodes), suppressErrors: true }).catch(() => {});
}, [content]);
if (!editor) return null;
return (
<div ref={containerRef} className={className}>
<EditorContent editor={editor} />
</div>
);
}