Full document, spreadsheet, slideshow, and diagram tooling
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 86 lines 2.8 kB view raw
1/** 2 * .docx Import module for Atmosphere Docs. 3 * 4 * Uses mammoth.js to convert .docx files to HTML, then feeds the result 5 * into the TipTap editor. 6 */ 7import type { Editor } from '@tiptap/core'; 8import type { DocxConvertResult, DocxMessage } from './types.js'; 9 10/** 11 * Convert a .docx ArrayBuffer to HTML using mammoth.js. 12 * Pure async function — testable without DOM. 13 */ 14export async function convertDocxToHtml(arrayBuffer: ArrayBuffer): Promise<DocxConvertResult> { 15 const mammoth = await import('mammoth'); 16 17 // mammoth accepts { arrayBuffer } in the browser and { buffer } in Node.js. 18 // Detect environment and pass the right input format. 19 const isNode = typeof process !== 'undefined' && process.versions?.node; 20 const input = isNode ? { buffer: Buffer.from(arrayBuffer) } : { arrayBuffer }; 21 22 const options = { 23 styleMap: [ 24 "p[style-name='Heading 1'] => h1:fresh", 25 "p[style-name='Heading 2'] => h2:fresh", 26 "p[style-name='Heading 3'] => h3:fresh", 27 "p[style-name='Heading 4'] => h4:fresh", 28 "p[style-name='Heading 5'] => h5:fresh", 29 "p[style-name='Heading 6'] => h6:fresh", 30 ], 31 }; 32 33 const result = await mammoth.convertToHtml(input, options); 34 return { 35 html: result.value, 36 messages: (result.messages || []) as DocxMessage[], 37 }; 38} 39 40/** 41 * Validate that the given ArrayBuffer looks like a valid .docx file. 42 * A .docx is a ZIP file, so it starts with the PK signature (0x504B0304). 43 */ 44export function isValidDocx(arrayBuffer: ArrayBuffer): boolean { 45 if (!arrayBuffer || arrayBuffer.byteLength < 4) return false; 46 const view = new Uint8Array(arrayBuffer); 47 return view[0] === 0x50 && view[1] === 0x4B && view[2] === 0x03 && view[3] === 0x04; 48} 49 50/** 51 * Import a .docx File object into the TipTap editor. 52 * DOM-coupled entry point — not unit-testable. 53 */ 54export async function importDocx( 55 file: File, 56 editor: Editor, 57 showToast: (message: string, duration: number) => void, 58): Promise<void> { 59 try { 60 const arrayBuffer = await file.arrayBuffer(); 61 62 if (!isValidDocx(arrayBuffer)) { 63 showToast('Invalid .docx file — the file appears to be corrupt', 5000); 64 return; 65 } 66 67 const { html, messages } = await convertDocxToHtml(arrayBuffer); 68 69 if (!html || html.trim() === '') { 70 showToast('The .docx file appears to be empty', 3000); 71 return; 72 } 73 74 editor.commands.setContent(html); 75 76 const warnings = messages.filter((m: DocxMessage) => m.type === 'warning'); 77 if (warnings.length > 0) { 78 showToast(`Imported "${file.name}" with ${warnings.length} warning(s)`, 4000); 79 } else { 80 showToast(`Imported "${file.name}" successfully`, 3000); 81 } 82 } catch (err) { 83 console.error('docx import error:', err); 84 showToast('Failed to import .docx file — it may be corrupt or unsupported', 5000); 85 } 86}