import { type IBlock } from './wysiwyg.types.js'; export class WysiwygConverters { static escapeHtml(text: string): string { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } static formatFileSize(bytes: number): string { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } static getHtmlOutput(blocks: IBlock[]): string { return blocks.map(block => { // Check if content already contains HTML formatting const content = block.content.includes('<') && block.content.includes('>') ? block.content // Already contains HTML formatting : this.escapeHtml(block.content); switch (block.type) { case 'paragraph': return block.content ? `

${content}

` : ''; case 'heading-1': return `

${content}

`; case 'heading-2': return `

${content}

`; case 'heading-3': return `

${content}

`; case 'quote': return `
${content}
`; case 'code': return `
${this.escapeHtml(block.content)}
`; case 'list': const items = block.content.split('\n').filter(item => item.trim()); if (items.length > 0) { const listTag = block.metadata?.listType === 'ordered' ? 'ol' : 'ul'; // Don't escape HTML in list items to preserve formatting return `<${listTag}>${items.map(item => `
  • ${item}
  • `).join('')}`; } return ''; case 'divider': return '
    '; case 'image': const imageUrl = block.metadata?.url; if (imageUrl) { const altText = this.escapeHtml(block.content || 'Image'); return `${altText}`; } return ''; case 'youtube': const videoId = block.metadata?.videoId; if (videoId) { return ``; } return ''; case 'markdown': // Return the raw markdown content wrapped in a div return `
    ${this.escapeHtml(block.content)}
    `; case 'html': // Return the raw HTML content (already HTML) return block.content; case 'attachment': const files = block.metadata?.files || []; if (files.length > 0) { return `
    ${files.map((file: any) => `
    ${this.escapeHtml(file.name)} (${this.formatFileSize(file.size)})
    ` ).join('')}
    `; } return ''; default: return `

    ${content}

    `; } }).filter(html => html !== '').join('\n'); } static getMarkdownOutput(blocks: IBlock[]): string { return blocks.map(block => { switch (block.type) { case 'paragraph': return block.content; case 'heading-1': return `# ${block.content}`; case 'heading-2': return `## ${block.content}`; case 'heading-3': return `### ${block.content}`; case 'quote': return `> ${block.content}`; case 'code': return `\`\`\`\n${block.content}\n\`\`\``; case 'list': const items = block.content.split('\n').filter(item => item.trim()); if (block.metadata?.listType === 'ordered') { return items.map((item, index) => `${index + 1}. ${item}`).join('\n'); } else { return items.map(item => `- ${item}`).join('\n'); } case 'divider': return '---'; case 'image': const imageUrl = block.metadata?.url; const altText = block.content || 'Image'; return imageUrl ? `![${altText}](${imageUrl})` : ''; case 'youtube': const videoId = block.metadata?.videoId; const url = block.metadata?.url || (videoId ? `https://youtube.com/watch?v=${videoId}` : ''); return url ? `[YouTube Video](${url})` : ''; case 'markdown': // Return the raw markdown content return block.content; case 'html': // Return as HTML comment in markdown return ``; case 'attachment': const files = block.metadata?.files || []; if (files.length > 0) { return files.map((file: any) => `- [${file.name}](${file.data})`).join('\n'); } return ''; default: return block.content; } }).filter(md => md !== '').join('\n\n'); } static parseHtmlToBlocks(html: string): IBlock[] { const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const blocks: IBlock[] = []; const processNode = (node: Node) => { if (node.nodeType === Node.TEXT_NODE && node.textContent?.trim()) { blocks.push({ id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`, type: 'paragraph', content: node.textContent.trim(), }); } else if (node.nodeType === Node.ELEMENT_NODE) { const element = node as Element; const tagName = element.tagName.toLowerCase(); switch (tagName) { case 'p': blocks.push({ id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`, type: 'paragraph', content: element.innerHTML || '', }); break; case 'h1': blocks.push({ id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`, type: 'heading-1', content: element.innerHTML || '', }); break; case 'h2': blocks.push({ id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`, type: 'heading-2', content: element.innerHTML || '', }); break; case 'h3': blocks.push({ id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`, type: 'heading-3', content: element.innerHTML || '', }); break; case 'blockquote': blocks.push({ id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`, type: 'quote', content: element.innerHTML || '', }); break; case 'pre': case 'code': blocks.push({ id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`, type: 'code', content: element.textContent || '', }); break; case 'ul': case 'ol': const listItems = Array.from(element.querySelectorAll('li')); // Use innerHTML to preserve formatting const content = listItems.map(li => li.innerHTML || '').join('\n'); blocks.push({ id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`, type: 'list', content: content, metadata: { listType: tagName === 'ol' ? 'ordered' : 'bullet' } }); break; case 'hr': blocks.push({ id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`, type: 'divider', content: ' ', }); break; case 'img': const imgElement = element as HTMLImageElement; blocks.push({ id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`, type: 'image', content: imgElement.alt || '', metadata: { url: imgElement.src } }); break; default: // Process children for other elements element.childNodes.forEach(child => processNode(child)); } } }; doc.body.childNodes.forEach(node => processNode(node)); return blocks; } static parseMarkdownToBlocks(markdown: string): IBlock[] { const lines = markdown.split('\n'); const blocks: IBlock[] = []; let currentListItems: string[] = []; for (let i = 0; i < lines.length; i++) { const line = lines[i]; if (line.startsWith('# ')) { blocks.push({ id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`, type: 'heading-1', content: line.substring(2), }); } else if (line.startsWith('## ')) { blocks.push({ id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`, type: 'heading-2', content: line.substring(3), }); } else if (line.startsWith('### ')) { blocks.push({ id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`, type: 'heading-3', content: line.substring(4), }); } else if (line.startsWith('> ')) { blocks.push({ id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`, type: 'quote', content: line.substring(2), }); } else if (line.startsWith('```')) { const codeLines: string[] = []; i++; while (i < lines.length && !lines[i].startsWith('```')) { codeLines.push(lines[i]); i++; } blocks.push({ id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`, type: 'code', content: codeLines.join('\n'), }); } else if (line.match(/^(\*|-) /)) { currentListItems.push(line.substring(2)); // Check if next line is not a list item if (i === lines.length - 1 || (!lines[i + 1].match(/^(\*|-) /))) { blocks.push({ id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`, type: 'list', content: currentListItems.join('\n'), metadata: { listType: 'bullet' } }); currentListItems = []; } } else if (line.match(/^\d+\. /)) { currentListItems.push(line.replace(/^\d+\. /, '')); // Check if next line is not a numbered list item if (i === lines.length - 1 || (!lines[i + 1].match(/^\d+\. /))) { blocks.push({ id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`, type: 'list', content: currentListItems.join('\n'), metadata: { listType: 'ordered' } }); currentListItems = []; } } else if (line === '---' || line === '***' || line === '___') { blocks.push({ id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`, type: 'divider', content: ' ', }); } else if (line.match(/^!\[([^\]]*)\]\(([^\)]+)\)$/)) { // Parse markdown image syntax ![alt](url) const match = line.match(/^!\[([^\]]*)\]\(([^\)]+)\)$/); if (match) { blocks.push({ id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`, type: 'image', content: match[1] || '', metadata: { url: match[2] } }); } } else if (line.trim()) { blocks.push({ id: `block-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`, type: 'paragraph', content: line, }); } } return blocks; } }