├── index.js ├── README.md ├── .gitignore ├── package.json ├── test ├── html.js └── text.js └── lib ├── html.js └── text.js /index.js: -------------------------------------------------------------------------------- 1 | module.exports.indentText = require("./lib/text").indentText; 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | mailpurify 2 | ========== 3 | 4 | Tidy e-mail HTML contents 5 | 6 | Doesn't really work yet. See tests for examples -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | lib-cov 4 | *.seed 5 | *.log 6 | *.csv 7 | *.dat 8 | *.out 9 | *.pid 10 | *.gz 11 | 12 | pids 13 | logs 14 | results 15 | 16 | node_modules 17 | npm-debug.log -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mailpurify", 3 | "version": "0.1.5", 4 | "description": "Convert plaintext e-mail body to HTML", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "nodeunit test" 8 | }, 9 | "repository": "", 10 | "author": "Andris Reinman", 11 | "license": "MIT", 12 | "dependencies": { 13 | }, 14 | "devDependencies": { 15 | "jsdom": "*", 16 | "nodeunit": "*", 17 | "mailparser": "*" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/html.js: -------------------------------------------------------------------------------- 1 | var testCase = require('nodeunit').testCase, 2 | indentHTML = require("../lib/html").indentHTML, 3 | MailParser = require("mailparser").Mailparser, 4 | jsdom = require("jsdom"); 5 | 6 | exports["Simple paragraphs"] = function(test){ 7 | var mailHtml = "

test

sdfsd

"; 8 | indentHTML(mailHtml, function(err, html){ 9 | console.log(html) 10 | test.ifError(err) 11 | test.done(); 12 | }); 13 | } -------------------------------------------------------------------------------- /lib/html.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tidies up mail HTML body into HTML with nested quotes 3 | * 4 | * @param {String} text E-mail html body 5 | * @param {Function} callback Callback function to run when ready 6 | */ 7 | module.exports.indentHTML = function(html, callback){ 8 | html = (html || "").toString().trim(); 9 | 10 | if(!html){ 11 | return callback(null, ""); 12 | } 13 | 14 | html = (html || "").toString().trim(); 15 | 16 | return callback(null, html); 17 | } -------------------------------------------------------------------------------- /test/text.js: -------------------------------------------------------------------------------- 1 | var testCase = require('nodeunit').testCase, 2 | indentText = require("../lib/text").indentText, 3 | MailParser = require("mailparser").Mailparser, 4 | jsdom = require("jsdom"); 5 | 6 | exports["Simple paragraphs"] = function(test){ 7 | var mailText = 'This is line nr 1\r\n'+ 8 | 'This is the same paragraph\n\n'+ 9 | 'This is another paragraph\n\n'+ 10 | 'And yet another paragraph'; 11 | 12 | jsdom.env(indentText(mailText), function(err, window){ 13 | var list; 14 | 15 | test.ifError(err); 16 | test.ok(!!window); 17 | 18 | list = Array.prototype.slice.call(window.document.body.getElementsByTagName("p") || []); 19 | 20 | test.equal(list.length, 3); 21 | test.equal((list[0].innerHTML || "").trim(), 'This is line nr 1
\nThis is the same paragraph'); 22 | test.equal((list[1].innerHTML || "").trim(), 'This is another paragraph'); 23 | test.equal((list[2].innerHTML || "").trim(), 'And yet another paragraph'); 24 | 25 | test.done(); 26 | }); 27 | } 28 | 29 | exports["Quote block"] = function(test){ 30 | var mailText = 'This is line nr 1\r\n'+ 31 | '> This is the same paragraph\n'+ 32 | '> This is another paragraph\n'+ 33 | '> And yet another paragraph'; 34 | 35 | jsdom.env(indentText(mailText), function(err, window){ 36 | test.ifError(err); 37 | test.ok(!!window); 38 | 39 | test.equal(Array.prototype.slice.call(window.document.body.getElementsByTagName("p") || []).length, 2); 40 | test.equal(Array.prototype.slice.call(window.document.body.getElementsByTagName("blockquote") || []).length, 1); 41 | 42 | test.done(); 43 | }); 44 | } -------------------------------------------------------------------------------- /lib/text.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Converts mail text body into HTML with nested quotes 4 | * 5 | * @param {String} text E_mail text body 6 | * @param {Number} [level=0] Nesting level 7 | * @return {String} HTML code 8 | */ 9 | module.exports.indentText = function(text, level){ 10 | return replaceURLWithHTMLLinks(indenter(text)).trim(); 11 | } 12 | 13 | function indenter(text, level){ 14 | var nodes = [], node, type = "default", startPos = false, lines, group, match; 15 | 16 | text = (text || "").toString(); 17 | level = level || 0; 18 | 19 | // Handle Outlook and friends 20 | lines = text.split(/\r?\n/); 21 | for(var i=3, j=3, len = lines.length; i= 3){ 30 | while(i/)){ 50 | line = line.substr(1); 51 | if(type == "quote"){ 52 | if(line.length && line.charAt(0) != ">"){ 53 | startPos = line.match(/^\s*/)[0].length; 54 | if(node.startPos === false || startPos < node.startPos){ 55 | node.startPos = startPos; 56 | } 57 | } 58 | node.lines.push(line); 59 | }else{ 60 | if(line.length && line.charAt(0) != ">"){ 61 | startPos = line.match(/^\s*/)[0].length; 62 | }else{ 63 | startPos = false; 64 | } 65 | node = {type: "quote", startPos: startPos, lines: [line]}; 66 | nodes.push(node); 67 | type = "quote"; 68 | } 69 | }else if(!line){ 70 | type = "default"; 71 | }else{ 72 | if(type == "text"){ 73 | node.lines.push(line); 74 | }else{ 75 | node = {type: "text", lines: [line]}; 76 | nodes.push(node); 77 | type = "text"; 78 | } 79 | } 80 | }); 81 | 82 | // convert found nodes to html 83 | text = nodes.map(function(node){ 84 | if(node.type == "text"){ 85 | return "

" + node.lines.map(function(line){ 86 | return line.replace(/&/g,"&"). 87 | replace(/^\s+/, function(str){ 88 | return new Array(str.length+1).join(" "); 89 | }). 90 | replace(//g,">"). 92 | replace(/"/g,"""); 93 | }).join("
\n") + "

"; 94 | }else if(node.type == "quote"){ 95 | return "
\n" + 96 | indenter(node.lines.map(function(line){ 97 | return line.substr(line.charAt(0) != ">" && node.startPos || 0); 98 | }).join("\n"), level + 1) + 99 | "
"; 100 | } 101 | }).join("\n"); 102 | 103 | return text; 104 | } 105 | 106 | // Modified http://stackoverflow.com/a/37687 107 | function replaceURLWithHTMLLinks(text){ 108 | var exp = /(\b(?:https?|ftp|file):\/\/|mailto:)([-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig; 109 | return text.replace(exp, function(full, prefix, link){ 110 | return ''+(prefix.toLowerCase() != "mailto:" ? full : link)+''; 111 | }); 112 | } 113 | --------------------------------------------------------------------------------