"use client";
import { BlockNoteView } from "@blocknote/mantine";
import { de } from "@blocknote/core/locales";
import { useCreateBlockNote } from "@blocknote/react";
import { useEffect } from "react";
const API_URL = process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:3002";
type NewsBlockNoteEditorProps = {
label?: string;
value: string;
placeholder?: string;
minHeightClassName?: string;
ensureLeadingHeading?: boolean;
onChange: (next: string) => void;
};
function normalizeHtml(html: string) {
const trimmed = html.trim();
if (!trimmed) return "";
if (trimmed === "
" || trimmed === "
") return "";
return trimmed;
}
function plainTextFromHtml(html: string) {
return html.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
}
async function uploadEditorImage(file: File) {
const formData = new FormData();
formData.append("file", file);
const response = await fetch(`${API_URL}/api/uploads/image`, {
method: "POST",
credentials: "include",
body: formData,
});
const payload = (await response.json().catch(() => ({}))) as { url?: string; error?: string };
if (!response.ok || !payload.url) {
throw new Error(payload.error ?? "Bild-Upload fehlgeschlagen");
}
return payload.url;
}
export function NewsBlockNoteEditor({
label,
value,
placeholder = "Hier den Beitrag schreiben...",
minHeightClassName = "min-h-[480px]",
ensureLeadingHeading = false,
onChange,
}: NewsBlockNoteEditorProps) {
const editor = useCreateBlockNote({
dictionary: {
...de,
placeholders: {
...(de as { placeholders?: Record }).placeholders,
default: placeholder,
emptyDocument: placeholder,
},
},
uploadFile: uploadEditorImage,
});
useEffect(() => {
const currentHtml = normalizeHtml(editor.blocksToHTMLLossy(editor.document));
const targetHtml = normalizeHtml(value || "");
if (currentHtml === targetHtml) return;
const starterBlocks = [
{
type: "heading",
props: { level: 1 },
},
{
type: "paragraph",
},
] as never;
try {
const blocks = targetHtml ? editor.tryParseHTMLToBlocks(targetHtml) : [];
if (blocks.length > 0) {
editor.replaceBlocks(editor.document, blocks);
return;
}
const fallbackText = plainTextFromHtml(targetHtml);
if (!fallbackText) {
editor.replaceBlocks(editor.document, ensureLeadingHeading ? starterBlocks : []);
return;
}
editor.replaceBlocks(editor.document, [
{
type: "paragraph",
content: fallbackText,
} as never,
]);
} catch {
const fallbackText = plainTextFromHtml(targetHtml);
if (!fallbackText) {
if (ensureLeadingHeading) {
editor.replaceBlocks(editor.document, starterBlocks);
}
return;
}
editor.replaceBlocks(editor.document, [
{
type: "paragraph",
content: fallbackText,
} as never,
]);
}
}, [editor, ensureLeadingHeading, value]);
return (
{label ?
: null}
{
const html = normalizeHtml(editor.blocksToHTMLLossy(editor.document));
onChange(html);
}}
slashMenu
formattingToolbar
sideMenu
tableHandles
className="bg-white"
/>
);
}