ultisuite-client/lib/suite/page-metadata.ts
R3D347HR4Y 6ec95262af Add OnlyOffice integration and update project configurations
- 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.
2026-06-07 15:49:21 +02:00

159 lines
4.7 KiB
TypeScript

import type { Metadata } from "next"
import { displayFileName } from "@/lib/drive/display-file-name"
import { parseDriveSegments } from "@/lib/drive/drive-url"
export type SuiteApp = "mail" | "drive" | "contacts" | "suite"
/** Separator between page segment and product name in document titles. */
export const SUITE_TITLE_SEP = " - "
export const MAIL_INBOX_DOCUMENT_TITLE = `Boîte mail${SUITE_TITLE_SEP}Ultimail`
const DESCRIPTIONS: Record<SuiteApp, string> = {
mail: "Client mail Ultimail — suite souveraine",
drive: "Stockage de fichiers UltiDrive — suite Ultimail",
contacts: "Carnet d'adresses — Ulti Suite",
suite: "Ultimail, UltiDrive et contacts — interface suite unifiée",
}
const APP_LABELS: Record<SuiteApp, string> = {
mail: "Ultimail",
drive: "UltiDrive",
contacts: "Ulti Suite",
suite: "Ulti Suite",
}
const ICONS: Record<Exclude<SuiteApp, "suite">, Metadata["icons"]> = {
mail: {
icon: [
{ url: "/brand/ultimail-header-icon.png", sizes: "109x109", type: "image/png" },
{ url: "/icon.png", sizes: "32x32", type: "image/png" },
],
apple: [{ url: "/brand/ultimail-header-icon.png", sizes: "180x180", type: "image/png" }],
shortcut: "/brand/ultimail-header-icon.png",
},
drive: {
icon: [
{ url: "/ultidrive-mark.svg", type: "image/svg+xml" },
{ url: "/drive/favicon-32.png", sizes: "32x32", type: "image/png" },
{ url: "/drive/favicon-16.png", sizes: "16x16", type: "image/png" },
],
apple: [{ url: "/drive/apple-touch-icon.png", sizes: "180x180", type: "image/png" }],
shortcut: "/ultidrive-mark.svg",
},
contacts: {
icon: [
{ url: "/contacts-mark.svg", type: "image/svg+xml" },
{ url: "/icon.png", sizes: "32x32", type: "image/png" },
],
apple: [{ url: "/contacts-mark.svg", type: "image/svg+xml" }],
shortcut: "/contacts-mark.svg",
},
}
type PageMetadataOptions = {
app: SuiteApp
/** Full document title (no suffix added). */
title?: string
/** Short segment only — parent layout `title.template` adds the product suffix. */
titleSegment?: string
description?: string
/** When true with `title`, use `title` as the full document title. */
absoluteTitle?: boolean
}
export function formatSuiteDocumentTitle(segment: string, app: SuiteApp): string {
return `${segment}${SUITE_TITLE_SEP}${APP_LABELS[app]}`
}
export function truncateSubjectForTitle(subject: string, maxLen = 48): string {
const clean = subject.replace(/\s+/g, " ").trim()
if (!clean) return ""
if (clean.length <= maxLen) return clean
return `${clean.slice(0, maxLen - 1).trimEnd()}`
}
export function mailDocumentTitle(subject: string | null | undefined): string {
const trimmed = subject?.replace(/\s+/g, " ").trim()
if (trimmed) {
return formatSuiteDocumentTitle(truncateSubjectForTitle(trimmed), "mail")
}
return MAIL_INBOX_DOCUMENT_TITLE
}
export function suiteRootMetadata(): Metadata {
return {
title: { default: APP_LABELS.suite },
description: DESCRIPTIONS.suite,
icons: ICONS.mail,
}
}
export function suitePageMetadata({
app,
title,
titleSegment,
description,
absoluteTitle = false,
}: PageMetadataOptions): Metadata {
const label = APP_LABELS[app]
let resolvedTitle: Metadata["title"]
if (titleSegment !== undefined) {
// Short segment — nearest ancestor `title.template` adds the product suffix once.
resolvedTitle = titleSegment
} else if (title === undefined) {
resolvedTitle = {
default: label,
template: `%s${SUITE_TITLE_SEP}${label}`,
}
} else if (absoluteTitle) {
resolvedTitle = { absolute: title }
} else {
// Full title already includes suffix — absolute so root/ancestor templates do not re-apply.
resolvedTitle = { absolute: formatSuiteDocumentTitle(title, app) }
}
const icons = app === "suite" ? ICONS.mail : ICONS[app]
const meta: Metadata = {
title: resolvedTitle,
description: description ?? DESCRIPTIONS[app],
icons,
}
if (app === "drive") {
meta.manifest = "/drive/manifest.webmanifest"
meta.appleWebApp = {
capable: true,
title: "UltiDrive",
statusBarStyle: "default",
}
}
return meta
}
export function driveDocumentTitle(segments?: string[]): string {
const route = parseDriveSegments(segments)
switch (route.view) {
case "recent":
return "Récents"
case "starred":
return "Favoris"
case "trash":
return "Corbeille"
case "shared":
return route.pathSegments.length > 0
? displayFileName(route.pathSegments.at(-1)!)
: "Partagés avec moi"
case "search":
return "Recherche"
default:
if (route.pathSegments.length > 0) {
return displayFileName(route.pathSegments.at(-1)!)
}
return "Mon Drive"
}
}