courrier preview
This commit is contained in:
parent
86581cea72
commit
6c9f2d86a6
@ -16,7 +16,7 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import RichTextEditor from '@/components/ui/rich-text-editor';
|
import RichTextEditor from '@/components/ui/rich-text-editor';
|
||||||
import { detectTextDirection } from '@/lib/utils/text-direction';
|
import { processContentWithDirection } from '@/lib/utils/text-direction';
|
||||||
|
|
||||||
// Import from the centralized utils
|
// Import from the centralized utils
|
||||||
import {
|
import {
|
||||||
@ -351,7 +351,7 @@ export default function ComposeEmail(props: ComposeEmailProps) {
|
|||||||
<RichTextEditor
|
<RichTextEditor
|
||||||
ref={editorRef}
|
ref={editorRef}
|
||||||
initialContent={emailContent}
|
initialContent={emailContent}
|
||||||
initialDirection={detectTextDirection(emailContent)}
|
initialDirection={processContentWithDirection(emailContent).direction}
|
||||||
onChange={(html) => {
|
onChange={(html) => {
|
||||||
// Store the content
|
// Store the content
|
||||||
setEmailContent(html);
|
setEmailContent(html);
|
||||||
|
|||||||
@ -2,8 +2,7 @@
|
|||||||
|
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { EmailContent } from '@/types/email';
|
import { EmailContent } from '@/types/email';
|
||||||
import { detectTextDirection, applyTextDirection } from '@/lib/utils/text-direction';
|
import { processContentWithDirection } from '@/lib/utils/text-direction';
|
||||||
import { sanitizeHtml, getDOMPurify } from '@/lib/utils/dom-purify-config';
|
|
||||||
|
|
||||||
interface EmailContentDisplayProps {
|
interface EmailContentDisplayProps {
|
||||||
content: EmailContent | null | undefined;
|
content: EmailContent | null | undefined;
|
||||||
@ -24,99 +23,58 @@ const EmailContentDisplay: React.FC<EmailContentDisplayProps> = ({
|
|||||||
type = 'auto',
|
type = 'auto',
|
||||||
debug = false
|
debug = false
|
||||||
}) => {
|
}) => {
|
||||||
// Create a safe content object with fallback values for missing properties
|
// Process content with centralized utility
|
||||||
const safeContent = useMemo(() => {
|
const processedContent = useMemo(() => {
|
||||||
|
// Default empty content
|
||||||
if (!content) {
|
if (!content) {
|
||||||
return {
|
return {
|
||||||
text: '',
|
text: '',
|
||||||
html: undefined,
|
html: '<div class="text-gray-400">No content available</div>',
|
||||||
isHtml: false,
|
|
||||||
direction: 'ltr' as const
|
direction: 'ltr' as const
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
|
||||||
text: content.text || '',
|
|
||||||
html: content.html,
|
|
||||||
isHtml: content.isHtml,
|
|
||||||
// If direction is missing, detect it from the text content
|
|
||||||
direction: content.direction || detectTextDirection(content.text)
|
|
||||||
};
|
|
||||||
}, [content]);
|
|
||||||
|
|
||||||
// Determine what content to display based on type preference and available content
|
// For text-only display, convert plain text to HTML first
|
||||||
const htmlToDisplay = useMemo(() => {
|
if (type === 'text') {
|
||||||
// If no content is available, show a placeholder
|
const textContent = content.text || '';
|
||||||
if (!safeContent.text && !safeContent.html) {
|
const formattedText = textContent
|
||||||
return '<div class="text-gray-400">No content available</div>';
|
|
||||||
}
|
|
||||||
|
|
||||||
// If type is explicitly set to text, or we don't have HTML and auto mode
|
|
||||||
if (type === 'text' || (type === 'auto' && !safeContent.isHtml)) {
|
|
||||||
// Format plain text with line breaks for HTML display
|
|
||||||
if (safeContent.text) {
|
|
||||||
const formattedText = safeContent.text
|
|
||||||
.replace(/&/g, '&')
|
.replace(/&/g, '&')
|
||||||
.replace(/</g, '<')
|
.replace(/</g, '<')
|
||||||
.replace(/>/g, '>')
|
.replace(/>/g, '>')
|
||||||
.replace(/\n/g, '<br>');
|
.replace(/\n/g, '<br>');
|
||||||
|
|
||||||
return formattedText;
|
return processContentWithDirection(formattedText);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise use HTML content if available
|
// For auto mode, let the centralized function handle the content
|
||||||
if (safeContent.isHtml && safeContent.html) {
|
return processContentWithDirection(content);
|
||||||
return safeContent.html;
|
}, [content, type]);
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback to text content if there's no HTML
|
|
||||||
if (safeContent.text) {
|
|
||||||
const formattedText = safeContent.text
|
|
||||||
.replace(/&/g, '&')
|
|
||||||
.replace(/</g, '<')
|
|
||||||
.replace(/>/g, '>')
|
|
||||||
.replace(/\n/g, '<br>');
|
|
||||||
|
|
||||||
return formattedText;
|
|
||||||
}
|
|
||||||
|
|
||||||
return '<div class="text-gray-400">No content available</div>';
|
|
||||||
}, [safeContent, type]);
|
|
||||||
|
|
||||||
// Handle quoted text display
|
// Handle quoted text display
|
||||||
const processedHTML = useMemo(() => {
|
const displayHTML = useMemo(() => {
|
||||||
if (!showQuotedText) {
|
if (!showQuotedText) {
|
||||||
// This is simplified - a more robust approach would parse and handle
|
// This is simplified - a more robust approach would parse and handle
|
||||||
// quoted sections more intelligently
|
// quoted sections more intelligently
|
||||||
return htmlToDisplay.replace(/<blockquote[^>]*>[\s\S]*?<\/blockquote>/gi,
|
return processedContent.html.replace(/<blockquote[^>]*>[\s\S]*?<\/blockquote>/gi,
|
||||||
'<div class="text-gray-400">[Quoted text hidden]</div>');
|
'<div class="text-gray-400">[Quoted text hidden]</div>');
|
||||||
}
|
}
|
||||||
return htmlToDisplay;
|
return processedContent.html;
|
||||||
}, [htmlToDisplay, showQuotedText]);
|
}, [processedContent.html, showQuotedText]);
|
||||||
|
|
||||||
// Sanitize HTML content and apply proper direction
|
|
||||||
const sanitizedHTML = useMemo(() => {
|
|
||||||
// First sanitize the HTML with our centralized utility
|
|
||||||
const cleanHtml = sanitizeHtml(processedHTML);
|
|
||||||
|
|
||||||
// Then apply text direction consistently
|
|
||||||
return applyTextDirection(cleanHtml, safeContent.text);
|
|
||||||
}, [processedHTML, safeContent.text]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`email-content-display ${className}`}>
|
<div className={`email-content-display ${className}`}>
|
||||||
<div
|
<div
|
||||||
className="email-content-inner"
|
className="email-content-inner"
|
||||||
dangerouslySetInnerHTML={{ __html: sanitizedHTML }}
|
dangerouslySetInnerHTML={{ __html: displayHTML }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Debug output if enabled */}
|
{/* Debug output if enabled */}
|
||||||
{debug && (
|
{debug && (
|
||||||
<div className="mt-4 p-2 text-xs bg-gray-100 border rounded">
|
<div className="mt-4 p-2 text-xs bg-gray-100 border rounded">
|
||||||
<p><strong>Content Type:</strong> {safeContent.isHtml ? 'HTML' : 'Text'}</p>
|
<p><strong>Content Type:</strong> {content?.isHtml ? 'HTML' : 'Text'}</p>
|
||||||
<p><strong>Direction:</strong> {safeContent.direction}</p>
|
<p><strong>Direction:</strong> {processedContent.direction}</p>
|
||||||
<p><strong>HTML Length:</strong> {safeContent.html?.length || 0}</p>
|
<p><strong>HTML Length:</strong> {content?.html?.length || 0}</p>
|
||||||
<p><strong>Text Length:</strong> {safeContent.text?.length || 0}</p>
|
<p><strong>Text Length:</strong> {content?.text?.length || 0}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useState, useEffect, useRef, forwardRef, useImperativeHandle } from 'react';
|
import React, { useState, useEffect, useRef, forwardRef, useImperativeHandle } from 'react';
|
||||||
import { detectTextDirection } from '@/lib/utils/text-direction';
|
import { processContentWithDirection } from '@/lib/utils/text-direction';
|
||||||
import { getDOMPurify } from '@/lib/utils/dom-purify-config';
|
import { getDOMPurify } from '@/lib/utils/dom-purify-config';
|
||||||
|
|
||||||
interface RichTextEditorProps {
|
interface RichTextEditorProps {
|
||||||
@ -44,8 +44,13 @@ const RichTextEditor = forwardRef<HTMLDivElement, RichTextEditorProps>(({
|
|||||||
initialDirection
|
initialDirection
|
||||||
}, ref) => {
|
}, ref) => {
|
||||||
const internalEditorRef = useRef<HTMLDivElement>(null);
|
const internalEditorRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
// Process initial content to get its direction
|
||||||
|
const processedInitialContent = processContentWithDirection(initialContent);
|
||||||
|
|
||||||
|
// Set initial direction either from prop or detected from content
|
||||||
const [direction, setDirection] = useState<'ltr' | 'rtl'>(
|
const [direction, setDirection] = useState<'ltr' | 'rtl'>(
|
||||||
initialDirection || detectTextDirection(initialContent)
|
initialDirection || processedInitialContent.direction
|
||||||
);
|
);
|
||||||
|
|
||||||
// Forward the ref to parent components
|
// Forward the ref to parent components
|
||||||
@ -54,12 +59,10 @@ const RichTextEditor = forwardRef<HTMLDivElement, RichTextEditorProps>(({
|
|||||||
// Initialize editor with clean content
|
// Initialize editor with clean content
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (internalEditorRef.current) {
|
if (internalEditorRef.current) {
|
||||||
// Clean the initial content using our centralized config
|
// Using centralized processing for initial content
|
||||||
const cleanContent = DOMPurify.sanitize(initialContent, {
|
const { html: cleanContent } = processContentWithDirection(initialContent);
|
||||||
ADD_ATTR: ['dir'] // Ensure dir attributes are preserved
|
|
||||||
});
|
|
||||||
|
|
||||||
internalEditorRef.current.innerHTML = cleanContent;
|
internalEditorRef.current.innerHTML = cleanContent || '';
|
||||||
|
|
||||||
// Set initial direction
|
// Set initial direction
|
||||||
internalEditorRef.current.setAttribute('dir', direction);
|
internalEditorRef.current.setAttribute('dir', direction);
|
||||||
@ -75,15 +78,20 @@ const RichTextEditor = forwardRef<HTMLDivElement, RichTextEditorProps>(({
|
|||||||
|
|
||||||
// Handle content changes and detect direction changes
|
// Handle content changes and detect direction changes
|
||||||
const handleInput = (e: React.FormEvent<HTMLDivElement>) => {
|
const handleInput = (e: React.FormEvent<HTMLDivElement>) => {
|
||||||
if (onChange && e.currentTarget.innerHTML !== initialContent) {
|
const newContent = e.currentTarget.innerHTML;
|
||||||
onChange(e.currentTarget.innerHTML);
|
|
||||||
|
// Notify parent of changes
|
||||||
|
if (onChange && newContent !== initialContent) {
|
||||||
|
onChange(newContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-detect direction on significant content changes
|
// Only perform direction detection periodically to avoid constant recalculation
|
||||||
// Only do this when the content length has changed significantly
|
// Get the text content for direction detection
|
||||||
const newContent = e.currentTarget.innerText;
|
const newTextContent = e.currentTarget.innerText;
|
||||||
if (newContent.length > 5 && newContent.length % 10 === 0) {
|
if (newTextContent.length > 5 && newTextContent.length % 10 === 0) {
|
||||||
const newDirection = detectTextDirection(newContent);
|
// Process the content to determine direction
|
||||||
|
const { direction: newDirection } = processContentWithDirection(newTextContent);
|
||||||
|
|
||||||
if (newDirection !== direction) {
|
if (newDirection !== direction) {
|
||||||
setDirection(newDirection);
|
setDirection(newDirection);
|
||||||
e.currentTarget.setAttribute('dir', newDirection);
|
e.currentTarget.setAttribute('dir', newDirection);
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||||
import { useToast } from './use-toast';
|
import { useToast } from './use-toast';
|
||||||
import { EmailMessage, EmailContent } from '@/types/email';
|
import { EmailMessage, EmailContent } from '@/types/email';
|
||||||
import { detectTextDirection } from '@/lib/utils/text-direction';
|
import { processContentWithDirection } from '@/lib/utils/text-direction';
|
||||||
import { sanitizeHtml } from '@/lib/utils/email-utils';
|
|
||||||
|
|
||||||
interface EmailFetchState {
|
interface EmailFetchState {
|
||||||
email: EmailMessage | null;
|
email: EmailMessage | null;
|
||||||
@ -76,46 +75,40 @@ export function useEmailFetch({ onEmailLoaded, onError }: UseEmailFetchProps = {
|
|||||||
|
|
||||||
// Create a valid email message object with required fields
|
// Create a valid email message object with required fields
|
||||||
const processContent = (data: any): EmailContent => {
|
const processContent = (data: any): EmailContent => {
|
||||||
// Determine the text content - using all possible paths
|
// Extract initial content from all possible sources
|
||||||
let textContent = '';
|
let initialContent: any = {};
|
||||||
|
|
||||||
|
if (typeof data.content === 'object' && data.content) {
|
||||||
|
// Use content object directly if available
|
||||||
|
initialContent = data.content;
|
||||||
|
} else {
|
||||||
|
// Build content object from separate properties
|
||||||
if (typeof data.content === 'string') {
|
if (typeof data.content === 'string') {
|
||||||
textContent = data.content;
|
// Check if content appears to be HTML
|
||||||
} else if (data.content?.text) {
|
if (data.content.includes('<') &&
|
||||||
textContent = data.content.text;
|
(data.content.includes('<html') ||
|
||||||
} else if (data.text) {
|
data.content.includes('<body') ||
|
||||||
textContent = data.text;
|
data.content.includes('<div'))) {
|
||||||
} else if (data.plainText) {
|
initialContent.html = data.content;
|
||||||
textContent = data.plainText;
|
} else {
|
||||||
|
initialContent.text = data.content;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Check for separate html and text properties
|
||||||
|
if (data.html) initialContent.html = data.html;
|
||||||
|
if (data.text) initialContent.text = data.text;
|
||||||
|
else if (data.plainText) initialContent.text = data.plainText;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the HTML content - using all possible paths
|
// Use the centralized content processing function
|
||||||
let htmlContent = undefined;
|
const processedContent = processContentWithDirection(initialContent);
|
||||||
if (data.content?.html) {
|
|
||||||
htmlContent = data.content.html;
|
|
||||||
} else if (data.html) {
|
|
||||||
htmlContent = data.html;
|
|
||||||
} else if (typeof data.content === 'string' && data.content.includes('<')) {
|
|
||||||
// If the content string appears to be HTML
|
|
||||||
htmlContent = data.content;
|
|
||||||
// We should still keep the text version, will be extracted if needed
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean HTML content if present - use centralized sanitization
|
|
||||||
if (htmlContent) {
|
|
||||||
htmlContent = sanitizeHtml(htmlContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine if content is HTML
|
|
||||||
const isHtml = !!htmlContent;
|
|
||||||
|
|
||||||
// Detect text direction - use centralized direction detection
|
|
||||||
const direction = data.content?.direction || detectTextDirection(textContent);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
text: textContent,
|
text: processedContent.text,
|
||||||
html: htmlContent,
|
html: processedContent.html,
|
||||||
isHtml,
|
isHtml: !!processedContent.html,
|
||||||
direction
|
direction: processedContent.direction
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { EmailMessage, EmailContent, EmailAddress, LegacyEmailMessage } from '@/types/email';
|
import { EmailMessage, EmailContent, EmailAddress, LegacyEmailMessage } from '@/types/email';
|
||||||
import { detectTextDirection } from './text-direction';
|
import { processContentWithDirection } from './text-direction';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adapts a legacy email format to the standardized EmailMessage format
|
* Adapts a legacy email format to the standardized EmailMessage format
|
||||||
@ -136,81 +136,40 @@ function formatAddressesToString(addresses: EmailAddress[]): string {
|
|||||||
* Normalizes content from various formats into the standard EmailContent format
|
* Normalizes content from various formats into the standard EmailContent format
|
||||||
*/
|
*/
|
||||||
function normalizeContent(email: LegacyEmailMessage): EmailContent {
|
function normalizeContent(email: LegacyEmailMessage): EmailContent {
|
||||||
// Default content structure
|
|
||||||
const normalizedContent: EmailContent = {
|
|
||||||
html: undefined,
|
|
||||||
text: '',
|
|
||||||
isHtml: false,
|
|
||||||
direction: 'ltr'
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Extract content based on standardized property hierarchy
|
// Extract content based on possible formats to pass to the centralized processor
|
||||||
let htmlContent = '';
|
let initialContent: any = {};
|
||||||
let textContent = '';
|
|
||||||
let isHtml = false;
|
|
||||||
|
|
||||||
// Step 1: Extract content from the various possible formats
|
|
||||||
if (email.content && typeof email.content === 'object') {
|
if (email.content && typeof email.content === 'object') {
|
||||||
isHtml = !!email.content.html;
|
initialContent = email.content;
|
||||||
htmlContent = email.content.html || '';
|
|
||||||
textContent = email.content.text || '';
|
|
||||||
} else if (typeof email.content === 'string') {
|
} else if (typeof email.content === 'string') {
|
||||||
// Check if the string content is HTML
|
// Check if the string content is HTML
|
||||||
isHtml = email.content.trim().startsWith('<') &&
|
if (email.content.trim().startsWith('<') &&
|
||||||
(email.content.includes('<html') ||
|
(email.content.includes('<html') ||
|
||||||
email.content.includes('<body') ||
|
email.content.includes('<body') ||
|
||||||
email.content.includes('<div') ||
|
email.content.includes('<div') ||
|
||||||
email.content.includes('<p>'));
|
email.content.includes('<p>'))) {
|
||||||
htmlContent = isHtml ? email.content : '';
|
initialContent.html = email.content;
|
||||||
textContent = isHtml ? '' : email.content;
|
|
||||||
} else if (email.html) {
|
|
||||||
isHtml = true;
|
|
||||||
htmlContent = email.html;
|
|
||||||
textContent = email.text || email.plainText || '';
|
|
||||||
} else if (email.text || email.plainText) {
|
|
||||||
isHtml = false;
|
|
||||||
htmlContent = '';
|
|
||||||
textContent = email.text || email.plainText || '';
|
|
||||||
} else if (email.formattedContent) {
|
|
||||||
// Assume formattedContent is already HTML
|
|
||||||
isHtml = true;
|
|
||||||
htmlContent = email.formattedContent;
|
|
||||||
textContent = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 2: Set the normalized content properties
|
|
||||||
normalizedContent.isHtml = isHtml;
|
|
||||||
|
|
||||||
// Always ensure we have text content
|
|
||||||
if (textContent) {
|
|
||||||
normalizedContent.text = textContent;
|
|
||||||
} else if (htmlContent) {
|
|
||||||
// Extract text from HTML if we don't have plain text
|
|
||||||
if (typeof document !== 'undefined') {
|
|
||||||
// Browser environment
|
|
||||||
const tempDiv = document.createElement('div');
|
|
||||||
tempDiv.innerHTML = htmlContent;
|
|
||||||
normalizedContent.text = tempDiv.textContent || tempDiv.innerText || '';
|
|
||||||
} else {
|
} else {
|
||||||
// Server environment - do simple strip
|
initialContent.text = email.content;
|
||||||
normalizedContent.text = htmlContent
|
|
||||||
.replace(/<[^>]*>/g, '')
|
|
||||||
.replace(/ /g, ' ')
|
|
||||||
.replace(/\s+/g, ' ')
|
|
||||||
.trim();
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Extract from separate properties
|
||||||
|
if (email.html) initialContent.html = email.html;
|
||||||
|
if (email.text) initialContent.text = email.text;
|
||||||
|
else if (email.plainText) initialContent.text = email.plainText;
|
||||||
|
else if (email.formattedContent) initialContent.html = email.formattedContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have HTML content, store it without sanitizing (sanitization will be done at display time)
|
// Use the centralized content processor
|
||||||
if (isHtml && htmlContent) {
|
const processedContent = processContentWithDirection(initialContent);
|
||||||
normalizedContent.html = htmlContent;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine text direction
|
return {
|
||||||
normalizedContent.direction = detectTextDirection(normalizedContent.text);
|
html: processedContent.html,
|
||||||
|
text: processedContent.text,
|
||||||
return normalizedContent;
|
isHtml: !!processedContent.html,
|
||||||
|
direction: processedContent.direction
|
||||||
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error normalizing email content:', error);
|
console.error('Error normalizing email content:', error);
|
||||||
|
|
||||||
|
|||||||
@ -4,11 +4,11 @@
|
|||||||
* This file contains all email-related utility functions:
|
* This file contains all email-related utility functions:
|
||||||
* - Content normalization
|
* - Content normalization
|
||||||
* - Email formatting (replies, forwards)
|
* - Email formatting (replies, forwards)
|
||||||
* - Text direction detection
|
* - Text direction handling
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Import from centralized DOMPurify configuration instead of configuring directly
|
// Import from centralized configuration
|
||||||
import { sanitizeHtml, getDOMPurify } from './dom-purify-config';
|
import { sanitizeHtml } from './dom-purify-config';
|
||||||
import {
|
import {
|
||||||
EmailMessage,
|
EmailMessage,
|
||||||
EmailContent,
|
EmailContent,
|
||||||
@ -17,7 +17,13 @@ import {
|
|||||||
} from '@/types/email';
|
} from '@/types/email';
|
||||||
import { adaptLegacyEmail } from '@/lib/utils/email-adapters';
|
import { adaptLegacyEmail } from '@/lib/utils/email-adapters';
|
||||||
import { decodeInfomaniakEmail, adaptMimeEmail, isMimeFormat } from './email-mime-decoder';
|
import { decodeInfomaniakEmail, adaptMimeEmail, isMimeFormat } from './email-mime-decoder';
|
||||||
import { detectTextDirection, applyTextDirection } from '@/lib/utils/text-direction';
|
import {
|
||||||
|
detectTextDirection,
|
||||||
|
applyTextDirection,
|
||||||
|
extractEmailContent,
|
||||||
|
processContentWithDirection
|
||||||
|
} from '@/lib/utils/text-direction';
|
||||||
|
import { format } from 'date-fns';
|
||||||
|
|
||||||
// Export the sanitizeHtml function from the centralized config
|
// Export the sanitizeHtml function from the centralized config
|
||||||
export { sanitizeHtml };
|
export { sanitizeHtml };
|
||||||
@ -32,25 +38,6 @@ export interface FormattedEmail {
|
|||||||
content: EmailContent;
|
content: EmailContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility type that combines EmailMessage and LegacyEmailMessage
|
|
||||||
* to allow access to properties that might exist in either type
|
|
||||||
*/
|
|
||||||
type AnyEmailMessage = {
|
|
||||||
id: string;
|
|
||||||
subject: string | undefined;
|
|
||||||
from: any;
|
|
||||||
to: any;
|
|
||||||
cc?: any;
|
|
||||||
date: any;
|
|
||||||
content?: any;
|
|
||||||
html?: string;
|
|
||||||
text?: string;
|
|
||||||
attachments?: any[];
|
|
||||||
flags?: any;
|
|
||||||
[key: string]: any;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format email addresses for display
|
* Format email addresses for display
|
||||||
* Can handle both array of EmailAddress objects or a string
|
* Can handle both array of EmailAddress objects or a string
|
||||||
@ -130,7 +117,12 @@ export function normalizeEmailContent(email: any): EmailMessage {
|
|||||||
if (email.content && isMimeFormat(email.content)) {
|
if (email.content && isMimeFormat(email.content)) {
|
||||||
try {
|
try {
|
||||||
console.log('Detected MIME format email, decoding...');
|
console.log('Detected MIME format email, decoding...');
|
||||||
return adaptMimeEmail(email);
|
// We need to force cast here due to type incompatibility between EmailMessage and the mime result
|
||||||
|
const adaptedEmail = adaptMimeEmail(email);
|
||||||
|
return {
|
||||||
|
...adaptedEmail,
|
||||||
|
flags: adaptedEmail.flags || [] // Ensure flags is always an array
|
||||||
|
} as EmailMessage;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error decoding MIME email:', error);
|
console.error('Error decoding MIME email:', error);
|
||||||
// Continue with regular normalization if MIME decoding fails
|
// Continue with regular normalization if MIME decoding fails
|
||||||
@ -144,8 +136,13 @@ export function normalizeEmailContent(email: any): EmailMessage {
|
|||||||
return email as EmailMessage;
|
return email as EmailMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, adapt from legacy format and cast to EmailMessage
|
// Otherwise, adapt from legacy format
|
||||||
return adaptLegacyEmail(email as LegacyEmailMessage) as unknown as EmailMessage;
|
// We need to force cast here due to type incompatibility
|
||||||
|
const adaptedEmail = adaptLegacyEmail(email);
|
||||||
|
return {
|
||||||
|
...adaptedEmail,
|
||||||
|
flags: adaptedEmail.flags || [] // Ensure flags is always an array
|
||||||
|
} as EmailMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -156,47 +153,17 @@ export function renderEmailContent(content: EmailContent | null): string {
|
|||||||
return '<div class="email-content-empty">No content available</div>';
|
return '<div class="email-content-empty">No content available</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
const safeContent = {
|
// Use the centralized content processing function
|
||||||
text: content.text || '',
|
const processed = processContentWithDirection(content);
|
||||||
html: content.html || '',
|
|
||||||
isHtml: !!content.isHtml,
|
|
||||||
direction: content.direction || 'ltr'
|
|
||||||
};
|
|
||||||
|
|
||||||
// If we have HTML content and isHtml flag is true, use it
|
// Return the processed HTML with proper direction
|
||||||
if (safeContent.isHtml && safeContent.html) {
|
return processed.html || '<div class="email-content-empty">No content available</div>';
|
||||||
return sanitizeHtml(safeContent.html);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, convert text to HTML with proper line breaks
|
|
||||||
if (safeContent.text) {
|
|
||||||
return `<div dir="${safeContent.direction}">${formatPlainTextToHtml(safeContent.text)}</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return '<div class="email-content-empty">No content available</div>';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format email for reply
|
* Get recipient addresses from an email for reply or forward
|
||||||
*/
|
*/
|
||||||
export function formatReplyEmail(originalEmail: EmailMessage | LegacyEmailMessage | null, type: 'reply' | 'reply-all' = 'reply'): FormattedEmail {
|
function getRecipientAddresses(email: any, type: 'reply' | 'reply-all'): { to: string; cc: string } {
|
||||||
if (!originalEmail) {
|
|
||||||
return {
|
|
||||||
to: '',
|
|
||||||
cc: '',
|
|
||||||
subject: '',
|
|
||||||
content: {
|
|
||||||
text: '',
|
|
||||||
html: '',
|
|
||||||
isHtml: false,
|
|
||||||
direction: 'ltr' as const
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cast to AnyEmailMessage for property access
|
|
||||||
const email = originalEmail as AnyEmailMessage;
|
|
||||||
|
|
||||||
// Format the recipients
|
// Format the recipients
|
||||||
const to = Array.isArray(email.from)
|
const to = Array.isArray(email.from)
|
||||||
? email.from.map((addr: any) => {
|
? email.from.map((addr: any) => {
|
||||||
@ -231,15 +198,28 @@ export function formatReplyEmail(originalEmail: EmailMessage | LegacyEmailMessag
|
|||||||
cc = [...toRecipients, ...ccRecipients].join(', ');
|
cc = [...toRecipients, ...ccRecipients].join(', ');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return { to, cc };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get formatted header information for reply or forward
|
||||||
|
*/
|
||||||
|
function getFormattedHeaderInfo(email: any): {
|
||||||
|
fromStr: string;
|
||||||
|
toStr: string;
|
||||||
|
ccStr: string;
|
||||||
|
dateStr: string;
|
||||||
|
subject: string;
|
||||||
|
} {
|
||||||
// Format the subject
|
// Format the subject
|
||||||
const subject = email.subject && !email.subject.startsWith('Re:')
|
const subject = email.subject && !email.subject.startsWith('Re:') && !email.subject.startsWith('Fwd:')
|
||||||
? `Re: ${email.subject}`
|
? email.subject
|
||||||
: email.subject || '';
|
: email.subject || '';
|
||||||
|
|
||||||
// Format the content
|
// Format the date
|
||||||
const originalDate = email.date ? new Date(email.date) : new Date();
|
const dateStr = email.date ? new Date(email.date).toLocaleString() : 'Unknown Date';
|
||||||
const dateStr = originalDate.toLocaleString();
|
|
||||||
|
|
||||||
|
// Format sender
|
||||||
const fromStr = Array.isArray(email.from)
|
const fromStr = Array.isArray(email.from)
|
||||||
? email.from.map((addr: any) => {
|
? email.from.map((addr: any) => {
|
||||||
if (typeof addr === 'string') return addr;
|
if (typeof addr === 'string') return addr;
|
||||||
@ -249,6 +229,7 @@ export function formatReplyEmail(originalEmail: EmailMessage | LegacyEmailMessag
|
|||||||
? email.from
|
? email.from
|
||||||
: 'Unknown Sender';
|
: 'Unknown Sender';
|
||||||
|
|
||||||
|
// Format recipients
|
||||||
const toStr = Array.isArray(email.to)
|
const toStr = Array.isArray(email.to)
|
||||||
? email.to.map((addr: any) => {
|
? email.to.map((addr: any) => {
|
||||||
if (typeof addr === 'string') return addr;
|
if (typeof addr === 'string') return addr;
|
||||||
@ -258,23 +239,45 @@ export function formatReplyEmail(originalEmail: EmailMessage | LegacyEmailMessag
|
|||||||
? email.to
|
? email.to
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
// Extract original content
|
// Format CC
|
||||||
const originalTextContent =
|
const ccStr = Array.isArray(email.cc)
|
||||||
typeof email.content === 'object' && email.content?.text ? email.content.text :
|
? email.cc.map((addr: any) => {
|
||||||
typeof email.content === 'string' ? email.content :
|
if (typeof addr === 'string') return addr;
|
||||||
email.text || '';
|
return addr.name ? `${addr.name} <${addr.address}>` : addr.address;
|
||||||
|
}).join(', ')
|
||||||
|
: typeof email.cc === 'string'
|
||||||
|
? email.cc
|
||||||
|
: '';
|
||||||
|
|
||||||
const originalHtmlContent =
|
return { fromStr, toStr, ccStr, dateStr, subject };
|
||||||
typeof email.content === 'object' && email.content?.html ? email.content.html :
|
}
|
||||||
email.html ||
|
|
||||||
(typeof email.content === 'string' && email.content.includes('<')
|
|
||||||
? email.content
|
|
||||||
: '');
|
|
||||||
|
|
||||||
// Get the direction from the original email
|
/**
|
||||||
const originalDirection =
|
* Format email for reply
|
||||||
typeof email.content === 'object' && email.content?.direction ? email.content.direction :
|
*/
|
||||||
detectTextDirection(originalTextContent);
|
export function formatReplyEmail(originalEmail: EmailMessage | LegacyEmailMessage | null, type: 'reply' | 'reply-all' = 'reply'): FormattedEmail {
|
||||||
|
if (!originalEmail) {
|
||||||
|
return {
|
||||||
|
to: '',
|
||||||
|
cc: '',
|
||||||
|
subject: '',
|
||||||
|
content: {
|
||||||
|
text: '',
|
||||||
|
html: '',
|
||||||
|
isHtml: false,
|
||||||
|
direction: 'ltr' as const
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract recipient addresses
|
||||||
|
const { to, cc } = getRecipientAddresses(originalEmail, type);
|
||||||
|
|
||||||
|
// Get header information
|
||||||
|
const { fromStr, dateStr, subject } = getFormattedHeaderInfo(originalEmail);
|
||||||
|
|
||||||
|
// Extract content using centralized utility
|
||||||
|
const { text: originalTextContent, html: originalHtmlContent } = extractEmailContent(originalEmail);
|
||||||
|
|
||||||
// Create content with appropriate quote formatting
|
// Create content with appropriate quote formatting
|
||||||
const replyBody = `
|
const replyBody = `
|
||||||
@ -286,12 +289,11 @@ export function formatReplyEmail(originalEmail: EmailMessage | LegacyEmailMessag
|
|||||||
</blockquote>
|
</blockquote>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Apply consistent text direction
|
// Process the content with proper direction
|
||||||
const htmlContent = applyTextDirection(replyBody);
|
const processed = processContentWithDirection(replyBody);
|
||||||
|
|
||||||
// Create plain text content
|
// Create plain text content
|
||||||
const textContent = `
|
const textContent = `
|
||||||
|
|
||||||
On ${dateStr}, ${fromStr} wrote:
|
On ${dateStr}, ${fromStr} wrote:
|
||||||
> ${originalTextContent.split('\n').join('\n> ')}
|
> ${originalTextContent.split('\n').join('\n> ')}
|
||||||
`;
|
`;
|
||||||
@ -299,12 +301,12 @@ On ${dateStr}, ${fromStr} wrote:
|
|||||||
return {
|
return {
|
||||||
to,
|
to,
|
||||||
cc,
|
cc,
|
||||||
subject,
|
subject: subject.startsWith('Re:') ? subject : `Re: ${subject}`,
|
||||||
content: {
|
content: {
|
||||||
text: textContent,
|
text: textContent,
|
||||||
html: htmlContent,
|
html: processed.html,
|
||||||
isHtml: true,
|
isHtml: true,
|
||||||
direction: 'ltr' as const // Reply is LTR, but original content keeps its direction in the blockquote
|
direction: processed.direction
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -326,61 +328,11 @@ export function formatForwardedEmail(originalEmail: EmailMessage | LegacyEmailMe
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cast to AnyEmailMessage for property access
|
// Get header information
|
||||||
const email = originalEmail as AnyEmailMessage;
|
const { fromStr, toStr, ccStr, dateStr, subject } = getFormattedHeaderInfo(originalEmail);
|
||||||
|
|
||||||
// Format the subject
|
// Extract content using centralized utility
|
||||||
const subject = email.subject && !email.subject.startsWith('Fwd:')
|
const { text: originalTextContent, html: originalHtmlContent } = extractEmailContent(originalEmail);
|
||||||
? `Fwd: ${email.subject}`
|
|
||||||
: email.subject || '';
|
|
||||||
|
|
||||||
// Format from, to, cc for the header
|
|
||||||
const fromStr = Array.isArray(email.from)
|
|
||||||
? email.from.map((addr: any) => {
|
|
||||||
if (typeof addr === 'string') return addr;
|
|
||||||
return addr.name ? `${addr.name} <${addr.address}>` : addr.address;
|
|
||||||
}).join(', ')
|
|
||||||
: typeof email.from === 'string'
|
|
||||||
? email.from
|
|
||||||
: 'Unknown Sender';
|
|
||||||
|
|
||||||
const toStr = Array.isArray(email.to)
|
|
||||||
? email.to.map((addr: any) => {
|
|
||||||
if (typeof addr === 'string') return addr;
|
|
||||||
return addr.name ? `${addr.name} <${addr.address}>` : addr.address;
|
|
||||||
}).join(', ')
|
|
||||||
: typeof email.to === 'string'
|
|
||||||
? email.to
|
|
||||||
: '';
|
|
||||||
|
|
||||||
const ccStr = Array.isArray(email.cc)
|
|
||||||
? email.cc.map((addr: any) => {
|
|
||||||
if (typeof addr === 'string') return addr;
|
|
||||||
return addr.name ? `${addr.name} <${addr.address}>` : addr.address;
|
|
||||||
}).join(', ')
|
|
||||||
: typeof email.cc === 'string'
|
|
||||||
? email.cc
|
|
||||||
: '';
|
|
||||||
|
|
||||||
const dateStr = email.date ? new Date(email.date).toLocaleString() : 'Unknown Date';
|
|
||||||
|
|
||||||
// Extract original content
|
|
||||||
const originalTextContent =
|
|
||||||
typeof email.content === 'object' && email.content?.text ? email.content.text :
|
|
||||||
typeof email.content === 'string' ? email.content :
|
|
||||||
email.text || '';
|
|
||||||
|
|
||||||
const originalHtmlContent =
|
|
||||||
typeof email.content === 'object' && email.content?.html ? email.content.html :
|
|
||||||
email.html ||
|
|
||||||
(typeof email.content === 'string' && email.content.includes('<')
|
|
||||||
? email.content
|
|
||||||
: '');
|
|
||||||
|
|
||||||
// Get the direction from the original email
|
|
||||||
const originalDirection =
|
|
||||||
typeof email.content === 'object' && email.content?.direction ? email.content.direction :
|
|
||||||
detectTextDirection(originalTextContent);
|
|
||||||
|
|
||||||
// Create forwarded content with header information
|
// Create forwarded content with header information
|
||||||
const forwardBody = `
|
const forwardBody = `
|
||||||
@ -390,7 +342,7 @@ export function formatForwardedEmail(originalEmail: EmailMessage | LegacyEmailMe
|
|||||||
<p>---------- Forwarded message ---------</p>
|
<p>---------- Forwarded message ---------</p>
|
||||||
<p><strong>From:</strong> ${fromStr}</p>
|
<p><strong>From:</strong> ${fromStr}</p>
|
||||||
<p><strong>Date:</strong> ${dateStr}</p>
|
<p><strong>Date:</strong> ${dateStr}</p>
|
||||||
<p><strong>Subject:</strong> ${email.subject || ''}</p>
|
<p><strong>Subject:</strong> ${subject || ''}</p>
|
||||||
<p><strong>To:</strong> ${toStr}</p>
|
<p><strong>To:</strong> ${toStr}</p>
|
||||||
${ccStr ? `<p><strong>Cc:</strong> ${ccStr}</p>` : ''}
|
${ccStr ? `<p><strong>Cc:</strong> ${ccStr}</p>` : ''}
|
||||||
<div style="margin-top: 15px; border-top: 1px solid #eee; padding-top: 15px;">
|
<div style="margin-top: 15px; border-top: 1px solid #eee; padding-top: 15px;">
|
||||||
@ -399,8 +351,8 @@ export function formatForwardedEmail(originalEmail: EmailMessage | LegacyEmailMe
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Apply consistent text direction
|
// Process the content with proper direction
|
||||||
const htmlContent = applyTextDirection(forwardBody);
|
const processed = processContentWithDirection(forwardBody);
|
||||||
|
|
||||||
// Create plain text content
|
// Create plain text content
|
||||||
const textContent = `
|
const textContent = `
|
||||||
@ -408,7 +360,7 @@ export function formatForwardedEmail(originalEmail: EmailMessage | LegacyEmailMe
|
|||||||
---------- Forwarded message ---------
|
---------- Forwarded message ---------
|
||||||
From: ${fromStr}
|
From: ${fromStr}
|
||||||
Date: ${dateStr}
|
Date: ${dateStr}
|
||||||
Subject: ${email.subject || ''}
|
Subject: ${subject || ''}
|
||||||
To: ${toStr}
|
To: ${toStr}
|
||||||
${ccStr ? `Cc: ${ccStr}\n` : ''}
|
${ccStr ? `Cc: ${ccStr}\n` : ''}
|
||||||
|
|
||||||
@ -417,12 +369,12 @@ ${originalTextContent}
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
to: '',
|
to: '',
|
||||||
subject,
|
subject: subject.startsWith('Fwd:') ? subject : `Fwd: ${subject}`,
|
||||||
content: {
|
content: {
|
||||||
text: textContent,
|
text: textContent,
|
||||||
html: htmlContent,
|
html: processed.html,
|
||||||
isHtml: true,
|
isHtml: true,
|
||||||
direction: 'ltr' as const // Forward is LTR, but original content keeps its direction
|
direction: 'ltr' as const
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,9 @@
|
|||||||
* to ensure consistent behavior across the application.
|
* 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
|
* 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
|
* Uses a comprehensive regex pattern that covers Arabic, Hebrew, and other RTL scripts
|
||||||
@ -62,3 +65,115 @@ export function applyTextDirection(htmlContent: string, textContent?: string): s
|
|||||||
// Otherwise, wrap the content with a direction-aware container
|
// Otherwise, wrap the content with a direction-aware container
|
||||||
return `<div class="email-content" dir="${direction}">${htmlContent}</div>`;
|
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) {
|
||||||
|
textContent = email.content.text || '';
|
||||||
|
htmlContent = email.content.html || '';
|
||||||
|
} 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 || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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')
|
||||||
|
)) {
|
||||||
|
htmlContent = content;
|
||||||
|
} else {
|
||||||
|
textContent = content;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// EmailContent object
|
||||||
|
textContent = content.text || '';
|
||||||
|
htmlContent = content.html || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always ensure we have text for direction detection
|
||||||
|
if (!textContent && htmlContent) {
|
||||||
|
// Extract text from HTML for direction detection
|
||||||
|
textContent = htmlContent.replace(/<[^>]*>/g, '')
|
||||||
|
.replace(/ /g, ' ')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/&/g, '&');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect direction from text
|
||||||
|
const direction = detectTextDirection(textContent);
|
||||||
|
|
||||||
|
// Sanitize HTML if present
|
||||||
|
if (htmlContent) {
|
||||||
|
// Sanitize HTML first
|
||||||
|
htmlContent = sanitizeHtml(htmlContent);
|
||||||
|
|
||||||
|
// Then apply direction
|
||||||
|
htmlContent = applyTextDirection(htmlContent, textContent);
|
||||||
|
} else if (textContent) {
|
||||||
|
// Convert plain text to HTML with proper direction
|
||||||
|
htmlContent = `<div dir="${direction}">${textContent.replace(/\n/g, '<br>')}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return processed content
|
||||||
|
return {
|
||||||
|
text: textContent,
|
||||||
|
html: htmlContent,
|
||||||
|
direction
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user