/** * Represents a TipTap editor node structure. */ export interface TiptapNode { type: string; content?: TiptapNode[]; text?: string; attrs?: { [key: string]: any; }; } /** * Utility class for handling TipTap content conversions. * Provides methods to convert TipTap JSON content to HTML and plain text. */ export default class Content { /** * Converts TipTap raw JSON string content to plain text. * First converts to HTML, then strips HTML tags to produce plain text. * * @param content - The TipTap JSON string to convert * @returns The plain text representation of the content */ static convertTipTapRawToText(content: string): string { const htmlContent: string = this.convertTiptapToHTMLFromString(content); return this.htmlToText(htmlContent); } /** * Converts HTML string to plain text by removing tags and normalizing whitespace. * Preserves paragraph structure by converting block elements to newlines. * * @param html - The HTML string to convert * @returns The plain text representation with preserved paragraph structure */ static htmlToText(html: string): string { return html .replace(//gi, '\n') .replace(/<\/?(p|h[1-6]|div)(\s+[^>]*)?>/gi, '\n') .replace(/<\/?[^>]+(>|$)/g, '') .replace(/(\n\s*){2,}/g, '\n\n') .replace(/^\s+|\s+$|(?<=\s)\s+/g, '') .trim(); } /** * Converts a TipTap JSON string to HTML. * Parses the JSON string and delegates to the node-based conversion method. * * @param jsonString - The TipTap JSON string to convert * @returns The HTML representation, or empty string if JSON is invalid */ static convertTiptapToHTMLFromString(jsonString: string): string { let tiptapNode: TiptapNode; try { tiptapNode = JSON.parse(jsonString); } catch (error) { console.error('Invalid JSON string:', error); return ''; } return this.convertTiptapToHTML(tiptapNode); } /** * Recursively converts a TipTap node structure to HTML. * Handles various node types including documents, paragraphs, headings, lists, * blockquotes, code blocks, and text with formatting attributes. * * @param node - The TipTap node to convert * @returns The HTML representation of the node and its children */ static convertTiptapToHTML(node: TiptapNode): string { let html = ''; switch (node.type) { case 'doc': if (node.content) { node.content.forEach((childNode: TiptapNode) => { html += this.convertTiptapToHTML(childNode); }); } break; case 'paragraph': html += '

'; if (node.content) { node.content.forEach((childNode: TiptapNode) => { html += this.convertTiptapToHTML(childNode); }); } html += '

'; break; case 'text': let formattedText = node.text || ''; if (node.attrs) { if (node.attrs.bold) { formattedText = `${formattedText}`; } if (node.attrs.italic) { formattedText = `${formattedText}`; } if (node.attrs.underline) { formattedText = `${formattedText}`; } if (node.attrs.strike) { formattedText = `${formattedText}`; } if (node.attrs.link) { formattedText = `${formattedText}`; } } html += formattedText; break; case 'heading': const headingLevel = node.attrs?.level || 1; html += ``; if (node.content) { node.content.forEach((childNode: TiptapNode) => { html += this.convertTiptapToHTML(childNode); }); } html += ``; break; case 'bulletList': html += ''; break; case 'orderedList': html += '
    '; if (node.content) { node.content.forEach((childNode: TiptapNode) => { html += this.convertTiptapToHTML(childNode); }); } html += '
'; break; case 'listItem': html += '
  • '; if (node.content) { node.content.forEach((childNode: TiptapNode) => { html += this.convertTiptapToHTML(childNode); }); } html += '
  • '; break; case 'blockquote': html += '
    '; if (node.content) { node.content.forEach((childNode: TiptapNode) => { html += this.convertTiptapToHTML(childNode); }); } html += '
    '; break; case 'codeBlock': html += '
    ';
                    if (node.content) {
                        node.content.forEach((childNode: TiptapNode) => {
                            html += this.convertTiptapToHTML(childNode);
                        });
                    }
                    html += '
    '; break; default: console.warn(`Unhandled node type: ${node.type}`); if (node.content) { node.content.forEach((childNode: TiptapNode) => { html += this.convertTiptapToHTML(childNode); }); } break; } return html; } }