introduced blocknote editor

This commit is contained in:
Anish Sarkar 2025-11-23 15:23:31 +05:30
parent 70f3381d7e
commit e68286f22e
23 changed files with 2158 additions and 14 deletions

View file

@ -0,0 +1,53 @@
"use client";
import { useEffect, useRef } from "react";
import "@blocknote/core/fonts/inter.css";
import "@blocknote/mantine/style.css";
import { useCreateBlockNote } from "@blocknote/react";
import { BlockNoteView } from "@blocknote/mantine";
interface BlockNoteEditorProps {
initialContent?: any;
onChange?: (content: any) => void;
}
export default function BlockNoteEditor({
initialContent,
onChange,
}: BlockNoteEditorProps) {
// Track the initial content to prevent re-initialization
const initialContentRef = useRef<any>(null);
const isInitializedRef = useRef(false);
// Creates a new editor instance - only use initialContent on first render
const editor = useCreateBlockNote({
initialContent: initialContentRef.current === null ? (initialContent || undefined) : undefined,
});
// Store initial content on first render only
useEffect(() => {
if (initialContent && initialContentRef.current === null) {
initialContentRef.current = initialContent;
isInitializedRef.current = true;
}
}, [initialContent]);
// Call onChange when document changes (but don't update from props)
useEffect(() => {
if (!onChange || !editor || !isInitializedRef.current) return;
const handleChange = () => {
onChange(editor.document);
};
// Subscribe to document changes
const unsubscribe = editor.onChange(handleChange);
return () => {
unsubscribe();
};
}, [editor, onChange]);
// Renders the editor instance
return <BlockNoteView editor={editor} />;
}

View file

@ -0,0 +1,9 @@
"use client";
import dynamic from "next/dynamic";
// Dynamically import BlockNote editor with SSR disabled
export const BlockNoteEditor = dynamic(
() => import("./BlockNoteEditor"),
{ ssr: false }
);

View file

@ -3,7 +3,7 @@
import { useAtomValue } from "jotai";
import { usePathname } from "next/navigation";
import { useTranslations } from "next-intl";
import React, { useEffect } from "react";
import React, { useEffect, useState } from "react";
import { activeChatAtom } from "@/atoms/chats/chat-query.atoms";
import {
Breadcrumb,
@ -34,6 +34,42 @@ export function DashboardBreadcrumb() {
autoFetch: !!searchSpaceId,
});
// State to store document title for editor breadcrumb
const [documentTitle, setDocumentTitle] = useState<string | null>(null);
// Fetch document title when on editor page
useEffect(() => {
if (segments[2] === "editor" && segments[3] && searchSpaceId) {
const documentId = segments[3];
const token = typeof window !== "undefined"
? localStorage.getItem("surfsense_bearer_token")
: null;
if (token) {
fetch(
`${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/documents/${documentId}/editor-content`,
{
headers: {
Authorization: `Bearer ${token}`,
},
}
)
.then((res) => res.json())
.then((data) => {
if (data.title) {
setDocumentTitle(data.title);
}
})
.catch(() => {
// If fetch fails, just use the document ID
setDocumentTitle(null);
});
}
} else {
setDocumentTitle(null);
}
}, [segments, searchSpaceId]);
// Parse the pathname to create breadcrumb items
const generateBreadcrumbs = (path: string): BreadcrumbItemInterface[] => {
const segments = path.split("/").filter(Boolean);
@ -66,6 +102,7 @@ export function DashboardBreadcrumb() {
logs: t("logs"),
chats: t("chats"),
settings: t("settings"),
editor: t("editor"),
};
sectionLabel = sectionLabels[section] || sectionLabel;
@ -73,7 +110,21 @@ export function DashboardBreadcrumb() {
// Handle sub-sections
if (segments[3]) {
const subSection = segments[3];
let subSectionLabel = subSection.charAt(0).toUpperCase() + subSection.slice(1);
// Handle editor sub-sections (document ID)
if (section === "editor") {
const documentLabel = documentTitle || subSection;
breadcrumbs.push({
label: t("documents"),
href: `/dashboard/${segments[1]}/documents`,
});
breadcrumbs.push({
label: sectionLabel,
href: `/dashboard/${segments[1]}/documents`,
});
breadcrumbs.push({ label: documentLabel });
return breadcrumbs;
}
// Handle sources sub-sections
if (section === "sources") {
@ -81,7 +132,7 @@ export function DashboardBreadcrumb() {
add: "Add Sources",
};
const sourceLabel = sourceLabels[subSection] || subSectionLabel;
const sourceLabel = sourceLabels[subSection] || subSection;
breadcrumbs.push({
label: "Sources",
href: `/dashboard/${segments[1]}/sources`,
@ -98,7 +149,7 @@ export function DashboardBreadcrumb() {
webpage: t("add_webpages"),
};
const documentLabel = documentLabels[subSection] || subSectionLabel;
const documentLabel = documentLabels[subSection] || subSection;
breadcrumbs.push({
label: t("documents"),
href: `/dashboard/${segments[1]}/documents`,
@ -158,7 +209,7 @@ export function DashboardBreadcrumb() {
manage: t("manage_connectors"),
};
const connectorLabel = connectorLabels[subSection] || subSectionLabel;
const connectorLabel = connectorLabels[subSection] || subSection;
breadcrumbs.push({
label: t("connectors"),
href: `/dashboard/${segments[1]}/connectors`,
@ -168,6 +219,7 @@ export function DashboardBreadcrumb() {
}
// Handle other sub-sections
let subSectionLabel = subSection.charAt(0).toUpperCase() + subSection.slice(1);
const subSectionLabels: Record<string, string> = {
upload: t("upload_documents"),
youtube: t("add_youtube"),