feat(files): in-app .docx preview + allow office/text mimes

- .docx now renders client-side via docx-preview (fetches bytes from our
  own storage; works with private MinIO/disk). Drops Microsoft's hosted
  Office viewer which can't reach a private object store.
- add office (.docx/.doc/.xlsx/.xls) + text/csv to PREVIEWABLE_MIMES so
  /api/v1/files/[id]/preview returns a URL instead of rejecting them
  (was surfacing as a misleading "Failed to load preview")
- legacy .doc + spreadsheets fall through to a download CTA (can't render
  client-side); text/csv use the existing TextPreview

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-03 15:45:11 +02:00
parent 95724c8e3a
commit 3b227fe9b2
5 changed files with 157 additions and 18 deletions

View File

@@ -34,8 +34,28 @@ export const PREVIEWABLE_MIMES = new Set<string>([
'image/gif',
'image/webp',
'application/pdf',
// Plain text + CSV render via the in-app TextPreview.
'text/plain',
'text/csv',
// Office formats: .docx renders client-side via docx-preview; the
// legacy/binary + spreadsheet formats fall through to a download CTA in
// the preview dialog. They're allow-listed here so the preview endpoint
// returns a URL instead of rejecting the file outright (which surfaced
// as a misleading "Failed to load preview"). We deliberately do NOT use
// Microsoft's hosted Office viewer — it can't reach our private storage.
'application/vnd.openxmlformats-officedocument.wordprocessingml.document', // .docx
'application/msword', // .doc
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // .xlsx
'application/vnd.ms-excel', // .xls
]);
/** True when the file is an OOXML Word document we can render in-browser. */
export function isWordDocx(mimeType: string | undefined, fileName: string | undefined): boolean {
if (mimeType === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document')
return true;
return (fileName ?? '').toLowerCase().endsWith('.docx');
}
/**
* Magic-byte signatures keyed by claimed MIME type. Used by the file
* upload handler to reject files whose first few bytes don't match the