import htmlEntities from './html-entities.js'; export function decodeHTMLEntities(str) { return str.replace(/&(#\d+|#x[a-f0-9]+|[a-z]+\d*);?/gi, (match, entity) => { if (typeof htmlEntities[match] === 'string') { return htmlEntities[match]; } if (entity.charAt(0) !== '#' || match.charAt(match.length - 1) !== ';') { // keep as is, invalid or unknown sequence return match; } let codePoint; if (entity.charAt(1) === 'x') { // hex codePoint = parseInt(entity.substr(2), 16); } else { // dec codePoint = parseInt(entity.substr(1), 10); } let output = ''; if ((codePoint >= 0xd800 && codePoint <= 0xdfff) || codePoint > 0x10ffff) { // Invalid range, return a replacement character instead return '\uFFFD'; } if (codePoint > 0xffff) { codePoint -= 0x10000; output += String.fromCharCode(((codePoint >>> 10) & 0x3ff) | 0xd800); codePoint = 0xdc00 | (codePoint & 0x3ff); } output += String.fromCharCode(codePoint); return output; }); } export function escapeHtml(str) { return str.trim().replace(/[<>"'?&]/g, c => { let hex = c.charCodeAt(0).toString(16); if (hex.length < 2) { hex = '0' + hex; } return '&#x' + hex.toUpperCase() + ';'; }); } export function textToHtml(str) { let html = escapeHtml(str).replace(/\n/g, '
'); return '
' + html + '
'; } export function htmlToText(str) { str = str // we can't process tags on multiple lines so remove newlines first .replace(/\r?\n/g, '\u0001') .replace(/<\!\-\-.*?\-\->/gi, ' ') .replace(/]*>/gi, '\n') .replace(/<\/?(p|div|table|tr|td|th)\b[^>]*>/gi, '\n\n') .replace(/]*>.*?<\/script\b[^>]*>/gi, ' ') .replace(/^.*]*>/i, '') .replace(/^.*<\/head\b[^>]*>/i, '') .replace(/^.*<\!doctype\b[^>]*>/i, '') .replace(/<\/body\b[^>]*>.*$/i, '') .replace(/<\/html\b[^>]*>.*$/i, '') .replace(/]*href\s*=\s*["']?([^\s"']+)[^>]*>/gi, ' ($1) ') .replace(/<\/?(span|em|i|strong|b|u|a)\b[^>]*>/gi, '') .replace(/]*>[\n\u0001\s]*/gi, '* ') .replace(/]*>/g, '\n-------------\n') .replace(/<[^>]*>/g, ' ') // convert linebreak placeholders back to newlines .replace(/\u0001/g, '\n') .replace(/[ \t]+/g, ' ') .replace(/^\s+$/gm, '') .replace(/\n\n+/g, '\n\n') .replace(/^\n+/, '\n') .replace(/\n+$/, '\n'); str = decodeHTMLEntities(str); return str; } function formatTextAddress(address) { return [] .concat(address.name || []) .concat(address.name ? `<${address.address}>` : address.address) .join(' '); } function formatTextAddresses(addresses) { let parts = []; let processAddress = (address, partCounter) => { if (partCounter) { parts.push(', '); } if (address.group) { let groupStart = `${address.name}:`; let groupEnd = `;`; parts.push(groupStart); address.group.forEach(processAddress); parts.push(groupEnd); } else { parts.push(formatTextAddress(address)); } }; addresses.forEach(processAddress); return parts.join(''); } function formatHtmlAddress(address) { return ``; } function formatHtmlAddresses(addresses) { let parts = []; let processAddress = (address, partCounter) => { if (partCounter) { parts.push(''); } if (address.group) { let groupStart = ``; let groupEnd = ``; parts.push(groupStart); address.group.forEach(processAddress); parts.push(groupEnd); } else { parts.push(formatHtmlAddress(address)); } }; addresses.forEach(processAddress); return parts.join(' '); } function foldLines(str, lineLength, afterSpace) { str = (str || '').toString(); lineLength = lineLength || 76; let pos = 0, len = str.length, result = '', line, match; while (pos < len) { line = str.substr(pos, lineLength); if (line.length < lineLength) { result += line; break; } if ((match = line.match(/^[^\n\r]*(\r?\n|\r)/))) { line = match[0]; result += line; pos += line.length; continue; } else if ( (match = line.match(/(\s+)[^\s]*$/)) && match[0].length - (afterSpace ? (match[1] || '').length : 0) < line.length ) { line = line.substr(0, line.length - (match[0].length - (afterSpace ? (match[1] || '').length : 0))); } else if ((match = str.substr(pos + line.length).match(/^[^\s]+(\s*)/))) { line = line + match[0].substr(0, match[0].length - (!afterSpace ? (match[1] || '').length : 0)); } result += line; pos += line.length; if (pos < len) { result += '\r\n'; } } return result; } export function formatTextHeader(message) { let rows = []; if (message.from) { rows.push({ key: 'From', val: formatTextAddress(message.from) }); } if (message.subject) { rows.push({ key: 'Subject', val: message.subject }); } if (message.date) { let dateOptions = { year: 'numeric', month: 'numeric', day: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric', hour12: false }; let dateStr = typeof Intl === 'undefined' ? message.date : new Intl.DateTimeFormat('default', dateOptions).format(new Date(message.date)); rows.push({ key: 'Date', val: dateStr }); } if (message.to && message.to.length) { rows.push({ key: 'To', val: formatTextAddresses(message.to) }); } if (message.cc && message.cc.length) { rows.push({ key: 'Cc', val: formatTextAddresses(message.cc) }); } if (message.bcc && message.bcc.length) { rows.push({ key: 'Bcc', val: formatTextAddresses(message.bcc) }); } // Align keys and values by adding space between these two // Also make sure that the separator line is as long as the longest line // Should end up with something like this: /* ----------------------------- From: xx xx Subject: Example Subject Date: 16/02/2021, 02:57:06 To: not@found.com ----------------------------- */ let maxKeyLength = rows .map(r => r.key.length) .reduce((acc, cur) => { return cur > acc ? cur : acc; }, 0); rows = rows.flatMap(row => { let sepLen = maxKeyLength - row.key.length; let prefix = `${row.key}: ${' '.repeat(sepLen)}`; let emptyPrefix = `${' '.repeat(row.key.length + 1)} ${' '.repeat(sepLen)}`; let foldedLines = foldLines(row.val, 80, true) .split(/\r?\n/) .map(line => line.trim()); return foldedLines.map((line, i) => `${i ? emptyPrefix : prefix}${line}`); }); let maxLineLength = rows .map(r => r.length) .reduce((acc, cur) => { return cur > acc ? cur : acc; }, 0); let lineMarker = '-'.repeat(maxLineLength); let template = ` ${lineMarker} ${rows.join('\n')} ${lineMarker} `; return template; } export function formatHtmlHeader(message) { let rows = []; if (message.from) { rows.push( `` ); } if (message.subject) { rows.push( `` ); } if (message.date) { let dateOptions = { year: 'numeric', month: 'numeric', day: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric', hour12: false }; let dateStr = typeof Intl === 'undefined' ? message.date : new Intl.DateTimeFormat('default', dateOptions).format(new Date(message.date)); rows.push( `` ); } if (message.to && message.to.length) { rows.push( `` ); } if (message.cc && message.cc.length) { rows.push( `` ); } if (message.bcc && message.bcc.length) { rows.push( `` ); } let template = ``; return template; }