107 lines
2.5 KiB
TypeScript
107 lines
2.5 KiB
TypeScript
"use client";
|
|
|
|
import * as React from "react";
|
|
|
|
import { cn } from "@/lib/utils";
|
|
import { Card } from "@/components/ui/card";
|
|
import { Editor, EditorContent, useEditor } from "@tiptap/react";
|
|
import StarterKit from "@tiptap/starter-kit";
|
|
import Underline from "@tiptap/extension-underline";
|
|
import Link from "@tiptap/extension-link";
|
|
import TTImage from "@tiptap/extension-image";
|
|
import TextAlign from "@tiptap/extension-text-align";
|
|
import Youtube from "@tiptap/extension-youtube";
|
|
import Dropcursor from "@tiptap/extension-dropcursor";
|
|
import { EditorMenu } from "./editor-menu";
|
|
|
|
interface EditorProps {
|
|
content: string;
|
|
onChange?: (content: string) => void;
|
|
className?: string;
|
|
onTitleChange?: (t: string) => void;
|
|
}
|
|
|
|
export function LocalEditor({
|
|
content,
|
|
onChange,
|
|
className,
|
|
onTitleChange: setTitle,
|
|
}: EditorProps) {
|
|
const getTitle = (editor: Editor) => {
|
|
const firstNode = editor.state.doc.firstChild;
|
|
|
|
if (!firstNode) {
|
|
editor.commands.setNode("heading", {
|
|
level: 1,
|
|
content: [{ type: "text", text: "Titre" }],
|
|
});
|
|
}
|
|
|
|
if (
|
|
firstNode &&
|
|
!(firstNode.type.name === "heading" && firstNode.attrs.level === 1)
|
|
) {
|
|
setFirstLineAsH1(editor);
|
|
}
|
|
|
|
return firstNode?.textContent;
|
|
};
|
|
|
|
const setFirstLineAsH1 = (editor: Editor) => {
|
|
const firstNode = editor.state.doc.firstChild;
|
|
|
|
// Check if the first node is a paragraph and make it h1
|
|
if (firstNode && firstNode.type.name === "paragraph") {
|
|
editor.commands.setNode("heading", { level: 1 });
|
|
}
|
|
};
|
|
|
|
const editor = useEditor({
|
|
extensions: [
|
|
StarterKit,
|
|
Underline,
|
|
Link,
|
|
Youtube,
|
|
Dropcursor,
|
|
TTImage.configure({
|
|
HTMLAttributes: {
|
|
class: "max-w-full w-auto h-auto resize overflow-auto",
|
|
},
|
|
}),
|
|
TextAlign.configure({
|
|
alignments: ["left", "center", "right", "justify"],
|
|
defaultAlignment: "justify",
|
|
types: ["heading", "paragraph"],
|
|
}),
|
|
],
|
|
content,
|
|
immediatelyRender: false,
|
|
editorProps: {
|
|
attributes: {
|
|
class: "prose prose-sm sm:prose-base lg:prose-lg xl:prose-2xl m-5 focus:outline-none dark:prose-invert",
|
|
},
|
|
},
|
|
|
|
onCreate: ({ editor }) => {
|
|
const title = getTitle(editor);
|
|
setTitle?.(title ?? "");
|
|
},
|
|
onUpdate: ({ editor }) => {
|
|
onChange?.(editor.getHTML());
|
|
const title = getTitle(editor);
|
|
setTitle?.(title ?? "");
|
|
},
|
|
});
|
|
|
|
if (!editor) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<Card className={cn("border rounded-lg overflow-hidden", className)}>
|
|
<EditorMenu editor={editor} />
|
|
<EditorContent editor={editor} />
|
|
</Card>
|
|
);
|
|
}
|