174 lines
5.2 KiB
TypeScript
174 lines
5.2 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" | "admin" | "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 UltiSuite",
|
|
contacts: "Carnet d'adresses — UltiSuite",
|
|
admin: "Console d'administration — UltiSuite",
|
|
suite: "Ultimail, UltiDrive et contacts — interface suite unifiée",
|
|
}
|
|
|
|
const APP_LABELS: Record<SuiteApp, string> = {
|
|
mail: "Ultimail",
|
|
drive: "UltiDrive",
|
|
contacts: "UltiSuite",
|
|
admin: "Administration",
|
|
suite: "UltiSuite",
|
|
}
|
|
|
|
const ICONS: Record<SuiteApp, Metadata["icons"]> = {
|
|
suite: {
|
|
icon: [
|
|
{ url: "/ultisuite-mark.svg", type: "image/svg+xml" },
|
|
{ url: "/ultisuite-32.png", sizes: "32x32", type: "image/png" },
|
|
],
|
|
apple: [{ url: "/ultisuite-180.png", sizes: "180x180", type: "image/png" }],
|
|
shortcut: "/ultisuite-mark.svg",
|
|
},
|
|
admin: {
|
|
icon: [{ url: "/admin-mark.svg", type: "image/svg+xml" }],
|
|
apple: [{ url: "/admin-mark.svg", type: "image/svg+xml" }],
|
|
shortcut: "/admin-mark.svg",
|
|
},
|
|
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: APP_LABELS.suite,
|
|
description: DESCRIPTIONS.suite,
|
|
icons: ICONS.suite,
|
|
}
|
|
}
|
|
|
|
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 = 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"
|
|
}
|
|
}
|