"use client" import * as React from "react" import { ID } from "jazz-tools" import { PersonalPage } from "@/lib/schema" import { useCallback, useRef, useEffect } from "react" 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" const TITLE_PLACEHOLDER = "Untitled" export function PageDetailRoute({ pageId }: { pageId: string }) { const isMobile = useMedia("(max-width: 770px)") const page = useCoState(PersonalPage, pageId as ID) if (!page) return null return (
{!isMobile && (
Page actions
{ page.topic = topic page.updatedAt = new Date() }} />
)}
) } export 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 personalPages = me?.root?.personalPages?.toJSON() || [] const slug = generateUniqueSlug(personalPages, page.slug || "") const trimmedTitle = editor.getText().trim() page.title = trimmedTitle page.slug = slug page.updatedAt = new Date() editor.commands.setContent(trimmedTitle) } 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)} />
) }