payload-plugin-newsletter
Version:
Complete newsletter management plugin for Payload CMS with subscriber management, magic link authentication, and email service integration
1 lines • 21.8 kB
Source Map (JSON)
{"version":3,"sources":["../src/exports/utils.ts","../src/utils/emailSafeHtml.ts","../src/utils/validateEmailHtml.ts"],"sourcesContent":["// Email utilities\nexport { convertToEmailSafeHtml, EMAIL_SAFE_CONFIG } from '../utils/emailSafeHtml'\nexport { validateEmailHtml } from '../utils/validateEmailHtml'\nexport type { ValidationResult } from '../utils/validateEmailHtml'","import DOMPurify from 'isomorphic-dompurify'\nimport type { SerializedEditorState } from 'lexical'\n\n/**\n * DOMPurify configuration for email-safe HTML\n */\nexport const EMAIL_SAFE_CONFIG = {\n ALLOWED_TAGS: [\n 'p', 'br', 'strong', 'b', 'em', 'i', 'u', 'strike', 's', 'span',\n 'a', 'h1', 'h2', 'h3', 'ul', 'ol', 'li', 'blockquote', 'hr'\n ],\n ALLOWED_ATTR: ['href', 'style', 'target', 'rel', 'align'],\n ALLOWED_STYLES: {\n '*': [\n 'color', 'background-color', 'font-size', 'font-weight',\n 'font-style', 'text-decoration', 'text-align', 'margin',\n 'margin-top', 'margin-right', 'margin-bottom', 'margin-left',\n 'padding', 'padding-top', 'padding-right', 'padding-bottom', \n 'padding-left', 'line-height', 'border-left', 'border-left-width',\n 'border-left-style', 'border-left-color'\n ],\n },\n FORBID_TAGS: ['script', 'style', 'iframe', 'object', 'embed', 'form', 'input'],\n FORBID_ATTR: ['class', 'id', 'onclick', 'onload', 'onerror'],\n}\n\n/**\n * Converts Lexical editor state to email-safe HTML\n */\nexport async function convertToEmailSafeHtml(\n editorState: SerializedEditorState,\n options?: {\n wrapInTemplate?: boolean\n preheader?: string\n }\n): Promise<string> {\n // First, convert Lexical state to HTML using custom converters\n const rawHtml = await lexicalToEmailHtml(editorState)\n \n // Sanitize the HTML\n const sanitizedHtml = DOMPurify.sanitize(rawHtml, EMAIL_SAFE_CONFIG)\n \n // Optionally wrap in email template\n if (options?.wrapInTemplate) {\n return wrapInEmailTemplate(sanitizedHtml, options.preheader)\n }\n \n return sanitizedHtml\n}\n\n/**\n * Custom Lexical to HTML converter for email\n */\nasync function lexicalToEmailHtml(editorState: SerializedEditorState): Promise<string> {\n const { root } = editorState\n \n if (!root || !root.children) {\n return ''\n }\n \n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const html = root.children.map((node: any) => convertNode(node)).join('')\n return html\n}\n\n/**\n * Convert individual Lexical nodes to email-safe HTML\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction convertNode(node: any): string {\n switch (node.type) {\n case 'paragraph':\n return convertParagraph(node)\n case 'heading':\n return convertHeading(node)\n case 'list':\n return convertList(node)\n case 'listitem':\n return convertListItem(node)\n case 'blockquote':\n return convertBlockquote(node)\n case 'text':\n return convertText(node)\n case 'link':\n return convertLink(node)\n case 'linebreak':\n return '<br>'\n default:\n // Unknown node type - convert children if any\n if (node.children) {\n return node.children.map(convertNode).join('')\n }\n return ''\n }\n}\n\n/**\n * Convert paragraph node\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction convertParagraph(node: any): string {\n const align = getAlignment(node.format)\n const children = node.children?.map(convertNode).join('') || ''\n \n if (!children.trim()) {\n return '<p style=\"margin: 0 0 16px 0; min-height: 1em;\"> </p>'\n }\n \n return `<p style=\"margin: 0 0 16px 0; text-align: ${align};\">${children}</p>`\n}\n\n/**\n * Convert heading node\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction convertHeading(node: any): string {\n const tag = node.tag || 'h1'\n const align = getAlignment(node.format)\n const children = node.children?.map(convertNode).join('') || ''\n \n const styles: Record<string, string> = {\n h1: 'font-size: 32px; font-weight: 700; margin: 0 0 24px 0; line-height: 1.2;',\n h2: 'font-size: 24px; font-weight: 600; margin: 0 0 16px 0; line-height: 1.3;',\n h3: 'font-size: 20px; font-weight: 600; margin: 0 0 12px 0; line-height: 1.4;',\n }\n \n const style = `${styles[tag] || styles.h3} text-align: ${align};`\n \n return `<${tag} style=\"${style}\">${children}</${tag}>`\n}\n\n/**\n * Convert list node\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction convertList(node: any): string {\n const tag = node.listType === 'number' ? 'ol' : 'ul'\n const children = node.children?.map(convertNode).join('') || ''\n \n const style = tag === 'ul' \n ? 'margin: 0 0 16px 0; padding-left: 24px; list-style-type: disc;'\n : 'margin: 0 0 16px 0; padding-left: 24px; list-style-type: decimal;'\n \n return `<${tag} style=\"${style}\">${children}</${tag}>`\n}\n\n/**\n * Convert list item node\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction convertListItem(node: any): string {\n const children = node.children?.map(convertNode).join('') || ''\n return `<li style=\"margin: 0 0 8px 0;\">${children}</li>`\n}\n\n/**\n * Convert blockquote node\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction convertBlockquote(node: any): string {\n const children = node.children?.map(convertNode).join('') || ''\n const style = 'margin: 0 0 16px 0; padding-left: 16px; border-left: 4px solid #e5e7eb; color: #6b7280;'\n \n return `<blockquote style=\"${style}\">${children}</blockquote>`\n}\n\n/**\n * Convert text node\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction convertText(node: any): string {\n let text = escapeHtml(node.text || '')\n \n // Apply formatting\n if (node.format & 1) { // Bold\n text = `<strong>${text}</strong>`\n }\n if (node.format & 2) { // Italic\n text = `<em>${text}</em>`\n }\n if (node.format & 8) { // Underline\n text = `<u>${text}</u>`\n }\n if (node.format & 4) { // Strikethrough\n text = `<strike>${text}</strike>`\n }\n \n return text\n}\n\n/**\n * Convert link node\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction convertLink(node: any): string {\n const children = node.children?.map(convertNode).join('') || ''\n const url = node.fields?.url || '#'\n \n // Ensure links open in new tab and have security attributes\n return `<a href=\"${escapeHtml(url)}\" target=\"_blank\" rel=\"noopener noreferrer\" style=\"color: #2563eb; text-decoration: underline;\">${children}</a>`\n}\n\n/**\n * Get text alignment from format number\n */\nfunction getAlignment(format?: number): string {\n if (!format) return 'left'\n \n // Lexical alignment format values\n if (format & 2) return 'center'\n if (format & 3) return 'right'\n if (format & 4) return 'justify'\n \n return 'left'\n}\n\n/**\n * Escape HTML special characters\n */\nfunction escapeHtml(text: string): string {\n const map: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": '''\n }\n \n return text.replace(/[&<>\"']/g, m => map[m])\n}\n\n/**\n * Wrap content in a basic email template\n */\nfunction wrapInEmailTemplate(content: string, preheader?: string): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Email</title>\n <!--[if mso]>\n <noscript>\n <xml>\n <o:OfficeDocumentSettings>\n <o:PixelsPerInch>96</o:PixelsPerInch>\n </o:OfficeDocumentSettings>\n </xml>\n </noscript>\n <![endif]-->\n</head>\n<body style=\"margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 16px; line-height: 1.5; color: #333333; background-color: #f3f4f6;\">\n ${preheader ? `<div style=\"display: none; max-height: 0; overflow: hidden;\">${escapeHtml(preheader)}</div>` : ''}\n <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\" style=\"margin: 0; padding: 0;\">\n <tr>\n <td align=\"center\" style=\"padding: 20px 0;\">\n <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" width=\"600\" style=\"margin: 0 auto; background-color: #ffffff; border-radius: 8px; overflow: hidden;\">\n <tr>\n <td style=\"padding: 40px 30px;\">\n ${content}\n </td>\n </tr>\n </table>\n </td>\n </tr>\n </table>\n</body>\n</html>`\n}\n\n/**\n * Extract personalization tags from content\n */\nexport function extractPersonalizationTags(html: string): string[] {\n const regex = /\\{\\{([^}]+)\\}\\}/g\n const tags: string[] = []\n let match\n \n while ((match = regex.exec(html)) !== null) {\n tags.push(match[1].trim())\n }\n \n return [...new Set(tags)]\n}\n\n/**\n * Replace personalization tags with sample data\n */\nexport function replacePersonalizationTags(\n html: string, \n sampleData: Record<string, string>\n): string {\n return html.replace(/\\{\\{([^}]+)\\}\\}/g, (match, tag) => {\n const trimmedTag = tag.trim()\n return sampleData[trimmedTag] || match\n })\n}","/**\n * Email HTML validation utilities\n */\n\nexport interface ValidationResult {\n valid: boolean\n warnings: string[]\n errors: string[]\n stats: {\n sizeInBytes: number\n imageCount: number\n linkCount: number\n hasExternalStyles: boolean\n hasJavaScript: boolean\n }\n}\n\n/**\n * Validate HTML for email compatibility\n */\nexport function validateEmailHtml(html: string): ValidationResult {\n const warnings: string[] = []\n const errors: string[] = []\n \n // Calculate size\n const sizeInBytes = new Blob([html]).size\n \n // Check size limits\n if (sizeInBytes > 102400) { // 100KB\n warnings.push(`Email size (${Math.round(sizeInBytes / 1024)}KB) exceeds Gmail's 102KB limit - email may be clipped`)\n }\n \n // Check for problematic CSS\n if (html.includes('position:') && (html.includes('position: absolute') || html.includes('position: fixed'))) {\n errors.push('Absolute/fixed positioning is not supported in most email clients')\n }\n \n if (html.includes('display: flex') || html.includes('display: grid')) {\n errors.push('Flexbox and Grid layouts are not supported in many email clients')\n }\n \n if (html.includes('@media')) {\n warnings.push('Media queries may not work in all email clients')\n }\n \n // Check for JavaScript\n const hasJavaScript = \n html.includes('<script') || \n html.includes('onclick') || \n html.includes('onload') ||\n html.includes('javascript:')\n \n if (hasJavaScript) {\n errors.push('JavaScript is not supported in email and will be stripped by email clients')\n }\n \n // Check for external styles\n const hasExternalStyles = html.includes('<link') && html.includes('stylesheet')\n if (hasExternalStyles) {\n errors.push('External stylesheets are not supported - use inline styles only')\n }\n \n // Check for forms\n if (html.includes('<form') || html.includes('<input') || html.includes('<button')) {\n errors.push('Forms and form elements are not reliably supported in email')\n }\n \n // Check for unsupported tags\n const unsupportedTags = [\n 'video', 'audio', 'iframe', 'embed', 'object', 'canvas', 'svg'\n ]\n \n for (const tag of unsupportedTags) {\n if (html.includes(`<${tag}`)) {\n errors.push(`<${tag}> tags are not supported in email`)\n }\n }\n \n // Count images and links\n const imageCount = (html.match(/<img/g) || []).length\n const linkCount = (html.match(/<a/g) || []).length\n \n // Check image usage\n if (imageCount > 20) {\n warnings.push(`High number of images (${imageCount}) may affect email performance`)\n }\n \n // Check for missing alt text\n const imagesWithoutAlt = (html.match(/<img(?![^>]*\\balt\\s*=)[^>]*>/g) || []).length\n if (imagesWithoutAlt > 0) {\n warnings.push(`${imagesWithoutAlt} image(s) missing alt text - important for accessibility`)\n }\n \n // Check for proper link attributes\n const linksWithoutTarget = (html.match(/<a(?![^>]*\\btarget\\s*=)[^>]*>/g) || []).length\n if (linksWithoutTarget > 0) {\n warnings.push(`${linksWithoutTarget} link(s) missing target=\"_blank\" attribute`)\n }\n \n // Check for CSS property usage\n if (html.includes('margin: auto') || html.includes('margin:auto')) {\n warnings.push('margin: auto is not supported in Outlook - use align=\"center\" or tables for centering')\n }\n \n if (html.includes('background-image')) {\n warnings.push('Background images are not reliably supported - consider using <img> tags instead')\n }\n \n // Check for rem/em units\n if (html.match(/\\d+\\s*(rem|em)/)) {\n warnings.push('rem/em units may render inconsistently - use px for reliable sizing')\n }\n \n // Check for negative margins\n if (html.match(/margin[^:]*:\\s*-\\d+/)) {\n errors.push('Negative margins are not supported in many email clients')\n }\n \n // Validate personalization tags\n const personalizationTags = html.match(/\\{\\{([^}]+)\\}\\}/g) || []\n const validTags = ['subscriber.name', 'subscriber.email', 'subscriber.firstName', 'subscriber.lastName']\n \n for (const tag of personalizationTags) {\n const tagContent = tag.replace(/[{}]/g, '').trim()\n if (!validTags.includes(tagContent)) {\n warnings.push(`Unknown personalization tag: ${tag}`)\n }\n }\n \n return {\n valid: errors.length === 0,\n warnings,\n errors,\n stats: {\n sizeInBytes,\n imageCount,\n linkCount,\n hasExternalStyles,\n hasJavaScript,\n }\n }\n}\n\n/**\n * Get email client compatibility warnings for specific HTML\n */\nexport function getClientCompatibilityWarnings(html: string): Record<string, string[]> {\n const warnings: Record<string, string[]> = {\n gmail: [],\n outlook: [],\n appleMail: [],\n mobile: [],\n }\n \n // Gmail specific\n if (html.includes('<style')) {\n warnings.gmail.push('Gmail may strip <style> tags in some contexts')\n }\n \n // Outlook specific\n if (html.includes('margin: auto') || html.includes('margin:auto')) {\n warnings.outlook.push('Outlook does not support margin: auto')\n }\n \n if (html.includes('padding') && html.includes('<p')) {\n warnings.outlook.push('Outlook may not respect padding on <p> tags')\n }\n \n if (html.includes('background-image')) {\n warnings.outlook.push('Outlook has limited background image support')\n }\n \n // Mobile specific\n const hasSmallText = html.match(/font-size:\\s*(\\d+)px/g)?.some(match => {\n const size = parseInt(match.match(/\\d+/)?.[0] || '16')\n return size < 14\n })\n \n if (hasSmallText) {\n warnings.mobile.push('Text smaller than 14px may be hard to read on mobile')\n }\n \n const hasSmallLinks = html.match(/<a[^>]*>[^<]{1,3}<\\/a>/g)\n if (hasSmallLinks) {\n warnings.mobile.push('Short link text may be hard to tap on mobile devices')\n }\n \n return warnings\n}\n\n/**\n * Suggest fixes for common email HTML issues\n */\nexport function suggestFixes(html: string): string[] {\n const suggestions: string[] = []\n \n if (html.includes('display: flex')) {\n suggestions.push('Replace flexbox with table-based layouts for better email client support')\n }\n \n if (html.includes('position: absolute')) {\n suggestions.push('Use table cells or margins instead of absolute positioning')\n }\n \n if (html.match(/\\d+rem/) || html.match(/\\d+em/)) {\n suggestions.push('Convert rem/em units to px for consistent rendering')\n }\n \n if (!html.includes('<!DOCTYPE')) {\n suggestions.push('Add <!DOCTYPE html> declaration for better rendering')\n }\n \n if (!html.includes('charset')) {\n suggestions.push('Add <meta charset=\"UTF-8\"> for proper character encoding')\n }\n \n return suggestions\n}"],"mappings":"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","names":["DOMPurify"]}