"use client" import { useCallback, useEffect, useMemo, useRef, useState } from "react" import Link from "next/link" import { Button } from "@/components/ui/button" import { ArrowLeft } from "lucide-react" import { resolvePublicShareEditReturnTo } from "@/lib/drive/public-share-url" type DocEditorInstance = { destroyEditor: () => void } declare global { interface Window { DocsAPI?: { DocEditor: new (id: string, config: Record) => DocEditorInstance } DocEditor?: { instances: Record } } } let docsApiLoad: Promise | null = null function loadDocsApi(documentServerUrl: string): Promise { if (window.DocsAPI) return Promise.resolve() if (docsApiLoad) return docsApiLoad const base = documentServerUrl.replace(/\/$/, "") + "/" docsApiLoad = new Promise((resolve, reject) => { const script = document.createElement("script") script.id = "onlyoffice-docs-api-public" script.src = `${base}web-apps/apps/api/documents/api.js` script.async = true script.onload = () => resolve() script.onerror = () => { docsApiLoad = null reject(new Error(`Error load DocsAPI from ${base}`)) } document.body.appendChild(script) }) return docsApiLoad } function destroyDocEditor(id: string) { const inst = window.DocEditor?.instances?.[id] if (inst) { try { inst.destroyEditor() } catch { /* ignore */ } } document.getElementById(id)?.replaceChildren() } function OnlyOfficeMount({ editorId, documentServerUrl, config, onError, }: { editorId: string documentServerUrl: string config: Record onError: (message: string) => void }) { const configJson = JSON.stringify(config) const onErrorRef = useRef(onError) onErrorRef.current = onError useEffect(() => { let cancelled = false const id = editorId const parsed = JSON.parse(configJson) as Record const editorConfig: Record = { type: "desktop", width: "100%", height: "100%", events: { onError: (event: { data?: { errorDescription?: string; errorCode?: number } }) => { const msg = event?.data?.errorDescription || (event?.data?.errorCode != null ? `OnlyOffice error ${event.data.errorCode}` : "Erreur OnlyOffice.") onErrorRef.current(msg) }, }, ...parsed, } void loadDocsApi(documentServerUrl) .then(() => { if (cancelled || !window.DocsAPI) return destroyDocEditor(id) if (!window.DocEditor) window.DocEditor = { instances: {} } const editor = new window.DocsAPI.DocEditor(id, editorConfig) window.DocEditor.instances[id] = editor }) .catch((err: unknown) => { if (!cancelled) { onErrorRef.current( err instanceof Error ? err.message : "Impossible de charger OnlyOffice." ) } }) return () => { cancelled = true destroyDocEditor(id) } }, [editorId, documentServerUrl, configJson]) return
} export function PublicOfficeEditor({ token, filePath, password, returnTo, mode = "edit", }: { token: string filePath: string password?: string returnTo?: string | null mode?: "edit" | "view" }) { const instanceSeq = useRef(0) const guestId = useRef( typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `guest-${Date.now()}` ) const [config, setConfig] = useState | null>(null) const [serverUrl, setServerUrl] = useState("") const [editorId, setEditorId] = useState(null) const [error, setError] = useState(null) const backHref = useMemo( () => resolvePublicShareEditReturnTo(token, returnTo, filePath), [token, returnTo, filePath] ) useEffect(() => { let cancelled = false setConfig(null) setServerUrl("") setEditorId(null) setError(null) void (async () => { try { const res = await fetch( `/api/v1/drive/public/shares/${encodeURIComponent(token)}/office/session`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ path: filePath, mode, password: password ?? "", guest_id: guestId.current, }), } ) if (!res.ok) throw new Error("session_failed") const data = (await res.json()) as { config: Record serverUrl: string } if (cancelled) return instanceSeq.current += 1 setConfig(data.config) setServerUrl(data.serverUrl || process.env.NEXT_PUBLIC_ONLYOFFICE_URL || "") setEditorId(`ultidrive-public-editor-${instanceSeq.current}`) } catch { if (!cancelled) setError("Impossible de charger l’éditeur.") } })() return () => { cancelled = true } }, [token, filePath, password, mode]) const handleEditorError = useCallback((message: string) => { setError(message) }, []) if (error) { return (

{error}

) } if (!config || !editorId || !serverUrl) { return

Ouverture du document…

} return (
{filePath.split("/").pop()}
) }