163 lines
5.2 KiB
TypeScript
163 lines
5.2 KiB
TypeScript
"use client"
|
||
|
||
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||
import Link from "next/link"
|
||
import { Button } from "@/components/ui/button"
|
||
import { getOnlyOfficeUrl } from "@/lib/runtime-config"
|
||
import { ArrowLeft } from "lucide-react"
|
||
import { OnlyOfficeMount } from "@/components/drive/onlyoffice-mount"
|
||
import { OfficeEditorChrome } from "@/components/drive/office-editor-chrome"
|
||
import { displayFileBaseName } from "@/lib/drive/display-file-name"
|
||
import { getGuestEditorIdentity } from "@/lib/drive/guest-editor-identity"
|
||
import { resolvePublicShareEditReturnTo, shouldShowPublicShareEditorBack } from "@/lib/drive/public-share-url"
|
||
import type { PublicShareRootType } from "@/lib/drive/public-share-url"
|
||
import { resolveOnlyOfficeEditorKind } from "@/lib/drive/onlyoffice-formats"
|
||
import { useDriveDocumentTitle } from "@/lib/drive/use-drive-document-title"
|
||
|
||
function fileNameFromPath(filePath: string, fallback?: string): string {
|
||
const base = filePath.split("/").filter(Boolean).pop()
|
||
return base || fallback || filePath
|
||
}
|
||
|
||
export function PublicOfficeEditor({
|
||
token,
|
||
filePath,
|
||
password,
|
||
returnTo,
|
||
mode = "edit",
|
||
fileDisplayName,
|
||
shareRoot,
|
||
}: {
|
||
token: string
|
||
filePath: string
|
||
password?: string
|
||
returnTo?: string | null
|
||
mode?: "edit" | "view"
|
||
fileDisplayName?: string
|
||
shareRoot?: PublicShareRootType | null
|
||
}) {
|
||
const instanceSeq = useRef(0)
|
||
const guest = useMemo(() => getGuestEditorIdentity(token), [token])
|
||
const [config, setConfig] = useState<Record<string, unknown> | null>(null)
|
||
const [serverUrl, setServerUrl] = useState("")
|
||
const [editorId, setEditorId] = useState<string | null>(null)
|
||
const [error, setError] = useState<string | null>(null)
|
||
const [resolvedMode, setResolvedMode] = useState<"edit" | "view">(mode)
|
||
|
||
const fileName = fileDisplayName || fileNameFromPath(filePath)
|
||
const title = displayFileBaseName(fileName)
|
||
const editorKind = resolveOnlyOfficeEditorKind({ name: fileName })
|
||
useDriveDocumentTitle(title, editorKind)
|
||
|
||
const backHref = useMemo(
|
||
() => resolvePublicShareEditReturnTo(token, returnTo, filePath),
|
||
[token, returnTo, filePath]
|
||
)
|
||
const showBack = shouldShowPublicShareEditorBack(shareRoot, returnTo, filePath)
|
||
|
||
useEffect(() => {
|
||
let cancelled = false
|
||
setConfig(null)
|
||
setServerUrl("")
|
||
setEditorId(null)
|
||
setError(null)
|
||
setResolvedMode(mode)
|
||
|
||
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: guest.guestId,
|
||
guest_name: guest.guestName,
|
||
display_name: fileName,
|
||
}),
|
||
}
|
||
)
|
||
if (!res.ok) throw new Error("session_failed")
|
||
const data = (await res.json()) as {
|
||
config: Record<string, unknown>
|
||
serverUrl: string
|
||
mode?: "edit" | "view"
|
||
}
|
||
if (cancelled) return
|
||
instanceSeq.current += 1
|
||
setConfig(data.config)
|
||
setServerUrl(data.serverUrl || getOnlyOfficeUrl() || "")
|
||
setEditorId(`ultidrive-public-editor-${instanceSeq.current}`)
|
||
if (data.mode === "edit" || data.mode === "view") {
|
||
setResolvedMode(data.mode)
|
||
} else {
|
||
const editorConfig = data.config?.editorConfig as { mode?: string } | undefined
|
||
if (editorConfig?.mode === "edit" || editorConfig?.mode === "view") {
|
||
setResolvedMode(editorConfig.mode)
|
||
}
|
||
}
|
||
} catch {
|
||
if (!cancelled) setError("Impossible de charger l’éditeur.")
|
||
}
|
||
})()
|
||
|
||
return () => {
|
||
cancelled = true
|
||
}
|
||
}, [token, filePath, password, mode, fileName])
|
||
|
||
const handleEditorError = useCallback((message: string) => {
|
||
setError(message)
|
||
}, [])
|
||
|
||
if (error) {
|
||
return (
|
||
<div className="flex h-dvh flex-col items-center justify-center gap-4">
|
||
<p className="text-destructive">{error}</p>
|
||
{showBack ? (
|
||
<Button asChild variant="outline">
|
||
<Link href={backHref}>
|
||
<ArrowLeft className="mr-2 h-4 w-4" />
|
||
Retour
|
||
</Link>
|
||
</Button>
|
||
) : null}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
if (!config || !editorId || !serverUrl) {
|
||
return <p className="p-8 text-center text-muted-foreground">Ouverture du document…</p>
|
||
}
|
||
|
||
return (
|
||
<div className="flex h-dvh flex-col">
|
||
<OfficeEditorChrome
|
||
backHref={backHref}
|
||
backLabel="Partage"
|
||
showBack={showBack}
|
||
title={title}
|
||
trailing={
|
||
resolvedMode === "view" ? (
|
||
<span className="rounded-md bg-muted px-2 py-1 text-xs text-muted-foreground">
|
||
Lecture seule
|
||
</span>
|
||
) : null
|
||
}
|
||
/>
|
||
<div className="relative min-h-0 flex-1">
|
||
<OnlyOfficeMount
|
||
editorId={editorId}
|
||
documentServerUrl={serverUrl.replace(/\/$/, "")}
|
||
config={config}
|
||
onError={handleEditorError}
|
||
scriptId="onlyoffice-docs-api-public"
|
||
/>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|