"use client" import React, { useCallback, useRef, useEffect } from "react" import { ID } from "jazz-tools" import { PersonalPage } from "@/lib/schema" import { LAEditor, LAEditorRef } from "@/components/la-editor" import { Content, EditorContent, useEditor } from "@tiptap/react" import { StarterKit } from "@/components/la-editor/extensions/starter-kit" import { Paragraph } from "@/components/la-editor/extensions/paragraph" import { useAccount, useCoState } from "@/lib/providers/jazz-provider" import { EditorView } from "@tiptap/pm/view" import { Editor } from "@tiptap/core" import { generateUniqueSlug } from "@/lib/utils" import { FocusClasses } from "@tiptap/extension-focus" import { DetailPageHeader } from "./header" import { useMedia } from "react-use" import { TopicSelector } from "@/components/custom/topic-selector" import { Button } from "@/components/ui/button" import { LaIcon } from "@/components/custom/la-icon" import { useConfirm } from "@omit/react-confirm-dialog" import { toast } from "sonner" import { useRouter } from "next/navigation" const TITLE_PLACEHOLDER = "Untitled" export function PageDetailRoute({ pageId }: { pageId: string }) { const { me } = useAccount({ root: { personalLinks: [] } }) const isMobile = useMedia("(max-width: 770px)") const page = useCoState(PersonalPage, pageId as ID) const router = useRouter() const confirm = useConfirm() const handleDelete = async () => { const result = await confirm({ title: "Delete page", description: "Are you sure you want to delete this page?", confirmText: "Delete", cancelText: "Cancel", cancelButton: { variant: "outline" }, confirmButton: { variant: "destructive" } }) if (result && me?.root.personalPages) { try { const index = me.root.personalPages.findIndex(item => item?.id === pageId) if (index === -1) { toast.error("Page not found.") return } me.root.personalPages.splice(index, 1) toast.success("Page deleted.", { position: "bottom-right" }) router.replace("/") } catch (error) { console.error("Delete operation fail", { error }) } } } if (!page) return null return (
{!isMobile && }
) } const SidebarActions = ({ page, handleDelete }: { page: PersonalPage; handleDelete: () => void }) => (
Page actions
{ page.topic = topic page.updatedAt = new Date() }} variant="ghost" className="-ml-1.5" renderSelectedText={() => {page.topic?.prettyName || "Select a topic"}} />
) const DetailPageForm = ({ page }: { page: PersonalPage }) => { const { me } = useAccount() const titleEditorRef = useRef(null) const contentEditorRef = useRef(null) const isTitleInitialMount = useRef(true) const isContentInitialMount = useRef(true) const updatePageContent = (content: Content, model: PersonalPage) => { if (isContentInitialMount.current) { isContentInitialMount.current = false return } model.content = content model.updatedAt = new Date() } const handleUpdateTitle = (editor: Editor) => { if (isTitleInitialMount.current) { isTitleInitialMount.current = false return } const newTitle = editor.getText() if (newTitle !== page.title) { const personalPages = me?.root?.personalPages?.toJSON() || [] const slug = generateUniqueSlug(personalPages, page.slug || "") page.title = newTitle page.slug = slug page.updatedAt = new Date() } } const handleTitleKeyDown = useCallback((view: EditorView, event: KeyboardEvent) => { const editor = titleEditorRef.current if (!editor) return false const { state } = editor const { selection } = state const { $anchor } = selection switch (event.key) { case "ArrowRight": case "ArrowDown": if ($anchor.pos === state.doc.content.size - 1) { event.preventDefault() contentEditorRef.current?.editor?.commands.focus("start") return true } break case "Enter": if (!event.shiftKey) { event.preventDefault() contentEditorRef.current?.editor?.commands.focus("start") return true } break } return false }, []) const handleContentKeyDown = useCallback((view: EditorView, event: KeyboardEvent) => { const editor = contentEditorRef.current?.editor if (!editor) return false const { state } = editor const { selection } = state const { $anchor } = selection if ((event.key === "ArrowLeft" || event.key === "ArrowUp") && $anchor.pos - 1 === 0) { event.preventDefault() titleEditorRef.current?.commands.focus("end") return true } return false }, []) const titleEditor = useEditor({ immediatelyRender: false, autofocus: true, extensions: [ FocusClasses, Paragraph, StarterKit.configure({ bold: false, italic: false, typography: false, hardBreak: false, listItem: false, strike: false, focus: false, gapcursor: false, placeholder: { placeholder: TITLE_PLACEHOLDER } }) ], editorProps: { attributes: { spellcheck: "true", role: "textbox", "aria-readonly": "false", "aria-multiline": "false", "aria-label": TITLE_PLACEHOLDER, translate: "no", class: "focus:outline-none" }, handleKeyDown: handleTitleKeyDown }, onCreate: ({ editor }) => { if (page.title) editor.commands.setContent(`

${page.title}

`) }, onBlur: ({ editor }) => handleUpdateTitle(editor), onUpdate: ({ editor }) => handleUpdateTitle(editor) }) useEffect(() => { if (titleEditor) { titleEditorRef.current = titleEditor } }, [titleEditor]) useEffect(() => { isTitleInitialMount.current = true isContentInitialMount.current = true }, []) return (
updatePageContent(c, page)} handleKeyDown={handleContentKeyDown} onBlur={c => updatePageContent(c, page)} onNewBlock={c => updatePageContent(c, page)} />
) }