/**
* Shared utilities for editor components
*/
/**
* Convert HTML to plain text
*/
export const htmlToText = (html: string): string => {
const div = document.createElement("div");
div.innerHTML = html;
return div.textContent || div.innerText || "";
};
/**
* Convert markdown to HTML (basic)
*/
export const markdownToHtml = (markdown: string): string => {
let html = markdown;
// Headers
html = html.replace(/^### (.*$)/gim, "
$1
");
html = html.replace(/^## (.*$)/gim, "$1
");
html = html.replace(/^# (.*$)/gim, "$1
");
// Bold
html = html.replace(/\*\*(.+?)\*\*/g, "$1");
// Italic
html = html.replace(/\*(.+?)\*/g, "$1");
// Links
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1');
// Code
html = html.replace(/`([^`]+)`/g, "$1");
// Line breaks
html = html.replace(/\n/g, "
");
return html;
};
/**
* Serialize Tiptap JSON to HTML
*/
export const serializeToHtml = (json: any): string => {
if (!json || !json.content) return "";
let html = "";
for (const node of json.content) {
switch (node.type) {
case "paragraph":
html += `${serializeContent(node.content)}
`;
break;
case "heading":
html += `${serializeContent(node.content)}`;
break;
case "bulletList":
html += `${serializeContent(node.content)}
`;
break;
case "orderedList":
html += `${serializeContent(node.content)}
`;
break;
case "listItem":
html += `${serializeContent(node.content)}`;
break;
case "blockquote":
html += `${serializeContent(node.content)}
`;
break;
case "codeBlock":
html += `${serializeContent(node.content)}
`;
break;
default:
if (node.content) {
html += serializeContent(node.content);
}
}
}
return html;
};
/**
* Serialize content array
*/
const serializeContent = (content?: any[]): string => {
if (!content) return "";
return content
.map((node) => {
switch (node.type) {
case "text": {
let text = node.text || "";
if (node.marks) {
for (const mark of node.marks) {
switch (mark.type) {
case "bold":
text = `${text}`;
break;
case "italic":
text = `${text}`;
break;
case "code":
text = `${text}`;
break;
case "link":
text = `${text}`;
break;
case "strike":
text = `${text}`;
break;
case "underline":
text = `${text}`;
break;
}
}
}
return text;
}
default:
return serializeToHtml({ content: [node] });
}
})
.join("");
};
/**
* Get word count from text
*/
export const getWordCount = (text: string): number => {
return text
.trim()
.split(/\s+/)
.filter((word) => word.length > 0).length;
};
/**
* Get character count from text
*/
export const getCharacterCount = (text: string): number => {
return text.length;
};
/**
* Estimate reading time in minutes
*/
export const getReadingTime = (text: string, wordsPerMinute = 200): number => {
const words = getWordCount(text);
return Math.ceil(words / wordsPerMinute);
};
/**
* Sanitize HTML to prevent XSS
*/
export const sanitizeHtml = (html: string): string => {
const div = document.createElement("div");
div.textContent = html;
return div.innerHTML;
};
/**
* Truncate text to specified length
*/
export const truncateText = (
text: string,
maxLength: number,
suffix = "...",
): string => {
if (text.length <= maxLength) return text;
return text.substring(0, maxLength - suffix.length) + suffix;
};
/**
* Extract plain text from Tiptap JSON
*/
export const extractText = (json: any): string => {
if (!json || !json.content) return "";
const extractFromContent = (content: any[]): string => {
return content
.map((node) => {
if (node.type === "text") {
return node.text || "";
}
if (node.content) {
return extractFromContent(node.content);
}
return "";
})
.join(" ");
};
return extractFromContent(json.content).trim();
};
/**
* Insert text at cursor position in textarea
*/
export const insertAtCursor = (
textarea: HTMLTextAreaElement,
text: string,
before = "",
after = "",
): void => {
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const value = textarea.value;
const newValue =
value.substring(0, start) + before + text + after + value.substring(end);
textarea.value = newValue;
// Set cursor position after inserted text
const newPosition = start + before.length + text.length;
textarea.setSelectionRange(newPosition, newPosition);
textarea.focus();
};