UNPKG

automizy-email-editor

Version:

Design mobile friendly HTML emails in your application.

252 lines (227 loc) 8.38 kB
// get accurate timing. // This file must be loaded after prettify.js for this to work. PR_SHOULD_USE_CONTINUATION = false; var attribToHtml, textToHtml; var getInnerHtml; (function () { /** is the given node's innerHTML normally unescaped? */ function isRawContent(node) { return 'XMP' === node.tagName; } var newlineRe = /[\r\n]/g; /** * Are newlines and adjacent spaces significant in the given node's innerHTML? */ function isPreformatted(node, content) { // PRE means preformatted, and is a very common case, so don't create // unnecessary computed style objects. if ('PRE' === node.tagName) { return true; } if (!newlineRe.test(content)) { return true; } // Don't care var whitespace = ''; // For disconnected nodes, IE has no currentStyle. if (node.currentStyle) { whitespace = node.currentStyle.whiteSpace; } else if (window.getComputedStyle) { // Firefox makes a best guess if node is disconnected whereas Safari // returns the empty string. whitespace = window.getComputedStyle(node, null).whiteSpace; } return !whitespace || whitespace === 'pre'; } // Define regexps here so that the interpreter doesn't have to create an // object each time the function containing them is called. // The language spec requires a new object created even if you don't access // the $1 members. var pr_amp = /&/g; var pr_lt = /</g; var pr_gt = />/g; var pr_quot = /\"/g; /** escapest html special characters to html. */ textToHtml = function (str) { return str.replace(pr_amp, '&amp;') .replace(pr_lt, '&lt;') .replace(pr_gt, '&gt;'); }; /** like textToHtml but escapes double quotes to be attribute safe. */ attribToHtml = function (str) { return str.replace(pr_amp, '&amp;') .replace(pr_lt, '&lt;') .replace(pr_gt, '&gt;') .replace(pr_quot, '&quot;'); }; var PR_innerHtmlWorks = null; getInnerHtml = function (node) { // inner html is hopelessly broken in Safari 2.0.4 when the content is // an html description of well formed XML and the containing tag is a PRE // tag, so we detect that case and emulate innerHTML. if (null === PR_innerHtmlWorks) { var testNode = document.createElement('PRE'); testNode.appendChild( document.createTextNode('<!DOCTYPE foo PUBLIC "foo bar">\n<foo />')); PR_innerHtmlWorks = !/</.test(testNode.innerHTML); } if (PR_innerHtmlWorks) { var content = node.innerHTML; // XMP tags contain unescaped entities so require special handling. if (isRawContent(node)) { content = textToHtml(content); } else if (!isPreformatted(node, content)) { content = content.replace(/(<br\s*\/?>)[\r\n]+/g, '$1') .replace(/(?:[\r\n]+[ \t]*)+/g, ' '); } return content; } var out = []; for (var child = node.firstChild; child; child = child.nextSibling) { normalizedHtml(child, out); } return out.join(''); }; })(); function normalizedHtml(node, out, opt_sortAttrs) { switch (node.nodeType) { case 1: // an element var name = node.tagName.toLowerCase(); out.push('<', name); var attrs = node.attributes; var n = attrs.length; if (n) { if (opt_sortAttrs) { var sortedAttrs = []; for (var i = n; --i >= 0;) { sortedAttrs[i] = attrs[i]; } sortedAttrs.sort(function (a, b) { return (a.name < b.name) ? -1 : a.name === b.name ? 0 : 1; }); attrs = sortedAttrs; } for (var i = 0; i < n; ++i) { var attr = attrs[i]; if (!attr.specified) { continue; } out.push(' ', attr.name.toLowerCase(), '="', attribToHtml(attr.value), '"'); } } out.push('>'); for (var child = node.firstChild; child; child = child.nextSibling) { normalizedHtml(child, out, opt_sortAttrs); } if (node.firstChild || !/^(?:br|link|img)$/.test(name)) { out.push('<\/', name, '>'); } break; case 3: case 4: // text out.push(textToHtml(node.nodeValue)); break; } } /** * @param golden a mapping from IDs of prettyprinted chunks to an abbreviated * form of the expected output. See "var goldens" in prettify_test.html * for an example. */ function go(goldens) { startClock(); prettyPrint(function () { stopClock(); runTests(goldens); }); } function runTests(goldens) { /** number of characters in common at the end up to max. */ function commonPrefix(a, b) { var n = Math.min(a.length, b.length); var i; for (i = 0; i < n; ++i) { if (a.charAt(i) !== b.charAt(i)) { break; } } return i; } /** number of characters in common at the end up to max. */ function commonSuffix(a, b, max) { var n = Math.min(a.length - max, b.length - max); var i; for (i = 0; i < n; ++i) { if (a.charAt(a.length - i - 1) !== b.charAt(b.length - i - 1)) { break; } } return i; } /** convert a plain text string to html by escaping html special chars. */ function html(plainText) { return attribToHtml(plainText).replace(/\xa0/g, '&nbsp;'); } /** * get normalized markup. innerHTML varies enough across browsers that we * can't use it. */ function normalizedInnerHtml(node) { var out = []; for (var child = node.firstChild; child; child = child.nextSibling) { normalizedHtml(child, out, true); } out = out.join(''); // more normalization to work around problems with non-ascii chars in // regexps in Safari for (var i = 0; (i = out.indexOf('\xa0')) >= 0;) { out = out.substring(0, i) + '&nbsp;' + out.substring(i + 1); } return out.replace(/\r\n?/g, '\n'); } var htmlOut = []; var failures = 0; document.getElementById('errorReport').innerHTML = '<h1>Running tests&hellip;<\/h1>'; htmlOut.push('<h1>Test results<\/h1>'); for (var lang in goldens) { var container = document.getElementById(lang); // Convert abbreviations that start with `. var golden = goldens[lang].replace(/`([A-Z]{3})/g, function (_, lbl) { return (lbl == 'END' ? '<\/span>' : '<span class="' + lbl.toLowerCase() + '">'); }) // Line numbers .replace(/`#(?![0-9])/, '<li class="L0">') .replace(/`#([0-9])/g, '</li><li class="L$1">'); var actual = normalizedInnerHtml(container); if (golden !== actual) { // test failed // write out var pre = commonPrefix(golden, actual); var post = commonSuffix(golden, actual, pre); ++failures; htmlOut.push( '<h2><a href="#' + html(lang) + '">' + html(lang) + '<\/a> Failed<\/h2>'); htmlOut.push( '<tt>' + html(golden.substring(0, pre)) + '&raquo;<span class="mismatch">' + html(golden.substring(pre, golden.length - post)) + '<\/span>&laquo;' + html(golden.substring(golden.length - post)) + '<br>!==<br>' + html(actual.substring(0, pre)) + '&raquo;<span class="mismatch">' + html(actual.substring(pre, actual.length - post)) + '<\/span>&laquo;' + html(actual.substring(actual.length - post)) + '<\/tt>'); } else { htmlOut.push( '<h2><a href="#' + html(lang) + '">' + html(lang) + '<\/a> OK<\/h2>'); } } var summary = ( failures ? (failures + ' test' + (failures === 1 ? '' : 's') + ' failed') : 'Tests Passed'); var summaryStr = '<h2>' + summary + '<\/h2>'; htmlOut.push(summaryStr); htmlOut.splice(0, 0, summaryStr); document.title += ' \u2014 ' + summary; document.getElementById('errorReport').innerHTML = htmlOut.join('').replace(/&lt;br&gt;/g, '&lt;br&gt;\n'); } var startTime = null; function startClock() { startTime = (new Date).getTime(); } function stopClock() { var delta = (new Date).getTime() - startTime; startTime = null; document.getElementById('timing').innerHTML = 'Took ' + delta + ' ms'; }