/** * DOCX Export (#103) * * Exports the TipTap editor content as a .docx file using the * Word-compatible HTML approach. Wraps the editor HTML in a * Word XML envelope that Microsoft Word and LibreOffice can open. * * This avoids heavy dependencies while producing correct output * for the most common formatting: headings, lists, bold, italic, * underline, tables, images, links, and code blocks. */ const WORD_HTML_PREFIX = ` `; const WORD_HTML_SUFFIX = ``; /** * Clean up TipTap-specific HTML for Word compatibility. */ export function cleanHtmlForWord(html: string): string { let cleaned = html; // Remove TipTap-specific attributes that Word doesn't understand cleaned = cleaned.replace(/\s+data-type="[^"]*"/g, ''); cleaned = cleaned.replace(/\s+data-checked="[^"]*"/g, ''); cleaned = cleaned.replace(/\s+contenteditable="[^"]*"/g, ''); cleaned = cleaned.replace(/\s+draggable="[^"]*"/g, ''); cleaned = cleaned.replace(/\s+class="[^"]*"/g, ''); // Convert task list checkboxes to text cleaned = cleaned.replace(/]*type="checkbox"[^>]*checked[^>]*>/gi, '☑ '); cleaned = cleaned.replace(/]*type="checkbox"[^>]*>/gi, '☐ '); // Remove empty paragraphs that Word renders as extra space cleaned = cleaned.replace(/

<\/p>/g, '

 

'); return cleaned; } export interface DocxExportOptions { editorHtml: string; title: string; } /** * Export editor content as a .docx file. * Creates a Word-compatible HTML document and triggers download. */ export function exportDocx({ editorHtml, title }: DocxExportOptions): void { const cleanedHtml = cleanHtmlForWord(editorHtml); const fullHtml = WORD_HTML_PREFIX + cleanedHtml + WORD_HTML_SUFFIX; const blob = new Blob([fullHtml], { type: 'application/vnd.ms-word', }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${title || 'document'}.doc`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }