courrier preview
This commit is contained in:
parent
fba2b1213f
commit
b149c52931
@ -16,7 +16,7 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import RichEmailEditor from '@/components/email/RichEmailEditor';
|
||||
import { processContentWithDirection } from '@/lib/utils/text-direction';
|
||||
import { detectTextDirection } from '@/lib/utils/text-direction';
|
||||
|
||||
// Import from the centralized utils
|
||||
import {
|
||||
@ -318,9 +318,6 @@ export default function ComposeEmail(props: ComposeEmailProps) {
|
||||
}
|
||||
};
|
||||
|
||||
// Get initial direction for the content
|
||||
const { direction } = processContentWithDirection(emailContent);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full max-h-[80vh] bg-white border rounded-md shadow-md">
|
||||
{/* Header */}
|
||||
|
||||
@ -2,10 +2,10 @@
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import { EmailContent } from '@/types/email';
|
||||
import { processContentWithDirection } from '@/lib/utils/text-direction';
|
||||
import { formatEmailContent } from '@/lib/utils/email-content';
|
||||
|
||||
interface EmailContentDisplayProps {
|
||||
content: EmailContent | null | undefined;
|
||||
content: EmailContent | string | null;
|
||||
className?: string;
|
||||
showQuotedText?: boolean;
|
||||
type?: 'html' | 'text' | 'auto';
|
||||
@ -23,58 +23,57 @@ const EmailContentDisplay: React.FC<EmailContentDisplayProps> = ({
|
||||
type = 'auto',
|
||||
debug = false
|
||||
}) => {
|
||||
// Process content with centralized utility
|
||||
// Process content if provided
|
||||
const processedContent = useMemo(() => {
|
||||
// Default empty content
|
||||
if (!content) {
|
||||
return {
|
||||
text: '',
|
||||
html: '<div class="text-gray-400">No content available</div>',
|
||||
direction: 'ltr' as const
|
||||
};
|
||||
}
|
||||
|
||||
// For text-only display, convert plain text to HTML first
|
||||
if (type === 'text') {
|
||||
const textContent = content.text || '';
|
||||
const formattedText = textContent
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/\n/g, '<br>');
|
||||
|
||||
return processContentWithDirection(formattedText);
|
||||
return { __html: '<div class="email-content-empty">No content available</div>' };
|
||||
}
|
||||
|
||||
// For auto mode, let the centralized function handle the content
|
||||
return processContentWithDirection(content);
|
||||
}, [content, type]);
|
||||
try {
|
||||
let formattedContent: string;
|
||||
|
||||
// If it's a string, we need to determine if it's HTML or plain text
|
||||
if (typeof content === 'string') {
|
||||
formattedContent = formatEmailContent({ content });
|
||||
}
|
||||
// If it's an EmailContent object
|
||||
else {
|
||||
formattedContent = formatEmailContent({ content });
|
||||
}
|
||||
|
||||
return { __html: formattedContent };
|
||||
} catch (error) {
|
||||
console.error('Error processing email content:', error);
|
||||
return { __html: '<div class="email-content-error">Error displaying email content</div>' };
|
||||
}
|
||||
}, [content]);
|
||||
|
||||
// Handle quoted text display
|
||||
const displayHTML = useMemo(() => {
|
||||
if (!showQuotedText) {
|
||||
// Hide quoted text (usually in blockquotes)
|
||||
// This is simplified - a more robust approach would parse and handle
|
||||
// quoted sections more intelligently
|
||||
return processedContent.html.replace(/<blockquote[^>]*>[\s\S]*?<\/blockquote>/gi,
|
||||
const htmlWithoutQuotes = processedContent.__html.replace(/<blockquote[^>]*>[\s\S]*?<\/blockquote>/gi,
|
||||
'<div class="text-gray-400">[Quoted text hidden]</div>');
|
||||
return { __html: htmlWithoutQuotes };
|
||||
}
|
||||
return processedContent.html;
|
||||
}, [processedContent.html, showQuotedText]);
|
||||
return processedContent;
|
||||
}, [processedContent, showQuotedText]);
|
||||
|
||||
return (
|
||||
<div className={`email-content-display ${className}`}>
|
||||
<div
|
||||
className="email-content-inner"
|
||||
dangerouslySetInnerHTML={{ __html: displayHTML }}
|
||||
dangerouslySetInnerHTML={displayHTML}
|
||||
/>
|
||||
|
||||
{/* Debug output if enabled */}
|
||||
{debug && (
|
||||
<div className="mt-4 p-2 text-xs bg-gray-100 border rounded">
|
||||
<p><strong>Content Type:</strong> {content?.isHtml ? 'HTML' : 'Text'}</p>
|
||||
<p><strong>Direction:</strong> {processedContent.direction}</p>
|
||||
<p><strong>HTML Length:</strong> {content?.html?.length || 0}</p>
|
||||
<p><strong>Text Length:</strong> {content?.text?.length || 0}</p>
|
||||
<p><strong>Content Type:</strong> {typeof content === 'string' ? 'Text' : 'HTML'}</p>
|
||||
<p><strong>HTML Length:</strong> {typeof content === 'string' ? content.length : content?.html?.length || 0}</p>
|
||||
<p><strong>Text Length:</strong> {typeof content === 'string' ? content.length : content?.text?.length || 0}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@ -2,8 +2,9 @@
|
||||
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import 'quill/dist/quill.snow.css';
|
||||
import { sanitizeHtml } from '@/lib/utils/email-utils';
|
||||
import { processContentWithDirection } from '@/lib/utils/text-direction';
|
||||
import { sanitizeHtml } from '@/lib/utils/dom-purify-config';
|
||||
import { detectTextDirection } from '@/lib/utils/text-direction';
|
||||
import { processHtmlContent } from '@/lib/utils/email-content';
|
||||
|
||||
interface RichEmailEditorProps {
|
||||
initialContent: string;
|
||||
@ -84,20 +85,19 @@ const RichEmailEditor: React.FC<RichEmailEditorProps> = ({
|
||||
theme: 'snow',
|
||||
});
|
||||
|
||||
// Process initial content to detect direction
|
||||
const { direction, html: processedContent } = processContentWithDirection(initialContent);
|
||||
|
||||
// Set initial content properly
|
||||
if (initialContent) {
|
||||
try {
|
||||
console.log('Setting initial content in editor', {
|
||||
length: initialContent.length,
|
||||
startsWithHtml: initialContent.trim().startsWith('<'),
|
||||
direction
|
||||
});
|
||||
|
||||
// Simplify complex email content to something Quill can handle better
|
||||
const sanitizedContent = sanitizeHtml(processedContent || initialContent);
|
||||
// Detect text direction
|
||||
const direction = detectTextDirection(initialContent);
|
||||
|
||||
// Process HTML content using centralized utility
|
||||
const sanitizedContent = processHtmlContent(initialContent);
|
||||
|
||||
// Check if sanitized content is valid
|
||||
if (sanitizedContent.trim().length === 0) {
|
||||
@ -212,11 +212,11 @@ const RichEmailEditor: React.FC<RichEmailEditorProps> = ({
|
||||
startsWithHtml: initialContent.trim().startsWith('<')
|
||||
});
|
||||
|
||||
// Process content to ensure correct direction
|
||||
const { direction, html: processedContent } = processContentWithDirection(initialContent);
|
||||
// Detect text direction
|
||||
const direction = detectTextDirection(initialContent);
|
||||
|
||||
// Sanitize the HTML
|
||||
const sanitizedContent = sanitizeHtml(processedContent || initialContent);
|
||||
// Process HTML content using centralized utility
|
||||
const sanitizedContent = processHtmlContent(initialContent);
|
||||
|
||||
// Check if content is valid HTML
|
||||
if (sanitizedContent.trim().length === 0) {
|
||||
|
||||
@ -1,11 +1,156 @@
|
||||
import DOMPurify from 'dompurify';
|
||||
import { detectTextDirection, applyTextDirection } from './text-direction';
|
||||
import { sanitizeHtml } from './email-utils';
|
||||
/**
|
||||
* Centralized Email Content Utilities
|
||||
*
|
||||
* This file contains all core functions for email content processing:
|
||||
* - Content extraction
|
||||
* - HTML sanitization
|
||||
* - Text direction handling
|
||||
* - URL fixing
|
||||
*
|
||||
* Other modules should import from this file rather than implementing their own versions.
|
||||
*/
|
||||
|
||||
import { sanitizeHtml } from './dom-purify-config';
|
||||
import { detectTextDirection } from './text-direction';
|
||||
import { EmailContent } from '@/types/email';
|
||||
|
||||
/**
|
||||
* Extract content from various possible email formats
|
||||
* Centralized implementation to reduce duplication across the codebase
|
||||
*/
|
||||
export function extractEmailContent(email: any): { text: string; html: string } {
|
||||
// Default empty values
|
||||
let textContent = '';
|
||||
let htmlContent = '';
|
||||
|
||||
// Early exit if no email
|
||||
if (!email) {
|
||||
console.log('extractEmailContent: No email provided');
|
||||
return { text: '', html: '' };
|
||||
}
|
||||
|
||||
try {
|
||||
// Extract based on common formats
|
||||
if (email.content && typeof email.content === 'object') {
|
||||
// Standard format with content object
|
||||
textContent = email.content.text || '';
|
||||
htmlContent = email.content.html || '';
|
||||
|
||||
// Handle complex email formats where content might be nested
|
||||
if (!textContent && !htmlContent) {
|
||||
// Try to find content in deeper nested structure
|
||||
if (email.content.body) {
|
||||
if (typeof email.content.body === 'string') {
|
||||
// Determine if body is HTML or text
|
||||
if (isHtmlContent(email.content.body)) {
|
||||
htmlContent = email.content.body;
|
||||
} else {
|
||||
textContent = email.content.body;
|
||||
}
|
||||
} else if (typeof email.content.body === 'object' && email.content.body) {
|
||||
// Some email formats nest content inside body
|
||||
htmlContent = email.content.body.html || '';
|
||||
textContent = email.content.body.text || '';
|
||||
}
|
||||
}
|
||||
|
||||
// Check for data property which some email services use
|
||||
if (!textContent && !htmlContent && email.content.data) {
|
||||
if (typeof email.content.data === 'string') {
|
||||
// Check if data looks like HTML
|
||||
if (isHtmlContent(email.content.data)) {
|
||||
htmlContent = email.content.data;
|
||||
} else {
|
||||
textContent = email.content.data;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (typeof email.content === 'string') {
|
||||
// Check if content is likely HTML
|
||||
if (isHtmlContent(email.content)) {
|
||||
htmlContent = email.content;
|
||||
} else {
|
||||
textContent = email.content;
|
||||
}
|
||||
} else {
|
||||
// Check other common properties
|
||||
htmlContent = email.html || '';
|
||||
textContent = email.text || '';
|
||||
|
||||
// If still no content, check for less common properties
|
||||
if (!htmlContent && !textContent) {
|
||||
// Try additional properties that some email clients use
|
||||
htmlContent = email.body?.html || email.bodyHtml || email.htmlBody || '';
|
||||
textContent = email.body?.text || email.bodyText || email.plainText || '';
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error extracting email content:', error);
|
||||
}
|
||||
|
||||
// Ensure we always have at least some text content
|
||||
if (!textContent && htmlContent) {
|
||||
textContent = extractTextFromHtml(htmlContent);
|
||||
}
|
||||
|
||||
// Log extraction results
|
||||
console.log('Extracted email content:', {
|
||||
hasHtml: !!htmlContent,
|
||||
htmlLength: htmlContent?.length || 0,
|
||||
hasText: !!textContent,
|
||||
textLength: textContent?.length || 0
|
||||
});
|
||||
|
||||
return { text: textContent, html: htmlContent };
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract plain text from HTML content
|
||||
*/
|
||||
export function extractTextFromHtml(html: string): string {
|
||||
if (!html) return '';
|
||||
|
||||
try {
|
||||
// Use DOM API if available
|
||||
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
|
||||
const tempDiv = document.createElement('div');
|
||||
tempDiv.innerHTML = html;
|
||||
return tempDiv.textContent || tempDiv.innerText || '';
|
||||
} else {
|
||||
// Simple regex fallback for non-browser environments
|
||||
return html.replace(/<[^>]*>/g, ' ')
|
||||
.replace(/ /g, ' ')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error extracting text from HTML:', e);
|
||||
// Fallback to basic strip
|
||||
return html.replace(/<[^>]*>/g, ' ').trim();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a string is likely HTML content
|
||||
*/
|
||||
export function isHtmlContent(content: string): boolean {
|
||||
if (!content) return false;
|
||||
|
||||
return content.trim().startsWith('<') &&
|
||||
(content.includes('<html') ||
|
||||
content.includes('<body') ||
|
||||
content.includes('<div') ||
|
||||
content.includes('<p>') ||
|
||||
content.includes('<br>'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Format and standardize email content for display following email industry standards.
|
||||
* This function handles various email content formats and ensures proper display
|
||||
* including support for HTML emails, plain text emails, RTL languages, and email client quirks.
|
||||
* This is the main entry point for rendering email content.
|
||||
*/
|
||||
export function formatEmailContent(email: any): string {
|
||||
if (!email) {
|
||||
@ -14,76 +159,68 @@ export function formatEmailContent(email: any): string {
|
||||
}
|
||||
|
||||
try {
|
||||
// Get the content in order of preference with proper fallbacks
|
||||
let content = '';
|
||||
let isHtml = false;
|
||||
let textContent = '';
|
||||
|
||||
// Extract content based on standardized property hierarchy
|
||||
if (email.content && typeof email.content === 'object') {
|
||||
isHtml = !!email.content.html;
|
||||
content = email.content.html || '';
|
||||
textContent = email.content.text || '';
|
||||
} else if (typeof email.content === 'string') {
|
||||
// Check if the string content is HTML
|
||||
isHtml = email.content.trim().startsWith('<') &&
|
||||
(email.content.includes('<html') ||
|
||||
email.content.includes('<body') ||
|
||||
email.content.includes('<div') ||
|
||||
email.content.includes('<p>'));
|
||||
content = email.content;
|
||||
textContent = email.content;
|
||||
} else if (email.html) {
|
||||
isHtml = true;
|
||||
content = email.html;
|
||||
textContent = email.text || '';
|
||||
} else if (email.text) {
|
||||
isHtml = false;
|
||||
content = '';
|
||||
textContent = email.text;
|
||||
} else if (email.formattedContent) {
|
||||
// Assume formattedContent is already HTML
|
||||
isHtml = true;
|
||||
content = email.formattedContent;
|
||||
textContent = '';
|
||||
}
|
||||
|
||||
// Log what we found for debugging
|
||||
console.log(`Email content detected: isHtml=${isHtml}, contentLength=${content.length}, textLength=${textContent.length}`);
|
||||
// Extract content from email
|
||||
const { text, html } = extractEmailContent(email);
|
||||
|
||||
// If we have HTML content, sanitize and standardize it
|
||||
if (isHtml && content) {
|
||||
// CRITICAL FIX: Check for browser environment since DOMParser is browser-only
|
||||
const hasHtmlTag = content.includes('<html');
|
||||
const hasBodyTag = content.includes('<body');
|
||||
if (html) {
|
||||
// Process HTML content
|
||||
let processedHtml = processHtmlContent(html, text);
|
||||
|
||||
// Extract body content if we have a complete HTML document and in browser environment
|
||||
if (hasHtmlTag && hasBodyTag && typeof window !== 'undefined' && typeof DOMParser !== 'undefined') {
|
||||
try {
|
||||
// Create a DOM parser to extract just the body content
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(content, 'text/html');
|
||||
const bodyContent = doc.body.innerHTML;
|
||||
|
||||
if (bodyContent) {
|
||||
content = bodyContent;
|
||||
}
|
||||
} catch (error) {
|
||||
// If extraction fails, continue with the original content
|
||||
console.error('Error extracting body content:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Use the centralized sanitizeHtml function
|
||||
let sanitizedContent = sanitizeHtml(content);
|
||||
|
||||
// Fix URL encoding issues that might occur during sanitization
|
||||
// Apply styling
|
||||
return `<div class="email-content" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; line-height: 1.6; color: #333; max-width: 100%; overflow-x: auto; overflow-wrap: break-word; word-wrap: break-word;" dir="${detectTextDirection(text)}">${processedHtml}</div>`;
|
||||
}
|
||||
// If we only have text content, format it properly
|
||||
else if (text) {
|
||||
return formatPlainTextToHtml(text);
|
||||
}
|
||||
|
||||
// Default case: empty or unrecognized content
|
||||
return '<div class="email-content-empty" style="padding: 20px; text-align: center; color: #666;">No content available</div>';
|
||||
} catch (error) {
|
||||
console.error('formatEmailContent: Error formatting email content:', error);
|
||||
return `<div class="email-content-error" style="padding: 15px; color: #721c24; background-color: #f8d7da; border: 1px solid #f5c6cb; border-radius: 4px;"><p>Error displaying email content</p><p style="font-size: 12px; margin-top: 10px;">${error instanceof Error ? error.message : 'Unknown error'}</p></div>`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process HTML content to fix common email rendering issues
|
||||
*/
|
||||
export function processHtmlContent(htmlContent: string, textContent?: string): string {
|
||||
if (!htmlContent) return '';
|
||||
|
||||
try {
|
||||
// Check for browser environment (DOMParser is browser-only)
|
||||
const hasHtmlTag = htmlContent.includes('<html');
|
||||
const hasBodyTag = htmlContent.includes('<body');
|
||||
|
||||
// Extract body content if we have a complete HTML document and in browser environment
|
||||
if (hasHtmlTag && hasBodyTag && typeof window !== 'undefined' && typeof DOMParser !== 'undefined') {
|
||||
try {
|
||||
// Create a DOM parser to extract just the body content
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(htmlContent, 'text/html');
|
||||
const bodyContent = doc.body.innerHTML;
|
||||
|
||||
if (bodyContent) {
|
||||
htmlContent = bodyContent;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error extracting body content:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Use the centralized sanitizeHtml function
|
||||
let sanitizedContent = sanitizeHtml(htmlContent);
|
||||
|
||||
// Fix URL encoding issues
|
||||
try {
|
||||
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
|
||||
// Temporary element to manipulate the HTML
|
||||
const tempDiv = document.createElement('div');
|
||||
tempDiv.innerHTML = sanitizedContent;
|
||||
|
||||
// Fix all links
|
||||
// Fix all links that might have been double-encoded
|
||||
const links = tempDiv.querySelectorAll('a');
|
||||
links.forEach(link => {
|
||||
const href = link.getAttribute('href');
|
||||
@ -101,54 +238,50 @@ export function formatEmailContent(email: any): string {
|
||||
|
||||
// Get the fixed HTML
|
||||
sanitizedContent = tempDiv.innerHTML;
|
||||
} catch (e) {
|
||||
console.error('Error fixing URLs in content:', e);
|
||||
}
|
||||
|
||||
// Fix common email client quirks
|
||||
let fixedContent = sanitizedContent
|
||||
// Fix for Outlook WebVML content
|
||||
.replace(/<!--\[if\s+gte\s+mso/g, '<!--[if gte mso')
|
||||
// Fix for broken image paths that might be relative
|
||||
.replace(/(src|background)="(?!(?:https?:|data:|cid:))/gi, '$1="https://')
|
||||
// Fix for base64 images that might be broken across lines
|
||||
.replace(/src="data:image\/[^;]+;base64,\s*([^"]+)\s*"/gi, (match, p1) => {
|
||||
return `src="data:image/png;base64,${p1.replace(/\s+/g, '')}"`;
|
||||
});
|
||||
|
||||
// Use the centralized text direction utility
|
||||
const styledContent = `<div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; line-height: 1.6; color: #333; max-width: 100%; overflow-x: auto; overflow-wrap: break-word; word-wrap: break-word;">${fixedContent}</div>`;
|
||||
|
||||
// Apply correct text direction using the centralized utility
|
||||
return applyTextDirection(styledContent, textContent);
|
||||
}
|
||||
// If we only have text content, format it properly
|
||||
else if (textContent) {
|
||||
// Escape HTML characters to prevent XSS
|
||||
const escapedText = textContent
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
|
||||
// Format plain text with proper line breaks and paragraphs
|
||||
const formattedText = escapedText
|
||||
.replace(/\r\n|\r|\n/g, '<br>') // Convert all newlines to <br>
|
||||
.replace(/((?:<br>){2,})/g, '</p><p>') // Convert multiple newlines to paragraphs
|
||||
.replace(/<br><\/p>/g, '</p>') // Fix any <br></p> combinations
|
||||
.replace(/<p><br>/g, '<p>'); // Fix any <p><br> combinations
|
||||
|
||||
const styledPlainText = `<div style="font-family: -apple-system, BlinkMacSystemFont, Menlo, Monaco, Consolas, 'Courier New', monospace; white-space: pre-wrap; line-height: 1.5; color: #333; padding: 15px; max-width: 100%; overflow-wrap: break-word;"><p>${formattedText}</p></div>`;
|
||||
|
||||
// Apply correct text direction using the centralized utility
|
||||
return applyTextDirection(styledPlainText, textContent);
|
||||
} catch (e) {
|
||||
console.error('Error fixing URLs in content:', e);
|
||||
}
|
||||
|
||||
// Default case: empty or unrecognized content
|
||||
return '<div class="email-content-empty" style="padding: 20px; text-align: center; color: #666;">No content available</div>';
|
||||
// Fix common email client quirks
|
||||
return sanitizedContent
|
||||
// Fix for Outlook WebVML content
|
||||
.replace(/<!--\[if\s+gte\s+mso/g, '<!--[if gte mso')
|
||||
// Fix for broken image paths that might be relative
|
||||
.replace(/(src|background)="(?!(?:https?:|data:|cid:))/gi, '$1="https://')
|
||||
// Fix for base64 images that might be broken across lines
|
||||
.replace(/src="data:image\/[^;]+;base64,\s*([^"]+)\s*"/gi, (match, p1) => {
|
||||
return `src="data:image/png;base64,${p1.replace(/\s+/g, '')}"`;
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('formatEmailContent: Error formatting email content:', error);
|
||||
return `<div class="email-content-error" style="padding: 15px; color: #721c24; background-color: #f8d7da; border: 1px solid #f5c6cb; border-radius: 4px;"><p>Error displaying email content</p><p style="font-size: 12px; margin-top: 10px;">${error instanceof Error ? error.message : 'Unknown error'}</p></div>`;
|
||||
console.error('Error processing HTML content:', error);
|
||||
return htmlContent;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format plain text to HTML with proper line breaks and styling
|
||||
*/
|
||||
export function formatPlainTextToHtml(text: string): string {
|
||||
if (!text) return '';
|
||||
|
||||
// Detect text direction
|
||||
const direction = detectTextDirection(text);
|
||||
|
||||
// Escape HTML characters to prevent XSS
|
||||
const escapedText = text
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
|
||||
// Format plain text with proper line breaks and paragraphs
|
||||
const formattedText = escapedText
|
||||
.replace(/\r\n|\r|\n/g, '<br>') // Convert all newlines to <br>
|
||||
.replace(/((?:<br>){2,})/g, '</p><p>') // Convert multiple newlines to paragraphs
|
||||
.replace(/<br><\/p>/g, '</p>') // Fix any <br></p> combinations
|
||||
.replace(/<p><br>/g, '<p>'); // Fix any <p><br> combinations
|
||||
|
||||
return `<div class="email-content" style="font-family: -apple-system, BlinkMacSystemFont, Menlo, Monaco, Consolas, 'Courier New', monospace; white-space: pre-wrap; line-height: 1.5; color: #333; padding: 15px; max-width: 100%; overflow-wrap: break-word;" dir="${direction}"><p>${formattedText}</p></div>`;
|
||||
}
|
||||
@ -1,154 +0,0 @@
|
||||
/**
|
||||
* DEPRECATED - USE email-utils.ts INSTEAD
|
||||
*
|
||||
* This file is maintained for backward compatibility only.
|
||||
* New code should import directly from email-utils.ts, which contains
|
||||
* the canonical implementations of these functions.
|
||||
*/
|
||||
|
||||
import { sanitizeHtml } from './dom-purify-config';
|
||||
import { formatEmailAddresses, formatEmailDate, formatReplyEmail, formatForwardedEmail, formatEmailForReplyOrForward } from './email-utils';
|
||||
import { applyTextDirection } from './text-direction';
|
||||
import type { EmailMessage, EmailAddress } from '@/types/email';
|
||||
|
||||
// Re-export the functions from email-utils for backward compatibility
|
||||
export {
|
||||
formatEmailAddresses,
|
||||
formatEmailDate,
|
||||
formatReplyEmail,
|
||||
formatForwardedEmail,
|
||||
formatEmailForReplyOrForward,
|
||||
sanitizeHtml
|
||||
};
|
||||
|
||||
// Re-export types for backward compatibility
|
||||
export type { EmailAddress, EmailMessage };
|
||||
|
||||
/**
|
||||
* Format a date in a relative format
|
||||
* Simple implementation for email display
|
||||
*/
|
||||
function formatDateRelative(date: Date): string {
|
||||
if (!date) return '';
|
||||
|
||||
try {
|
||||
return date.toLocaleString('en-US', {
|
||||
weekday: 'short',
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
} catch (e) {
|
||||
return date.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse text and find URLs to turn into clickable links
|
||||
* This is a utility function used only in this file
|
||||
*/
|
||||
function parseUrlsToLinks(text: string): string {
|
||||
const urlRegex = /(https?:\/\/[^\s]+)/g;
|
||||
return text.replace(
|
||||
urlRegex,
|
||||
url => `<a href="${url}" target="_blank" rel="noopener noreferrer">${url}</a>`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert plain text email content to HTML with proper line breaks
|
||||
* This is a utility function used only in this file
|
||||
*/
|
||||
function textToHtml(text: string): string {
|
||||
if (!text) return '';
|
||||
|
||||
// Escape HTML characters first
|
||||
const escaped = text
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
|
||||
// Convert line breaks and wrap in a div
|
||||
const withLineBreaks = escaped.replace(/\n/g, '<br>');
|
||||
|
||||
// Parse URLs to make them clickable
|
||||
const withLinks = parseUrlsToLinks(withLineBreaks);
|
||||
|
||||
return withLinks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode compose content from MIME format to HTML and text
|
||||
*/
|
||||
export async function decodeComposeContent(content: string): Promise<{
|
||||
html: string | null;
|
||||
text: string | null;
|
||||
}> {
|
||||
if (!content.trim()) {
|
||||
return { html: null, text: null };
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/parse-email', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ email: content }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to parse email');
|
||||
}
|
||||
|
||||
const parsed = await response.json();
|
||||
|
||||
// Apply sanitization to the parsed content
|
||||
return {
|
||||
html: parsed.html ? sanitizeHtml(parsed.html) : null,
|
||||
text: parsed.text || null
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error parsing email content:', error);
|
||||
// Fallback to basic content handling with sanitization
|
||||
return {
|
||||
html: sanitizeHtml(content),
|
||||
text: content
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode compose content to MIME format for sending
|
||||
*/
|
||||
export function encodeComposeContent(content: string): string {
|
||||
if (!content.trim()) {
|
||||
throw new Error('Email content is empty');
|
||||
}
|
||||
|
||||
// Create MIME headers
|
||||
const mimeHeaders = {
|
||||
'MIME-Version': '1.0',
|
||||
'Content-Type': 'text/html; charset="utf-8"',
|
||||
'Content-Transfer-Encoding': 'quoted-printable'
|
||||
};
|
||||
|
||||
// Combine headers and content
|
||||
return Object.entries(mimeHeaders)
|
||||
.map(([key, value]) => `${key}: ${value}`)
|
||||
.join('\n') + '\n\n' + content;
|
||||
}
|
||||
|
||||
// Utility function to get the reply subject line
|
||||
export function getReplySubject(subject: string): string {
|
||||
return subject.startsWith('Re:') ? subject : `Re: ${subject}`;
|
||||
}
|
||||
|
||||
// Utility function to get the forward subject line
|
||||
export function getForwardSubject(subject: string): string {
|
||||
return subject.startsWith('Fwd:') ? subject : `Fwd: ${subject}`;
|
||||
}
|
||||
@ -1,14 +1,25 @@
|
||||
/**
|
||||
* Unified Email Utilities
|
||||
*
|
||||
* This file contains all email-related utility functions:
|
||||
* - Content normalization
|
||||
* - Email formatting (replies, forwards)
|
||||
* - Text direction handling
|
||||
* This file provides backward compatibility for email utilities.
|
||||
* New code should import directly from the specialized modules:
|
||||
* - email-content.ts (content processing)
|
||||
* - text-direction.ts (direction handling)
|
||||
* - dom-purify-config.ts (sanitization)
|
||||
*/
|
||||
|
||||
// Import from centralized configuration
|
||||
// Import from specialized modules
|
||||
import { sanitizeHtml } from './dom-purify-config';
|
||||
import { detectTextDirection, applyTextDirection } from './text-direction';
|
||||
import {
|
||||
extractEmailContent,
|
||||
formatEmailContent,
|
||||
processHtmlContent,
|
||||
formatPlainTextToHtml,
|
||||
isHtmlContent,
|
||||
extractTextFromHtml
|
||||
} from './email-content';
|
||||
|
||||
import {
|
||||
EmailMessage,
|
||||
EmailContent,
|
||||
@ -17,16 +28,18 @@ import {
|
||||
} from '@/types/email';
|
||||
import { adaptLegacyEmail } from '@/lib/utils/email-adapters';
|
||||
import { decodeInfomaniakEmail, adaptMimeEmail, isMimeFormat } from './email-mime-decoder';
|
||||
import {
|
||||
detectTextDirection,
|
||||
applyTextDirection,
|
||||
extractEmailContent,
|
||||
processContentWithDirection
|
||||
} from '@/lib/utils/text-direction';
|
||||
import { format } from 'date-fns';
|
||||
|
||||
// Export the sanitizeHtml function from the centralized config
|
||||
export { sanitizeHtml };
|
||||
// Re-export important functions for backward compatibility
|
||||
export {
|
||||
sanitizeHtml,
|
||||
extractEmailContent,
|
||||
formatEmailContent,
|
||||
processHtmlContent,
|
||||
formatPlainTextToHtml,
|
||||
detectTextDirection,
|
||||
applyTextDirection
|
||||
};
|
||||
|
||||
/**
|
||||
* Standard interface for formatted email responses
|
||||
@ -88,28 +101,6 @@ export function formatEmailDate(date: Date | string | undefined): string {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format plain text for HTML display with proper line breaks
|
||||
*/
|
||||
export function formatPlainTextToHtml(text: string | null | undefined): string {
|
||||
if (!text) return '';
|
||||
|
||||
// Escape HTML characters to prevent XSS
|
||||
const escapedText = text
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
|
||||
// Format plain text with proper line breaks and paragraphs
|
||||
return escapedText
|
||||
.replace(/\r\n|\r|\n/g, '<br>') // Convert all newlines to <br>
|
||||
.replace(/((?:<br>){2,})/g, '</p><p>') // Convert multiple newlines to paragraphs
|
||||
.replace(/<br><\/p>/g, '</p>') // Fix any <br></p> combinations
|
||||
.replace(/<p><br>/g, '<p>'); // Fix any <p><br> combinations
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize email content to our standard format regardless of input format
|
||||
*/
|
||||
@ -158,11 +149,11 @@ export function renderEmailContent(content: EmailContent | null): string {
|
||||
return '<div class="email-content-empty">No content available</div>';
|
||||
}
|
||||
|
||||
// Use the centralized content processing function
|
||||
const processed = processContentWithDirection(content);
|
||||
// Create a simple object that can be processed by formatEmailContent
|
||||
const emailObj = { content };
|
||||
|
||||
// Return the processed HTML with proper direction
|
||||
return processed.html || '<div class="email-content-empty">No content available</div>';
|
||||
// Use the centralized formatting function
|
||||
return formatEmailContent(emailObj);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -257,71 +248,6 @@ function getFormattedHeaderInfo(email: any): {
|
||||
return { fromStr, toStr, ccStr, dateStr, subject };
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract image attachments from HTML content
|
||||
*/
|
||||
function extractInlineImages(htmlContent: string): Array<{
|
||||
filename: string;
|
||||
contentType: string;
|
||||
content?: string;
|
||||
}> {
|
||||
const images: Array<{
|
||||
filename: string;
|
||||
contentType: string;
|
||||
content?: string;
|
||||
}> = [];
|
||||
|
||||
try {
|
||||
if (!htmlContent || typeof window === 'undefined') return images;
|
||||
|
||||
// Create a temporary DOM element to parse the HTML
|
||||
const tempDiv = document.createElement('div');
|
||||
tempDiv.innerHTML = htmlContent;
|
||||
|
||||
// Find all image elements
|
||||
const imgElements = tempDiv.querySelectorAll('img');
|
||||
|
||||
// Process each image
|
||||
imgElements.forEach((img, index) => {
|
||||
const src = img.getAttribute('src');
|
||||
if (!src) return;
|
||||
|
||||
// Only process data URLs and non-tracking pixels
|
||||
if (src.startsWith('data:image')) {
|
||||
const contentType = src.split(',')[0].split(':')[1].split(';')[0];
|
||||
const imageData = src.split(',')[1];
|
||||
|
||||
// Skip tiny images (likely tracking pixels)
|
||||
if (imageData.length < 100) return;
|
||||
|
||||
images.push({
|
||||
filename: `inline-image-${index + 1}.${contentType.split('/')[1] || 'png'}`,
|
||||
contentType,
|
||||
content: imageData
|
||||
});
|
||||
|
||||
// Replace the image source with a placeholder
|
||||
img.setAttribute('src', `cid:inline-image-${index + 1}`);
|
||||
}
|
||||
else if (src.startsWith('cid:')) {
|
||||
// Already a CID reference, just add a placeholder
|
||||
const cid = src.substring(4);
|
||||
images.push({
|
||||
filename: `${cid}.png`,
|
||||
contentType: 'image/png'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Update the HTML content to use the placeholders
|
||||
htmlContent = tempDiv.innerHTML;
|
||||
} catch (error) {
|
||||
console.error('Error extracting inline images:', error);
|
||||
}
|
||||
|
||||
return images;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format email for reply
|
||||
*/
|
||||
@ -349,15 +275,9 @@ export function formatReplyEmail(originalEmail: EmailMessage | LegacyEmailMessag
|
||||
// Extract just the text content for a clean reply
|
||||
let emailText = '';
|
||||
|
||||
// Try to get text directly from content.text first
|
||||
if (originalEmail.content && typeof originalEmail.content === 'object' && originalEmail.content.text) {
|
||||
emailText = originalEmail.content.text;
|
||||
}
|
||||
// Otherwise, fall back to extractEmailContent which tries various formats
|
||||
else {
|
||||
const { text } = extractEmailContent(originalEmail);
|
||||
emailText = text;
|
||||
}
|
||||
// Extract content using the centralized extraction function
|
||||
const { text, html } = extractEmailContent(originalEmail);
|
||||
emailText = text;
|
||||
|
||||
// Create simple reply with header
|
||||
const cleanReplyHeader = `<div style="margin: 20px 0 10px 0; color: #666; border-bottom: 1px solid #eee; padding-bottom: 10px;">On ${dateStr}, ${fromStr} wrote:</div>`;
|
||||
@ -417,15 +337,9 @@ export function formatForwardedEmail(originalEmail: EmailMessage | LegacyEmailMe
|
||||
// Extract just the text content for a clean forward
|
||||
let emailText = '';
|
||||
|
||||
// Try to get text directly from content.text first
|
||||
if (originalEmail.content && typeof originalEmail.content === 'object' && originalEmail.content.text) {
|
||||
emailText = originalEmail.content.text;
|
||||
}
|
||||
// Otherwise, fall back to extractEmailContent which tries various formats
|
||||
else {
|
||||
const { text } = extractEmailContent(originalEmail);
|
||||
emailText = text;
|
||||
}
|
||||
// Extract content using the centralized extraction function
|
||||
const { text, html } = extractEmailContent(originalEmail);
|
||||
emailText = text;
|
||||
|
||||
// Create simple forward with metadata header
|
||||
const cleanForwardHeader = `
|
||||
|
||||
@ -1,13 +1,10 @@
|
||||
/**
|
||||
* Text Direction Utilities
|
||||
*
|
||||
* Centralized utilities for handling text direction (RTL/LTR)
|
||||
* Core utilities for handling text direction (RTL/LTR)
|
||||
* to ensure consistent behavior across the application.
|
||||
*/
|
||||
|
||||
import { sanitizeHtml } from './dom-purify-config';
|
||||
import { EmailContent } from '@/types/email';
|
||||
|
||||
/**
|
||||
* Detects if text contains RTL characters and should be displayed right-to-left
|
||||
* Uses a comprehensive regex pattern that covers Arabic, Hebrew, and other RTL scripts
|
||||
@ -64,278 +61,4 @@ export function applyTextDirection(htmlContent: string, textContent?: string): s
|
||||
|
||||
// Otherwise, wrap the content with a direction-aware container
|
||||
return `<div class="email-content" dir="${direction}">${htmlContent}</div>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts content from various possible email formats
|
||||
* Reduces duplication across the codebase for content extraction
|
||||
*/
|
||||
export function extractEmailContent(email: any): { text: string; html: string } {
|
||||
// Default empty values
|
||||
let textContent = '';
|
||||
let htmlContent = '';
|
||||
|
||||
// Extract based on common formats
|
||||
if (email) {
|
||||
if (typeof email.content === 'object' && email.content) {
|
||||
// Standard format with content object
|
||||
textContent = email.content.text || '';
|
||||
htmlContent = email.content.html || '';
|
||||
|
||||
// Handle complex email formats where content might be nested
|
||||
if (!textContent && !htmlContent) {
|
||||
// Try to find content in deeper nested structure
|
||||
if (email.content.body) {
|
||||
if (typeof email.content.body === 'string') {
|
||||
// Determine if body is HTML or text
|
||||
if (email.content.body.includes('<') && (
|
||||
email.content.body.includes('<html') ||
|
||||
email.content.body.includes('<body') ||
|
||||
email.content.body.includes('<div')
|
||||
)) {
|
||||
htmlContent = email.content.body;
|
||||
} else {
|
||||
textContent = email.content.body;
|
||||
}
|
||||
} else if (typeof email.content.body === 'object' && email.content.body) {
|
||||
// Some email formats nest content inside body
|
||||
htmlContent = email.content.body.html || '';
|
||||
textContent = email.content.body.text || '';
|
||||
}
|
||||
}
|
||||
|
||||
// Check for data property which some email services use
|
||||
if (!textContent && !htmlContent && email.content.data) {
|
||||
if (typeof email.content.data === 'string') {
|
||||
// Check if data looks like HTML
|
||||
if (email.content.data.includes('<') && (
|
||||
email.content.data.includes('<html') ||
|
||||
email.content.data.includes('<body') ||
|
||||
email.content.data.includes('<div')
|
||||
)) {
|
||||
htmlContent = email.content.data;
|
||||
} else {
|
||||
textContent = email.content.data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Last resort: try to convert the entire content object to string
|
||||
if (!textContent && !htmlContent) {
|
||||
try {
|
||||
// Some email servers encode content as JSON string
|
||||
const contentStr = JSON.stringify(email.content);
|
||||
if (contentStr && contentStr !== '{}') {
|
||||
textContent = `[Complex email content - please view in original format]`;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error extracting content from complex object:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (typeof email.content === 'string') {
|
||||
// Check if content is likely HTML
|
||||
if (email.content.includes('<') && (
|
||||
email.content.includes('<html') ||
|
||||
email.content.includes('<body') ||
|
||||
email.content.includes('<div')
|
||||
)) {
|
||||
htmlContent = email.content;
|
||||
} else {
|
||||
textContent = email.content;
|
||||
}
|
||||
} else {
|
||||
// Check other common properties
|
||||
htmlContent = email.html || '';
|
||||
textContent = email.text || '';
|
||||
|
||||
// If still no content, check for less common properties
|
||||
if (!htmlContent && !textContent) {
|
||||
// Try additional properties that some email clients use
|
||||
htmlContent = email.body?.html || email.bodyHtml || email.htmlBody || '';
|
||||
textContent = email.body?.text || email.bodyText || email.plainText || '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure we always have at least some text content
|
||||
if (!textContent && htmlContent) {
|
||||
try {
|
||||
// Create a helper function to extract text from HTML
|
||||
const tempDiv = document.createElement('div');
|
||||
tempDiv.innerHTML = htmlContent;
|
||||
textContent = tempDiv.textContent || tempDiv.innerText || '';
|
||||
} catch (e) {
|
||||
// Fallback for non-browser environments or if extraction fails
|
||||
textContent = htmlContent.replace(/<[^>]*>/g, ' ')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim() || '[Email content]';
|
||||
}
|
||||
}
|
||||
|
||||
// Add debug logging to help troubleshoot content extraction
|
||||
console.log('Extracted email content:', {
|
||||
hasHtml: !!htmlContent,
|
||||
htmlLength: htmlContent.length,
|
||||
hasText: !!textContent,
|
||||
textLength: textContent.length
|
||||
});
|
||||
|
||||
return { text: textContent, html: htmlContent };
|
||||
}
|
||||
|
||||
/**
|
||||
* Comprehensive utility that processes email content:
|
||||
* - Sanitizes HTML content
|
||||
* - Detects text direction
|
||||
* - Applies direction attributes
|
||||
*
|
||||
* This reduces redundancy by combining these steps into one centralized function
|
||||
*/
|
||||
export function processContentWithDirection(content: string | EmailContent | null | undefined): {
|
||||
html: string;
|
||||
text: string;
|
||||
direction: 'ltr' | 'rtl';
|
||||
} {
|
||||
// Default result with fallbacks
|
||||
const result = {
|
||||
html: '',
|
||||
text: '',
|
||||
direction: 'ltr' as const
|
||||
};
|
||||
|
||||
// Handle null/undefined cases
|
||||
if (!content) return result;
|
||||
|
||||
// Extract text and HTML content based on input type
|
||||
let textContent = '';
|
||||
let htmlContent = '';
|
||||
|
||||
if (typeof content === 'string') {
|
||||
// Simple string content (check if it's HTML or plain text)
|
||||
if (content.includes('<') && (
|
||||
content.includes('<html') ||
|
||||
content.includes('<body') ||
|
||||
content.includes('<div') ||
|
||||
content.includes('<p>')
|
||||
)) {
|
||||
htmlContent = content;
|
||||
} else {
|
||||
textContent = content;
|
||||
}
|
||||
} else {
|
||||
// EmailContent object
|
||||
textContent = content.text || '';
|
||||
htmlContent = content.html || '';
|
||||
}
|
||||
|
||||
// Handle complex email content that might not be properly detected
|
||||
if (!textContent && !htmlContent && typeof content === 'object') {
|
||||
console.log('Processing complex content object:', content);
|
||||
|
||||
// Try to extract content from complex object structure
|
||||
try {
|
||||
// Check for common email content formats
|
||||
// Type assertion to 'any' since we need to handle various email formats
|
||||
const contentAny = content as any;
|
||||
|
||||
if (contentAny.body) {
|
||||
if (typeof contentAny.body === 'string') {
|
||||
// Detect if body is HTML or text
|
||||
if (contentAny.body.includes('<') && (
|
||||
contentAny.body.includes('<html') ||
|
||||
contentAny.body.includes('<body') ||
|
||||
contentAny.body.includes('<div')
|
||||
)) {
|
||||
htmlContent = contentAny.body;
|
||||
} else {
|
||||
textContent = contentAny.body;
|
||||
}
|
||||
} else if (typeof contentAny.body === 'object' && contentAny.body) {
|
||||
// Extract from nested body object
|
||||
htmlContent = contentAny.body.html || '';
|
||||
textContent = contentAny.body.text || '';
|
||||
}
|
||||
}
|
||||
|
||||
// Try to convert complex content to string for debugging
|
||||
if (!textContent && !htmlContent) {
|
||||
try {
|
||||
const contentStr = JSON.stringify(content);
|
||||
console.log('Complex content structure:', contentStr.slice(0, 300) + '...');
|
||||
textContent = '[Complex email content]';
|
||||
} catch (e) {
|
||||
console.error('Failed to stringify complex content:', e);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error processing complex content:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Always ensure we have text for direction detection
|
||||
if (!textContent && htmlContent) {
|
||||
// Extract text from HTML for direction detection
|
||||
try {
|
||||
// Use DOM API if available
|
||||
if (typeof document !== 'undefined') {
|
||||
const tempDiv = document.createElement('div');
|
||||
tempDiv.innerHTML = htmlContent;
|
||||
textContent = tempDiv.textContent || tempDiv.innerText || '';
|
||||
} else {
|
||||
// Simple regex fallback for non-browser environments
|
||||
textContent = htmlContent.replace(/<[^>]*>/g, ' ')
|
||||
.replace(/ /g, ' ')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error extracting text from HTML:', e);
|
||||
textContent = 'Failed to extract text content';
|
||||
}
|
||||
}
|
||||
|
||||
// Detect direction from text
|
||||
const direction = detectTextDirection(textContent);
|
||||
|
||||
// Sanitize HTML if present
|
||||
if (htmlContent) {
|
||||
try {
|
||||
// Sanitize HTML first using the centralized function
|
||||
htmlContent = sanitizeHtml(htmlContent);
|
||||
|
||||
// Then apply direction
|
||||
htmlContent = applyTextDirection(htmlContent, textContent);
|
||||
} catch (error) {
|
||||
console.error('Error sanitizing HTML content:', error);
|
||||
// Create fallback content if sanitization fails
|
||||
htmlContent = `<div dir="${direction}">${
|
||||
textContent ?
|
||||
textContent.replace(/\n/g, '<br>') :
|
||||
'Could not process HTML content'
|
||||
}</div>`;
|
||||
}
|
||||
} else if (textContent) {
|
||||
// Convert plain text to HTML with proper direction
|
||||
htmlContent = `<div dir="${direction}">${textContent.replace(/\n/g, '<br>')}</div>`;
|
||||
}
|
||||
|
||||
// Add debug logging for troubleshooting
|
||||
console.log('Processed content:', {
|
||||
direction,
|
||||
htmlLength: htmlContent.length,
|
||||
textLength: textContent.length,
|
||||
hasHtml: !!htmlContent,
|
||||
hasText: !!textContent
|
||||
});
|
||||
|
||||
// Return processed content
|
||||
return {
|
||||
text: textContent,
|
||||
html: htmlContent,
|
||||
direction
|
||||
};
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user