- Updated .env.example to include configuration for OnlyOffice Document Server. - Modified the workspace configuration to remove the drive-suite path. - Adjusted TypeScript environment imports for consistency. - Enhanced Next.js configuration to disable canvas in Webpack. - Updated package.json to include new dependencies for OnlyOffice and PDF.js. - Added global styles for OnlyOffice theme integration in the CSS. - Created new layout and page components for the Drive feature, including public sharing and editing functionalities. - Updated metadata handling across various layouts to reflect the new app structure.
136 lines
4.4 KiB
TypeScript
136 lines
4.4 KiB
TypeScript
import type { DriveFileInfo } from "@/lib/api/types"
|
|
import { blobForPreview } from "@/lib/api/drive-download"
|
|
|
|
export interface PublicShareView {
|
|
token: string
|
|
name: string
|
|
item_type: "file" | "folder"
|
|
path: string
|
|
permissions: number
|
|
owner_id?: string
|
|
owner_displayname?: string
|
|
files?: DriveFileInfo[]
|
|
file?: DriveFileInfo
|
|
}
|
|
|
|
/** Label for "Partagé par …" — display name, then local part of owner id, then fallback. */
|
|
export function publicShareOwnerLabel(data: Pick<PublicShareView, "owner_displayname" | "owner_id">): string {
|
|
const display = data.owner_displayname?.trim()
|
|
if (display) return display
|
|
const id = data.owner_id?.trim()
|
|
if (!id) return "Utilisateur"
|
|
const at = id.indexOf("@")
|
|
return at > 0 ? id.slice(0, at) : id
|
|
}
|
|
|
|
export function publicShareHref(token: string, path = "/"): string {
|
|
const base = `/drive/s/${encodeURIComponent(token)}`
|
|
const trimmed = path.replace(/^\/+|\/+$/g, "")
|
|
if (!trimmed) return base
|
|
return `${base}/${trimmed.split("/").map(encodeURIComponent).join("/")}`
|
|
}
|
|
|
|
export function folderPathFromPublicSegments(segments: string[] | undefined): string {
|
|
if (!segments?.length) return "/"
|
|
return "/" + segments.map((s) => decodeURIComponent(s)).join("/")
|
|
}
|
|
|
|
/** Extract NC or Ultidrive public share token from a share URL. */
|
|
export function extractShareTokenFromURL(url: string): string | null {
|
|
const trimmed = url.trim()
|
|
if (!trimmed) return null
|
|
try {
|
|
const parsed = new URL(trimmed, "http://localhost")
|
|
const match =
|
|
parsed.pathname.match(/\/index\.php\/s\/([^/]+)/) ??
|
|
parsed.pathname.match(/\/drive\/s\/([^/]+)/) ??
|
|
parsed.pathname.match(/\/s\/([^/]+)/)
|
|
return match ? decodeURIComponent(match[1]) : null
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
/** Prefer Ultidrive `/drive/s/{token}` URLs (absolute when origin known). */
|
|
export function normalizePublicShareURL(
|
|
url: string,
|
|
token?: string,
|
|
origin?: string
|
|
): string {
|
|
const resolvedToken = token?.trim() || extractShareTokenFromURL(url)
|
|
if (!resolvedToken) return url
|
|
const href = publicShareHref(resolvedToken)
|
|
const base = origin ?? (typeof window !== "undefined" ? window.location.origin : "")
|
|
return base ? `${base.replace(/\/$/, "")}${href}` : href
|
|
}
|
|
|
|
export function publicShareDownloadApiPath(
|
|
token: string,
|
|
filePath: string,
|
|
password?: string
|
|
): string {
|
|
const parts = filePath
|
|
.replace(/^\/+/, "")
|
|
.split("/")
|
|
.filter(Boolean)
|
|
.map((seg) => encodeURIComponent(seg))
|
|
const base = `/api/v1/drive/public/shares/${encodeURIComponent(token)}/download/${parts.join("/")}`
|
|
if (!password) return base
|
|
return `${base}?password=${encodeURIComponent(password)}`
|
|
}
|
|
|
|
export function publicSharePreviewApiPath(
|
|
token: string,
|
|
file: { path: string; name: string },
|
|
password?: string,
|
|
width = 400,
|
|
height = 300
|
|
): string {
|
|
const params = new URLSearchParams({
|
|
path: publicShareDownloadPath(file),
|
|
w: String(width),
|
|
h: String(height),
|
|
})
|
|
if (password) params.set("password", password)
|
|
return `/api/v1/drive/public/shares/${encodeURIComponent(token)}/preview?${params.toString()}`
|
|
}
|
|
|
|
export async function fetchPublicShare(
|
|
token: string,
|
|
path = "/",
|
|
password?: string
|
|
): Promise<PublicShareView> {
|
|
const params = new URLSearchParams()
|
|
if (path && path !== "/") params.set("path", path)
|
|
if (password) params.set("password", password)
|
|
const qs = params.toString()
|
|
const res = await fetch(
|
|
`/api/v1/drive/public/shares/${encodeURIComponent(token)}${qs ? `?${qs}` : ""}`
|
|
)
|
|
if (!res.ok) {
|
|
throw new Error(res.status === 403 ? "password_required" : "share_unavailable")
|
|
}
|
|
return (await res.json()) as PublicShareView
|
|
}
|
|
|
|
export function publicShareDownloadPath(file: { path: string; name: string }): string {
|
|
const path = (file.path ?? "").trim() || "/"
|
|
if (path === "/") return "/"
|
|
if (path.endsWith(`/${file.name}`) || path === `/${file.name}`) return path
|
|
return `${path.replace(/\/$/, "")}/${file.name}`.replace(/\/+/g, "/")
|
|
}
|
|
|
|
export async function fetchPublicShareBlob(
|
|
token: string,
|
|
file: { path: string; name: string; mime_type?: string },
|
|
password?: string
|
|
): Promise<Blob> {
|
|
const logical = publicShareDownloadPath(file)
|
|
const res = await fetch(publicShareDownloadApiPath(token, logical, password))
|
|
if (!res.ok) {
|
|
throw new Error("download_failed")
|
|
}
|
|
const raw = await res.blob()
|
|
return blobForPreview(raw, file.mime_type ?? "", file.name)
|
|
}
|