/**
* 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 = `'; 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;
}
}