/** * .docx Import module for Atmosphere Docs. * * Uses mammoth.js to convert .docx files to HTML, then feeds the result * into the TipTap editor. */ import type { Editor } from '@tiptap/core'; import type { DocxConvertResult, DocxMessage } from './types.js'; /** * Convert a .docx ArrayBuffer to HTML using mammoth.js. * Pure async function — testable without DOM. */ export async function convertDocxToHtml(arrayBuffer: ArrayBuffer): Promise { const mammoth = await import('mammoth'); // mammoth accepts { arrayBuffer } in the browser and { buffer } in Node.js. // Detect environment and pass the right input format. const isNode = typeof process !== 'undefined' && process.versions?.node; const input = isNode ? { buffer: Buffer.from(arrayBuffer) } : { arrayBuffer }; const options = { styleMap: [ "p[style-name='Heading 1'] => h1:fresh", "p[style-name='Heading 2'] => h2:fresh", "p[style-name='Heading 3'] => h3:fresh", "p[style-name='Heading 4'] => h4:fresh", "p[style-name='Heading 5'] => h5:fresh", "p[style-name='Heading 6'] => h6:fresh", ], }; const result = await mammoth.convertToHtml(input, options); return { html: result.value, messages: (result.messages || []) as DocxMessage[], }; } /** * Validate that the given ArrayBuffer looks like a valid .docx file. * A .docx is a ZIP file, so it starts with the PK signature (0x504B0304). */ export function isValidDocx(arrayBuffer: ArrayBuffer): boolean { if (!arrayBuffer || arrayBuffer.byteLength < 4) return false; const view = new Uint8Array(arrayBuffer); return view[0] === 0x50 && view[1] === 0x4B && view[2] === 0x03 && view[3] === 0x04; } /** * Import a .docx File object into the TipTap editor. * DOM-coupled entry point — not unit-testable. */ export async function importDocx( file: File, editor: Editor, showToast: (message: string, duration: number) => void, ): Promise { try { const arrayBuffer = await file.arrayBuffer(); if (!isValidDocx(arrayBuffer)) { showToast('Invalid .docx file — the file appears to be corrupt', 5000); return; } const { html, messages } = await convertDocxToHtml(arrayBuffer); if (!html || html.trim() === '') { showToast('The .docx file appears to be empty', 3000); return; } editor.commands.setContent(html); const warnings = messages.filter((m: DocxMessage) => m.type === 'warning'); if (warnings.length > 0) { showToast(`Imported "${file.name}" with ${warnings.length} warning(s)`, 4000); } else { showToast(`Imported "${file.name}" successfully`, 3000); } } catch (err) { console.error('docx import error:', err); showToast('Failed to import .docx file — it may be corrupt or unsupported', 5000); } }