Neah/lib/utils/text-direction.ts
2025-05-01 10:47:24 +02:00

179 lines
5.6 KiB
TypeScript

/**
* Text Direction Utilities
*
* Centralized 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
*
* @param text Text to analyze for direction
* @returns 'rtl' if RTL characters are detected, otherwise 'ltr'
*/
export function detectTextDirection(text: string | undefined | null): 'ltr' | 'rtl' {
if (!text) return 'ltr';
// Comprehensive pattern for RTL languages:
// - Arabic (0600-06FF, FB50-FDFF, FE70-FEFF)
// - Hebrew (0590-05FF, FB1D-FB4F)
// - RTL marks and controls (200F, 202B, 202E)
const rtlPattern = /[\u0591-\u07FF\u200F\u202B\u202E\uFB1D-\uFDFD\uFE70-\uFEFC]/;
return rtlPattern.test(text) ? 'rtl' : 'ltr';
}
/**
* Adds appropriate direction attribute to HTML content based on content analysis
*
* @param htmlContent HTML content to analyze and enhance with direction
* @param textContent Plain text version for direction analysis (optional)
* @returns HTML with appropriate direction attribute
*/
export function applyTextDirection(htmlContent: string, textContent?: string): string {
if (!htmlContent) return '';
// If text content is provided, use it for direction detection
// Otherwise extract text from HTML for direction detection
const textForAnalysis = textContent ||
htmlContent.replace(/<[^>]*>/g, '')
.replace(/&nbsp;/g, ' ')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&amp;/g, '&');
const direction = detectTextDirection(textForAnalysis);
// If the HTML already has a dir attribute, don't override it
if (htmlContent.includes('dir="rtl"') || htmlContent.includes('dir="ltr"')) {
return htmlContent;
}
// Check if we already have an email-content wrapper
if (htmlContent.startsWith('<div class="email-content')) {
// Replace opening div with one that includes direction
return htmlContent.replace(
/<div class="email-content([^"]*)"/,
`<div class="email-content$1" dir="${direction}"`
);
}
// 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) {
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(/&nbsp;/g, ' ')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&amp;/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
};
}