"use client" import { memo, useEffect, useRef, useState } from "react" import { EditorContent, type Editor } from "@tiptap/react" import { getPageFormat, PAGE_MARGIN_PX, pageFormatHeightPx, pageFormatWidthPx, type PageFormatId, } from "@/lib/drive/page-formats" import { cn } from "@/lib/utils" import { focusEditorAtPointer } from "@/lib/drive/focus-editor-at-pointer" const PAGE_GAP_PX = 12 /** Actual block layout height — ignores CSS min-height on ProseMirror (page stack). */ function measureProseContentHeight(prose: HTMLElement): number { if (prose.childElementCount === 0) { return 0 } let maxBottom = 0 for (const child of prose.children) { const el = child as HTMLElement maxBottom = Math.max(maxBottom, el.offsetTop + el.offsetHeight) } return maxBottom } function DocsPageViewInner({ editor, pageFormatId, zoom, editable, onPageCountChange, }: { editor: Editor pageFormatId: PageFormatId zoom: number editable: boolean onPageCountChange?: (count: number) => void }) { const format = getPageFormat(pageFormatId) const pageWidth = pageFormatWidthPx(format) const pageHeight = pageFormatHeightPx(format) const canvasRef = useRef(null) const contentRef = useRef(null) const [pageCount, setPageCount] = useState(1) const [narrowViewport, setNarrowViewport] = useState(false) const onPageCountChangeRef = useRef(onPageCountChange) onPageCountChangeRef.current = onPageCountChange const scale = zoom / 100 const scaledWidth = pageWidth * scale useEffect(() => { const canvas = canvasRef.current if (!canvas) return const syncViewport = () => { setNarrowViewport(canvas.clientWidth < scaledWidth) } syncViewport() const ro = new ResizeObserver(syncViewport) ro.observe(canvas) return () => ro.disconnect() }, [scaledWidth]) useEffect(() => { const surface = contentRef.current if (!surface) return let rafId = 0 const measure = () => { const prose = surface.querySelector(".ProseMirror") as HTMLElement | null if (!prose) return const contentHeight = measureProseContentHeight(prose) const paddedHeight = PAGE_MARGIN_PX * 2 + contentHeight const count = Math.max(1, Math.ceil(paddedHeight / pageHeight)) setPageCount((prev) => { if (prev === count) return prev onPageCountChangeRef.current?.(count) return count }) } const scheduleMeasure = () => { if (rafId) cancelAnimationFrame(rafId) rafId = requestAnimationFrame(measure) } scheduleMeasure() const prose = surface.querySelector(".ProseMirror") as HTMLElement | null const ro = prose ? new ResizeObserver(scheduleMeasure) : null if (prose && ro) ro.observe(prose) const onTransaction = () => scheduleMeasure() editor.on("transaction", onTransaction) return () => { if (rafId) cancelAnimationFrame(rafId) ro?.disconnect() editor.off("transaction", onTransaction) } }, [pageHeight, editor]) const stackHeight = pageCount * pageHeight + (pageCount - 1) * PAGE_GAP_PX const proseMinHeight = stackHeight - PAGE_MARGIN_PX * 2 const scaledHeight = stackHeight * scale const verticalPadding = narrowViewport ? 32 : 64 return (
{Array.from({ length: pageCount }, (_, index) => (
))}
{ if (!editable) return const target = event.target as HTMLElement if (target.closest(".ProseMirror")) return event.preventDefault() focusEditorAtPointer(editor, event.clientX, event.clientY) }} >
) } export const DocsPageView = memo(DocsPageViewInner) export function DocsStatusBar({ pageFormatId, pageCount, className, }: { pageFormatId: PageFormatId pageCount: number className?: string }) { const format = getPageFormat(pageFormatId) return (
Page 1 sur {pageCount} {format.label} ({format.widthMm} × {format.heightMm} mm)
) }