- 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.
117 lines
4.1 KiB
TypeScript
117 lines
4.1 KiB
TypeScript
import type { Email, EmailAttachment, EmailAttachmentKind } from "@/lib/email-data"
|
||
|
||
/** Pièce jointe calendrier (invitation) — masquée dans la liste des messages. */
|
||
export function isCalendarInvitationAttachment(name: string): boolean {
|
||
return name.toLowerCase().endsWith(".ics")
|
||
}
|
||
|
||
/** PJ visibles dans la liste (hors fichiers .ics d’invitation). */
|
||
export function attachmentsForEmailList(
|
||
email: Pick<Email, "attachments" | "hasAttachment" | "hasInvitation">
|
||
): EmailAttachment[] {
|
||
if (email.attachments?.length) {
|
||
return email.attachments.filter((a) => !isCalendarInvitationAttachment(a.name))
|
||
}
|
||
if (email.hasAttachment && email.hasInvitation !== true) {
|
||
return [{ name: "Pièce jointe", kind: "other" }]
|
||
}
|
||
return []
|
||
}
|
||
|
||
/** Aperçu message : uniquement des PJ avec métadonnées réelles (jamais de chip générique). */
|
||
export function resolvePreviewAttachments(
|
||
email: Pick<Email, "attachments" | "hasAttachment" | "hasInvitation">,
|
||
fetched: EmailAttachment[] | undefined,
|
||
fetchState: "idle" | "loading" | "done"
|
||
): EmailAttachment[] {
|
||
if (email.attachments?.length) {
|
||
return attachmentsForEmailList(email)
|
||
}
|
||
if (fetched?.length) {
|
||
return attachmentsForEmailList({ ...email, attachments: fetched })
|
||
}
|
||
if (email.hasAttachment && fetchState === "loading") {
|
||
return []
|
||
}
|
||
return []
|
||
}
|
||
|
||
/** Liste : noms réels si fetch OK ; pas de chip générique pendant le chargement. */
|
||
export function resolveListRowAttachments(
|
||
email: Pick<Email, "attachments" | "hasAttachment" | "hasInvitation">,
|
||
fetched: EmailAttachment[] | undefined,
|
||
fetchState: "idle" | "loading" | "done"
|
||
): EmailAttachment[] {
|
||
if (email.attachments?.length) {
|
||
return attachmentsForEmailList(email)
|
||
}
|
||
if (fetched?.length) {
|
||
return attachmentsForEmailList({ ...email, attachments: fetched })
|
||
}
|
||
if (email.hasAttachment && fetchState === "loading") {
|
||
return []
|
||
}
|
||
return attachmentsForEmailList(email)
|
||
}
|
||
|
||
/** Fichiers « riches » (cartes type Gmail) : PDF, images, vidéos, bureautique. */
|
||
const RICH_PREVIEW_EXT =
|
||
/\.(mp4|mpe?g|webm|mov|avi|mkv|m4v|wmv|flv|xls|xlsx|xlsm|ods|numbers|ppt|pptx|key|odp|doc|docx|odt|rtf)$/i
|
||
|
||
/** Seuil « petit fichier » pour la rangée en pills (aperçu message uniquement). */
|
||
export const ATTACHMENT_PILL_MAX_BYTES = 2 * 1024 * 1024
|
||
|
||
export function resolveAttachmentKind(
|
||
name: string,
|
||
kind?: EmailAttachment["kind"]
|
||
): EmailAttachmentKind {
|
||
if (kind) return kind
|
||
const lower = name.toLowerCase()
|
||
if (lower.endsWith(".pdf")) return "pdf"
|
||
if (/\.(png|jpe?g|gif|webp|svg|avif|bmp|heic)$/.test(lower)) return "image"
|
||
return "other"
|
||
}
|
||
|
||
export function isRichPreviewAttachment(name: string, kind?: EmailAttachment["kind"]): boolean {
|
||
const resolved = resolveAttachmentKind(name, kind)
|
||
if (resolved === "pdf" || resolved === "image") return true
|
||
return RICH_PREVIEW_EXT.test(name.toLowerCase())
|
||
}
|
||
|
||
export function isSmallEnoughForPillRow(sizeBytes?: number): boolean {
|
||
if (sizeBytes === undefined) return true
|
||
return sizeBytes >= 0 && sizeBytes <= ATTACHMENT_PILL_MAX_BYTES
|
||
}
|
||
|
||
/** Tous les fichiers simples + petits → UI pills dans l’aperçu message. */
|
||
export function shouldUseAttachmentPillsInPreview(attachments: EmailAttachment[]): boolean {
|
||
if (attachments.length === 0) return false
|
||
return attachments.every(
|
||
(a) => !isRichPreviewAttachment(a.name, a.kind) && isSmallEnoughForPillRow(a.sizeBytes)
|
||
)
|
||
}
|
||
|
||
function decFr(n: number): string {
|
||
return String(n).replace(".", ",")
|
||
}
|
||
|
||
export function formatAttachmentSize(sizeBytes: number): string {
|
||
if (!Number.isFinite(sizeBytes) || sizeBytes < 0) return "—"
|
||
if (sizeBytes < 1000) {
|
||
return `${Math.round(sizeBytes)}\u00a0o`
|
||
}
|
||
const kb = sizeBytes / 1024
|
||
if (kb < 1024) {
|
||
const v = kb >= 10 ? Math.round(kb) : Math.round(kb * 10) / 10
|
||
return `${decFr(v)}\u00a0Ko`
|
||
}
|
||
const mb = kb / 1024
|
||
const v = mb >= 10 ? Math.round(mb) : Math.round(mb * 10) / 10
|
||
return `${decFr(v)}\u00a0Mo`
|
||
}
|
||
|
||
export function attachmentPreviewTooltip(name: string, sizeBytes?: number): string {
|
||
if (sizeBytes === undefined) return name
|
||
return `${name}\n${formatAttachmentSize(sizeBytes)}`
|
||
}
|