"use client"; import * as React from "react"; import Link from "next/link"; import { useParams } from "next/navigation"; import { useSession } from "next-auth/react"; import { ChevronRight } from "lucide-react"; import { CollaborativeBlockEditor } from "@/components/editor"; import { api } from "@/lib/trpc"; import { Input } from "@/components/ui/input"; function contentToHtml(content: unknown): string { if (content == null) return "

"; if (typeof content === "string") return content || "

"; if ( typeof content === "object" && content !== null && "html" in content && typeof (content as { html: unknown }).html === "string" ) { return (content as { html: string }).html || "

"; } return "

"; } export default function DocEditorPage() { const params = useParams(); const { data: session } = useSession(); const utils = api.useUtils(); const workspaceSlug = typeof params?.workspaceSlug === "string" ? params.workspaceSlug : undefined; const docId = typeof params?.docId === "string" ? params.docId : undefined; const docQuery = api.objects.getById.useQuery( { id: docId! }, { enabled: Boolean(docId) }, ); const doc = docQuery.data as | { id: string; title: string; content: unknown; type: string } | undefined; const [titleDraft, setTitleDraft] = React.useState(""); React.useEffect(() => { if (doc?.title != null) setTitleDraft(doc.title); }, [doc?.title]); const saveContentTimeoutRef = React.useRef | null>( null, ); React.useEffect( () => () => { if (saveContentTimeoutRef.current) { clearTimeout(saveContentTimeoutRef.current); } }, [], ); const updateMutation = api.objects.update.useMutation({ onSuccess: async (_row, variables) => { await utils.objects.getById.invalidate({ id: variables.id }); if (workspaceSlug) { void utils.objects.list.invalidate({ workspaceId: workspaceSlug }); } }, }); const scheduleContentSave = React.useCallback( (html: string) => { if (!docId) return; if (saveContentTimeoutRef.current) { clearTimeout(saveContentTimeoutRef.current); } saveContentTimeoutRef.current = setTimeout(() => { saveContentTimeoutRef.current = null; updateMutation.mutate({ id: docId, content: html }); }, 500); }, [docId, updateMutation], ); const handleTitleBlur = () => { if (!docId || !doc) return; const next = titleDraft.trim(); if (next.length === 0) { setTitleDraft(doc.title); return; } if (next === doc.title) return; updateMutation.mutate({ id: docId, title: next }); }; if (!docId || !workspaceSlug) { return (
Invalid document link.
); } if (docQuery.isPending) { return (
); } if (docQuery.isError || !doc) { return (
Could not load this document.
); } return (
setTitleDraft(e.target.value)} onBlur={handleTitleBlur} className="mb-6 border-0 border-b border-transparent bg-transparent px-0 text-3xl font-bold tracking-tight shadow-none focus-visible:border-border focus-visible:ring-0" placeholder="Untitled" aria-label="Document title" />
); }