var MassMailerContacts = { parseCsv: function (text) { if (text.charCodeAt(0) === 65279) text = text.substring(1); var rows = [], row = [], field = "", quoted = false, started = false; for (var i = 0; i < text.length; i++) { var ch = text.charAt(i); if (quoted) { if (ch === '"') { if (text.charAt(i + 1) === '"') { field += '"'; i++; } else quoted = false; } else field += ch; } else if (ch === '"') { if (field.length !== 0) throw new Error("Unexpected quote in CSV row " + (rows.length + 1)); quoted = true; started = true; } else if (ch === ",") { row.push(field); field = ""; started = true; } else if (ch === "\r" || ch === "\n") { row.push(field); field = ""; if (started || row.length > 1) rows.push(row); row = []; started = false; if (ch === "\r" && text.charAt(i + 1) === "\n") i++; } else { field += ch; started = true; } } if (quoted) throw new Error("Unclosed quote in CSV"); if (started || row.length > 0 || field.length > 0) { row.push(field); rows.push(row); } return rows; }, emailKey: function (email) { return email.replace(/^\s+|\s+$/g, "").toLowerCase(); }, isEmail: function (email) { return email.length <= 254 && /^[^\s@,;<>]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/.test(email); }, isoDate: function (value) { if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return false; var year = Number(value.substring(0, 4)); var month = Number(value.substring(5, 7)); var day = Number(value.substring(8, 10)); var date = new Date(year, month - 1, day); return date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day; }, preview: function (text, fallbackSource, existing, suppressed) { var rows = this.parseCsv(text); if (rows.length < 2) throw new Error("CSV needs a header and at least one contact row"); var headers = {}, i, key; for (i = 0; i < rows[0].length; i++) { key = rows[0][i].replace(/^\s+|\s+$/g, "").toLowerCase(); if (headers["$" + key] !== undefined) throw new Error("Duplicate CSV header: " + key); headers["$" + key] = i; } if (headers.$email === undefined) throw new Error("CSV needs an email column"); var accepted = [], rejected = [], seen = {}; for (i = 1; i < rows.length; i++) { var cells = rows[i]; var email = this.cell(cells, headers, "email"); var emailKey = this.emailKey(email); var businessName = this.cell(cells, headers, "business_name"); var source = this.cell(cells, headers, "source_reference") || fallbackSource; var acquiredAt = this.cell(cells, headers, "acquired_at"); var reason = ""; if (cells.length > rows[0].length) reason = "Too many columns"; else if (!this.isEmail(email)) reason = "Invalid email"; else if (!source) reason = "Missing source reference"; else if (source.length > 2048) reason = "Source reference too long"; else if (businessName.length > 200) reason = "Business name too long"; else if (acquiredAt && !this.isoDate(acquiredAt)) reason = "acquired_at must be YYYY-MM-DD"; else if (suppressed[emailKey]) reason = "Suppressed address"; else if (existing[emailKey]) reason = "Already imported"; else if (seen[emailKey]) reason = "Duplicate in CSV"; var item = { line: i + 1, email: email, emailKey: emailKey, businessName: businessName, source: source, acquiredAt: acquiredAt, reason: reason }; if (reason) rejected.push(item); else { accepted.push(item); seen[emailKey] = true; } } return { accepted: accepted, rejected: rejected }; }, cell: function (cells, headers, name) { if (headers["$" + name] === undefined) return ""; return (cells[headers["$" + name]] || "").replace(/^\s+|\s+$/g, ""); } };