blob: 2cf75f04548916617b18744a1556eb4550c74ff7 [file] [log] [blame]
// Generated by CoffeeScript 1.9.3
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/* THIS IS AN AUTOMATICALLY COMBINED FILE. PLEASE EDIT coffee/*.coffee!! */
/*
******************************************
Fetched from coffee/calendar.coffee
******************************************
*/
var BasicEmailDisplay, BasicListView, Calendar, DateEmailDisplay, HTML, HTTPRequest, SingleListView, ThreadedEmailDisplay, calendar_months, cog, dbRead, dbWrite, dealWithKeyboard, e, footerScaffolding, genColors, get, hasRead, headerScaffolding, hsl2rgb, isArray, isHash, listView, listviewScaffolding, markRead, maxLists, parseURL, pendingURLStatus, pending_spinner_at, pending_url_operations, pm_snap, pm_storage_available, pm_storage_globvar, ponymail_current_email, ponymail_current_listview, ponymail_display_models, ponymail_domain, ponymail_email_open, ponymail_list, ponymail_list_json, ponymail_listname, ponymail_lists, ponymail_listview_models, ponymail_month, ponymail_preferences, ponymail_query, ponymail_quote_regex, ponymail_register_display, ponymail_register_listview, ponymail_stored_email, ponymail_url_regex, ponymail_version, quickSearch, quickSearchBar, readEmail, renderListView, scaffoldingEmailCallback, set, setupAccount, shortBits, shortenURL, spinCheck, testCoffee, testToggle, threadScaffolding, toggleMonth, toggleQuote, toggleYear, txt, unshortenURL,
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty;
calendar_months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
/**
* Calendar: Make a HTML calendar with years and months
* that expands/contracts. For the left side view.
* Usage: cal = new Calendar('2001-2', '2016-9', 2010)
* Would make a calendar going from 2001 to 2016 with 2010 expanded.
*/
Calendar = (function() {
function Calendar(start, end, jumpTo) {
var div, eMonth, eYear, extra, j, jMonth, jYear, month, monthDiv, monthsDiv, now, o, ref, ref1, ref2, ref3, ref4, ref5, ref6, sMonth, sYear, uid, yDiv, year, years;
now = new Date();
uid = parseInt(Math.random() * 100000000).toString(16);
/* Split start and end into years and months */
ref = String(start).split("-"), sYear = ref[0], sMonth = ref[1];
ref1 = [now.getFullYear(), now.getMonth() + 1], eYear = ref1[0], eMonth = ref1[1];
ref2 = [0, 0], jYear = ref2[0], jMonth = ref2[1];
if (jumpTo) {
ref3 = String(jumpTo).split("-", 2), jYear = ref3[0], jMonth = ref3[1];
jYear = parseInt(jYear);
jMonth = parseInt(jMonth);
}
/* If end year+month given, use it */
if (end) {
ref4 = String(end).split("-"), eYear = ref4[0], eMonth = ref4[1];
/* If end year is this year, restrict months to those that have passed */
if (parseInt(eYear) === now.getFullYear()) {
eMonth = now.getMonth() + 1;
}
}
/* Make sure months are there, otherwise set them */
if (!sMonth) {
sMonth = 1;
}
if (!eMonth) {
eMonth = 12;
}
/* For each year, construct the year div to hold months */
years = [];
for (year = j = ref5 = parseInt(sYear), ref6 = parseInt(eYear); ref5 <= ref6 ? j <= ref6 : j >= ref6; year = ref5 <= ref6 ? ++j : --j) {
yDiv = new HTML('div', {
id: ("calendar_year_" + uid + "_") + year,
data: String(year),
"class": "calendar_year",
onclick: "toggleYear(this);"
}, String(year));
/* Construct the placeholder for months */
/* Hide unless active year */
monthsDiv = new HTML('div', {
"class": (jumpTo && jYear === year) || (!jumpTo && year === parseInt(eYear)) ? "calendar_months" : "calendar_months_hidden",
id: ("calendar_months_" + uid + "_") + year
});
/* For each month, make a div */
for (month = o = 12; o >= 1; month = --o) {
/* Make sure this is within the start<->end range */
if ((year > sYear || month >= sMonth) && (year < eYear || month <= eMonth)) {
extra = "";
if (jumpTo && jYear === year && jMonth === month) {
extra = "calendar_month_selected";
}
monthDiv = new HTML('div', {
"class": "calendar_month " + extra,
id: "calendar_month_" + uid + "_" + year + "-" + month,
data: year + "-" + month,
onclick: "toggleMonth(this)"
}, calendar_months[month - 1]);
monthsDiv.inject(monthDiv);
}
}
/* unshift year into the div list (thus reverse order) */
years.unshift(monthsDiv);
years.unshift(yDiv);
}
/* Return a combined div */
div = new HTML('div', {
"class": "calendar",
id: uid,
data: sYear + "-" + eYear
}, years);
return div;
}
return Calendar;
})();
toggleYear = function(div) {
/* Get the start and end year from the parent div */
var eYear, j, month, ref, ref1, ref2, ref3, results, sYear, uid, y, year;
ref = div.parentNode.getAttribute('data').split("-"), sYear = ref[0], eYear = ref[1];
/* Get the year we clicked on */
ref1 = div.getAttribute("data").split("-"), year = ref1[0], month = ref1[1];
year = parseInt(year);
month = parseInt(month);
uid = div.parentNode.getAttribute("id");
/* For each year, hide if not this year, else show */
results = [];
for (y = j = ref2 = parseInt(sYear), ref3 = parseInt(eYear); ref2 <= ref3 ? j <= ref3 : j >= ref3; y = ref2 <= ref3 ? ++j : --j) {
if (y === year) {
results.push(get("calendar_months_" + uid + "_" + y).setAttribute("class", "calendar_months"));
} else {
results.push(get("calendar_months_" + uid + "_" + y).setAttribute("class", "calendar_months calendar_months_hidden"));
}
}
return results;
};
toggleMonth = function(div) {
var m, month, ref, uid, year;
uid = div.parentNode.parentNode.getAttribute("id");
m = div.getAttribute("data");
ref = m.split("-"), year = ref[0], month = ref[1];
/* Update the list view using the new month */
return listView({
month: year + "-" + parseInt(month).pad(2)
});
};
/*
******************************************
Fetched from coffee/colors.coffee
******************************************
*/
hsl2rgb = function(h, s, l) {
var fract, min, sh, sv, switcher, v, vsf;
h = h % 1;
if (s > 1) {
s = 1;
}
if (l > 1) {
l = 1;
}
if (l <= 0.5) {
v = l * (1 + s);
} else {
v = l + s - l * s;
}
if (v === 0) {
return {
r: 0,
g: 0,
b: 0
};
}
min = 2 * l - v;
sv = (v - min) / v;
sh = (6 * h) % 6;
switcher = Math.floor(sh);
fract = sh - switcher;
vsf = v * sv * fract;
switch (switcher) {
case 0:
return {
r: v,
g: min + vsf,
b: min
};
case 1:
return {
r: v - vsf,
g: v,
b: min
};
case 2:
return {
r: min,
g: v,
b: min + vsf
};
case 3:
return {
r: min,
g: v - vsf,
b: v
};
case 4:
return {
r: min + vsf,
g: min,
b: v
};
case 5:
return {
r: v,
g: min,
b: v - vsf
};
}
return {
r: 0,
g: 0,
b: 0
};
};
genColors = function(numColors, saturation, lightness, hex) {
var baseHue, c, cls, h, i, j, ref;
cls = [];
baseHue = 1.34;
for (i = j = 1, ref = numColors; 1 <= ref ? j <= ref : j >= ref; i = 1 <= ref ? ++j : --j) {
c = hsl2rgb(baseHue, saturation, lightness);
if (hex) {
h = (Math.round(c.r * 255 * 255 * 255) + Math.round(c.g * 255 * 255) + Math.round(c.b * 255)).toString(16);
while (h.length < 6) {
h = '0' + h;
}
h = '#' + h;
cls.push(h);
} else {
cls.push({
r: parseInt(c.r * 255),
g: parseInt(c.g * 255),
b: parseInt(c.b * 255)
});
}
baseHue -= 0.23;
if (baseHue < 0) {
baseHue += 1;
}
}
return cls;
};
/*
******************************************
Fetched from coffee/defaults.coffee
******************************************
*/
/* Pony Mail defaults */
ponymail_version = "0.10 (Coffee and Cake)";
ponymail_lists = {};
ponymail_list = "";
ponymail_month = "";
ponymail_query = "";
ponymail_listname = "";
ponymail_domain = "";
ponymail_list_json = {};
ponymail_current_listview = null;
ponymail_email_open = [];
ponymail_current_email = null;
ponymail_stored_email = {};
ponymail_preferences = {};
/*
******************************************
Fetched from coffee/defaults_register.coffee
******************************************
*/
/* Various register functions */
ponymail_listview_models = {};
ponymail_display_models = {};
ponymail_register_listview = function(name, title, cl) {
return ponymail_listview_models[name] = {
title: title,
"class": cl
};
};
ponymail_register_display = function(name, title, cl) {
return ponymail_display_models[name] = {
title: title,
"class": cl
};
};
/*
******************************************
Fetched from coffee/dom_utils.coffee
******************************************
*/
/**
* HTML: DOM creator class
* args:
* - type: HTML element type (div, table, p etc) to produce
* - params: hash of element params to add (class, style etc)
* - children: optional child or children objects to insert into the new element
* Example:
* div = new HTML('div', {
* class: "footer",
* style: {
* fontWeight: "bold"
* }
#}, "Some text inside a div")
*/
HTML = (function() {
function HTML(type, params, children) {
/* create the raw element, or clone if passed an existing element */
var child, j, key, len, subkey, subval, val;
if (typeof type === 'object') {
this.element = type.cloneNode();
} else {
this.element = document.createElement(type);
}
/* If params have been passed, set them */
if (isHash(params)) {
for (key in params) {
val = params[key];
/* Standard string value? */
if (typeof val === "string" || typeof val === 'number') {
this.element.setAttribute(key, val);
} else if (isArray(val)) {
/* Are we passing a list of data to set? concatenate then */
this.element.setAttribute(key, val.join(" "));
} else if (isHash(val)) {
/* Are we trying to set multiple sub elements, like a style? */
for (subkey in val) {
subval = val[subkey];
if (!this.element[key]) {
throw "No such attribute, " + key + "!";
}
this.element[key][subkey] = subval;
}
}
}
}
/* If any children have been passed, add them to the element */
if (children) {
/* If string, convert to textNode using txt() */
if (typeof children === "string") {
this.element.inject(txt(children));
} else {
/* If children is an array of elems, iterate and add */
if (isArray(children)) {
for (j = 0, len = children.length; j < len; j++) {
child = children[j];
/* String? Convert via txt() then */
if (typeof child === "string") {
this.element.inject(txt(child));
} else {
/* Plain element, add normally */
this.element.inject(child);
}
}
} else {
/* Just a single element, add it */
this.element.inject(children);
}
}
}
return this.element;
}
return HTML;
})();
/* Set: shortcut for a.setAttribute(b,c) */
set = function(a, b, c) {
return a.setAttribute(b, c);
};
/* txt: shortcut for creating a text node */
txt = function(a) {
return document.createTextNode(a);
};
/* Get: Shortcut for doc.getElementById */
get = function(a) {
return document.getElementById(a);
};
/**
* prototype injector for HTML elements:
* Example: mydiv.inject(otherdiv)
*/
HTMLElement.prototype.inject = function(child) {
var item, j, len;
if (isArray(child)) {
for (j = 0, len = child.length; j < len; j++) {
item = child[j];
if (typeof item === 'string') {
item = txt(item);
}
this.appendChild(item);
}
} else {
if (typeof child === 'string') {
child = txt(child);
}
this.appendChild(child);
}
return child;
};
/**
* prototype show/hide function for HTML elements:
* If called with a bool, show if True, hide if False.
* If no bool, toggle show/hide based on current state.
*/
HTMLElement.prototype.show = function(bool) {
var b, d;
d = 'block';
if (typeof bool === 'undefined') {
d = this.style && this.style.display === 'none' ? 'block' : 'none';
} else if (bool === false) {
d = 'none';
} else if (bool === true) {
b = 'block';
}
this.style.display = d;
return d;
};
/**
* prototype for emptying an html element
*/
HTMLElement.prototype.empty = function() {
var ndiv;
ndiv = this.cloneNode();
this.parentNode.replaceChild(ndiv, this);
return ndiv;
};
/* Cog: Loading panel for when waiting for a response */
cog = function(div, size) {
var i, idiv;
if (size == null) {
size = 200;
}
idiv = mk('div', {
"class": "icon",
style: {
texAlign: 'center',
verticalAlign: 'middle',
height: '500px'
}
});
i = mk('i', {
"class": 'fa fa-spin fa-cog',
style: {
fontSize: size + 'pt !important',
color: '#AAB'
}
});
idiv.inject([i, mk('br'), "Loading data, please wait..."]);
div.innerHTML = "";
return div.appendChild(idiv);
};
/*
******************************************
Fetched from coffee/email_display_basic.coffee
******************************************
*/
/* readMail: figure out how to display an email/thread */
readEmail = function(obj) {
/* find the original email ID and point of origin */
var closedOne, email, index, j, len, mid, parent;
mid = null;
parent = null;
if (typeof obj === 'string') {
mid = obj;
parent = document.body;
} else if (typeof obj === 'object') {
mid = obj.getAttribute("data");
parent = obj;
}
/* We good to go? */
if ((!mid) || (!parent)) {
alert("Couldn't find the email or insertion point!");
return;
}
/* First check if the MID is already open
* If so, close it instead
*/
closedOne = false;
for (j = 0, len = ponymail_email_open.length; j < len; j++) {
email = ponymail_email_open[j];
if (mid === email.mid) {
email.hide();
closedOne = true;
break;
}
}
if (!closedOne) {
/* Get thread index value if set, for threads */
index = parent.getAttribute("data-index");
/* We have an(other) email open now */
ponymail_current_email = new ThreadedEmailDisplay(parent, mid, index);
return ponymail_email_open.push(ponymail_current_email);
}
};
/* Basic email display class */
BasicEmailDisplay = (function() {
function BasicEmailDisplay(parent1, mid1) {
var me, r;
this.parent = parent1;
this.mid = mid1;
this.placeholder = get("placeholder_" + this.mid) || new HTML('div', {
"class": "email_placeholder",
id: "placeholder_" + this.mid
});
/* Inject into listview or body */
this.parent.inject(this.placeholder);
/* Make sure it's empty, may have been used before! */
this.placeholder = this.placeholder.empty();
this.placeholder.show(true);
me = this;
/* Do we have this email in cache? */
if (ponymail_stored_email[this.mid]) {
this.render(ponymail_stored_email[this.mid]);
} else {
/* Not stored, fetch the email first */
r = new HTTPRequest("api/email.lua?", {
get: {
id: this.mid
},
callback: function(json, state) {
return me.render(json, state);
}
});
}
}
BasicEmailDisplay.prototype.render = function(json, state) {
/* Store email in cache if not there already */
var at, att_line, b, buttons, date_line, file, from_line, fsize, headers, htmlbody, j, len, link, list_line, pbutton, placeholder, rbutton, ref, sbutton, shortID, subject_line;
if (!ponymail_stored_email[json.mid]) {
ponymail_stored_email[json.mid] = json;
}
/* Mark as read */
markRead(json.mid);
placeholder = get('placeholder_' + this.mid + "_" + json.mid) || get('placeholder_' + json.mid);
/* Display email headers */
headers = new HTML('div', {
"class": "email_header"
});
from_line = new HTML('div', {}, [
new HTML('div', {
"class": "header_key"
}, "From: "), new HTML('div', {
"class": "header_value"
}, json.from)
]);
headers.inject(from_line);
subject_line = new HTML('div', {}, [
new HTML('div', {
"class": "header_key"
}, "Subject: "), new HTML('div', {
"class": "header_value"
}, json.subject)
]);
headers.inject(subject_line);
date_line = new HTML('div', {}, [
new HTML('div', {
"class": "header_key"
}, "Date: "), new HTML('div', {
"class": "header_value"
}, new Date(json.epoch * 1000).ISOBare())
]);
headers.inject(date_line);
/* <a.b.c> -> a@b.c */
this.list = json.list_raw.replace(/<([^.]+)\.(.+)>/, (function(_this) {
return function(a, b, c) {
return b + "@" + c;
};
})(this));
list_line = new HTML('div', {}, [
new HTML('div', {
"class": "header_key"
}, "List: "), new HTML('div', {
"class": "header_value"
}, new HTML('a', {
href: "list.html?" + this.list
}, this.list))
]);
headers.inject(list_line);
/* Attachments, if any */
if (isArray(json.attachments) && json.attachments.length > 0) {
at = [];
ref = json.attachments;
for (j = 0, len = ref.length; j < len; j++) {
file = ref[j];
fsize = file.size;
/* Compact size to MB, KB or bytes */
if (fsize > (1024 * 1024)) {
fsize = (fsize / (1024 * 1024)).toFixed(2) + "MB";
} else if (fsize > 1024) {
fsize = (fsize / 1024.).toFixed(2) + "KB";
} else {
fsize = fsize + " bytes";
}
/* Make a link with the filename and size */
link = new HTML('a', {
href: "api/email.lua?attachment=true&file=" + file.hash + "&id=" + json.mid,
style: {
marginRight: "8px"
}
}, file.filename + " (" + fsize + ")");
at.push(link);
}
att_line = new HTML('div', {}, [
new HTML('div', {
"class": "header_key"
}, "Attachments: "), new HTML('div', {
"class": "header_value"
}, at)
]);
headers.inject(att_line);
}
/* Action buttons */
/* Permalink */
shortID = shortenURL(json.mid);
pbutton = new HTML('a', {
"class": "label_yellow",
href: "thread.html/" + shortID
}, "Permalink");
/* Source */
sbutton = new HTML('a', {
"class": "label_red",
href: "api/source.lua/" + json.mid
}, "View source");
/* Reply */
rbutton = new HTML('a', {
"class": "label_green",
href: "javascript:void(0);"
}, "Reply");
buttons = new HTML('div', {
"class": "email_header_buttons"
}, [pbutton, sbutton, rbutton]);
headers.inject(buttons);
placeholder.inject(headers);
/* parse body, convert quotes */
htmlbody = this.quotify(json.body);
/* Now inject the body */
b = new HTML('pre', {
"class": "email_body"
}, htmlbody);
return placeholder.inject(b);
};
/* quotify: put quotes inside quote blocks */
BasicEmailDisplay.prototype.quotify = function(splicer) {
var hideQuotes, i, m, qdiv, quote, quotes, t, textbits;
hideQuotes = true;
if (ponymail_preferences['hideQuotes'] && ponymail_preferences['hideQuotes'] === false) {
hideQuotes = false;
}
/* Array holding text and quotes */
textbits = [];
/* Find the first quote, if any */
i = splicer.search(ponymail_quote_regex);
quotes = 0;
/* While we have more links, ... */
while (i !== -1) {
quotes++;
/* Only parse the first 50 quotes... srsly */
if (quotes > 50) {
break;
}
/* Text preceding the quote? add it to textbits first */
if (i > 0) {
t = splicer.substr(0, i);
textbits.push(this.URLify(t));
splicer = splicer.substr(i);
}
/* Find the quote and cut it out as a div */
m = splicer.match(ponymail_quote_regex);
if (m) {
quote = m[1];
i = quote.length;
t = splicer.substr(0, i);
quote = quote.replace(/(>*\s*\r?\n)+$/g, "");
qdiv = new HTML('div', {
"class": "email_quote_parent"
}, [
new HTML('img', {
src: 'images/quote.png',
width: "24",
height: "26",
title: "Toggle quote",
onclick: "toggleQuote(this)"
}), new HTML('br'), new HTML('blockquote', {
"class": "email_quote",
style: {
display: hideQuotes ? 'none' : 'block'
}
}, this.URLify(quote))
]);
textbits.push(qdiv);
splicer = splicer.substr(i);
}
/* Find the next link */
i = splicer.search(ponymail_quote_regex);
}
/* push the remaining text into textbits */
textbits.push(this.URLify(splicer));
return textbits;
};
/* URLify: find links and HTML'ify them */
BasicEmailDisplay.prototype.URLify = function(splicer) {
/* Array holding text and links */
var i, m, t, textbits, url, urls;
textbits = [];
/* Find the first link, if any */
i = splicer.search(ponymail_url_regex);
urls = 0;
/* While we have more links, ... */
while (i !== -1) {
urls++;
/* Only parse the first 50 URLs... srsly */
if (urls > 50) {
break;
}
/* Text preceding the link? add it to textbits frst */
if (i > 0) {
t = splicer.substr(0, i);
textbits.push(t);
splicer = splicer.substr(i);
}
/* Find the URL and cut it out as a link */
m = splicer.match(ponymail_url_regex);
if (m) {
url = m[1];
i = url.length;
t = splicer.substr(0, i);
textbits.push(new HTML('a', {
href: url
}, url));
splicer = splicer.substr(i);
}
/* Find the next link */
i = splicer.search(ponymail_url_regex);
}
/* push the remaining text into textbits */
textbits.push(splicer);
return textbits;
};
BasicEmailDisplay.prototype.hide = function() {
this.placeholder.show(false);
ponymail_email_open.remove(this);
return ponymail_current_email = null;
};
return BasicEmailDisplay;
})();
ponymail_register_display('default', "Single email view", BasicEmailDisplay);
/* toggleQuote: show/hide a quote */
toggleQuote = function(div) {
return div.parentNode.childNodes[2].show();
};
/*
******************************************
Fetched from coffee/email_display_bydate.coffee
******************************************
*/
/* date-sorted multi email display class - extends BasicEmail Display */
DateEmailDisplay = (function(superClass) {
extend(DateEmailDisplay, superClass);
function DateEmailDisplay(parent1, mid1, index) {
var email, emails, item, j, len, len1, me, o, ref, thread;
this.parent = parent1;
this.mid = mid1;
this.placeholder = get("placeholder_" + this.mid) || new HTML('div', {
"class": "email_placeholder",
id: "placeholder_" + this.mid
});
/* Inject into listview or body */
this.parent.inject(this.placeholder);
/* Make sure it's empty, may have been used before! */
this.placeholder = this.placeholder.empty();
this.placeholder.show(true);
me = this;
/* Find the thread or fake one */
thread = {
tid: this.mid
};
if (index && ponymail_current_listview && ponymail_current_listview.json.thread_struct[index]) {
thread = ponymail_current_listview.json.thread_struct[index];
}
emails = [[this.mid, 0]];
ref = this.dateSort(thread);
for (j = 0, len = ref.length; j < len; j++) {
item = ref[j];
emails.push(item);
}
for (o = 0, len1 = emails.length; o < len1; o++) {
email = emails[o];
this.dateFetch(this.placeholder, email[0]);
}
return this;
}
DateEmailDisplay.prototype.dateSort = function(thread) {
var citem, item, j, len, len1, list, o, ref, ref1;
list = [];
if (thread.children && isArray(thread.children)) {
ref = thread.children;
for (j = 0, len = ref.length; j < len; j++) {
item = ref[j];
list.push([item.tid, item.epoch]);
ref1 = this.dateSort(item);
for (o = 0, len1 = ref1.length; o < len1; o++) {
citem = ref1[o];
list.push(citem);
}
}
}
list.sort((function(_this) {
return function(a, b) {
return a[1] > b[1];
};
})(this));
return list;
};
DateEmailDisplay.prototype.dateFetch = function(parent, thread) {
/* Make the thread item placeholder */
var bcolor, bcolors, bodyplace, me, place, r, replyplace;
bodyplace = new HTML('div', {
id: "placeholder_" + this.mid + "_" + thread,
"class": "email_boxed"
});
/* Assign a random color to the left */
this.prevColor = this.prevColor || "";
bcolors = ['#C93F20', '#20C94A', '#2063C9', '#C9AA20', '#AD20C9', '#99C920', '#20C9C3'];
bcolor = bcolors[Math.round(Math.random() * bcolors.length)];
/* ensure we don't get the same color twice in a row */
while (bcolor === this.prevColor) {
bcolor = bcolors[Math.round(Math.random() * bcolors.length)];
}
this.prevColor = bcolor;
bodyplace.style.borderLeft = "4px solid " + bcolor;
replyplace = new HTML('div', {
id: "thread_replies_" + this.mid + "_" + thread,
style: {
marginLeft: "20px"
}
});
place = new HTML('div', {
id: "thread_parent_" + this.mid + "_" + thread,
style: {
float: "left",
width: "100%"
}
}, [bodyplace, replyplace]);
parent.inject(place);
/* Do we have this email in cache? */
if (ponymail_stored_email[thread]) {
return this.render(ponymail_stored_email[thread]);
} else {
me = this;
/* Not stored, fetch the email first */
return r = new HTTPRequest("api/email.lua?", {
get: {
id: thread
},
callback: function(json, state) {
return me.render(json, state);
}
});
}
};
return DateEmailDisplay;
})(BasicEmailDisplay);
ponymail_register_display('date', "Stacked view", DateEmailDisplay);
/*
******************************************
Fetched from coffee/email_display_threaded.coffee
******************************************
*/
/* threaded email display class - extends BasicEmail Display */
ThreadedEmailDisplay = (function(superClass) {
extend(ThreadedEmailDisplay, superClass);
function ThreadedEmailDisplay(parent1, mid1, index, tjson) {
var me, thread;
this.parent = parent1;
this.mid = mid1;
if (tjson == null) {
tjson = null;
}
this.placeholder = get("placeholder_" + this.mid) || new HTML('div', {
"class": "email_placeholder",
id: "placeholder_" + this.mid
});
this.shown = {};
me = this;
/* Find the thread or fake one */
thread = {
tid: this.mid
};
if (tjson) {
thread = tjson;
this.mid = tjson.mid;
this.parent = get('email_placeholder');
} else if (index && ponymail_current_listview && ponymail_current_listview.json.thread_struct[index]) {
thread = ponymail_current_listview.json.thread_struct[index];
}
/* Inject into listview or body */
this.parent.inject(this.placeholder);
/* Make sure it's empty, may have been used before! */
this.placeholder = this.placeholder.empty();
this.placeholder.show(true);
this.threadedFetch(this.placeholder, thread, 1);
return this;
}
ThreadedEmailDisplay.prototype.threadedFetch = function(parent, thread, nestedness) {
/* First off, we don't want duplicates due to whatever bug, so bug out if we've already rendered this email */
var bcolor, bcolors, bodyplace, item, j, len, me, place, r, ref, replyplace;
if (this.shown[thread.tid]) {
return;
}
this.shown[thread.tid] = true;
/* Make the thread item placeholder */
bodyplace = new HTML('div', {
id: "placeholder_" + this.mid + "_" + thread.tid,
"class": "email_boxed"
});
/* Assign a random color to the left */
this.prevColor = this.prevColor || "";
bcolors = ['#C93F20', '#20C94A', '#2063C9', '#C9AA20', '#AD20C9', '#99C920', '#20C9C3'];
bcolor = bcolors[Math.round(Math.random() * bcolors.length)];
/* ensure we don't get the same color twice in a row */
while (bcolor === this.prevColor) {
bcolor = bcolors[Math.round(Math.random() * bcolors.length)];
}
this.prevColor = bcolor;
bodyplace.style.borderLeft = "4px solid " + bcolor;
replyplace = new HTML('div', {
id: "thread_replies_" + this.mid + "_" + thread.tid,
style: {
marginLeft: "20px"
}
});
place = new HTML('div', {
id: "thread_parent_" + this.mid + "_" + thread.tid,
style: {
float: "left",
width: "100%"
}
}, [bodyplace, replyplace]);
parent.inject(place);
/* Do we have this email in cache? */
if (ponymail_stored_email[thread.tid]) {
this.render(ponymail_stored_email[thread.tid]);
} else {
me = this;
/* Not stored, fetch the email first */
r = new HTTPRequest("api/email.lua?", {
get: {
id: thread.tid
},
callback: function(json, state) {
return me.render(json, state);
},
state: {
nest: Math.min(nestedness + 1, 5)
}
});
}
/* Now do the same for each child item */
if (thread.children && isArray(thread.children) && thread.children.length > 0) {
ref = thread.children;
for (j = 0, len = ref.length; j < len; j++) {
item = ref[j];
this.threadedFetch(replyplace, item, Math.min(nestedness + 1, 5));
}
}
return this;
};
return ThreadedEmailDisplay;
})(BasicEmailDisplay);
ponymail_register_display('threaded', "Threaded view", ThreadedEmailDisplay);
/*
******************************************
Fetched from coffee/http_utils.coffee
******************************************
*/
/**
* Pending URLs watcher:
* Watches which URLs have been pending a result for a while
* and shows the spinner if things are taking too long.
*/
pending_url_operations = {};
pending_spinner_at = 0;
spinCheck = function(div, reset) {
var ndiv, spnow;
if (div.style.display === "block") {
spnow = new Date().getTime();
if (reset || (spnow - pending_spinner_at) >= 4000) {
pending_spinner_at = spnow;
ndiv = div.cloneNode(true);
return div.parentNode.replaceChild(ndiv, div);
}
} else {
return pending_spinner_at = 0;
}
};
pendingURLStatus = function() {
var div, pending, spnow, time, url;
pending = 0;
spnow = new Date().getTime();
div = get('loading');
for (url in pending_url_operations) {
time = pending_url_operations[url];
/* Is something taking too long?? */
if ((spnow - time) > 1500) {
pending++;
if (!div) {
div = new HTML('div', {
id: 'loading',
"class": "spinner"
}, [
new HTML('div', {
"class": "spinwheel"
}, new HTML('div', {
"class": "spinwheel_md"
}, new HTML('div', {
"class": "spinwheel_sm"
}))), new HTML('br'), "Loading, please wait..."
]);
document.body.inject(div);
pending_spinner_at = spnow;
div.addEventListener('animationend', function(e) {
return spinCheck(div);
});
}
}
}
/* If no pending operations, hide the spnner */
if (pending === 0) {
div = get('loading');
if (div) {
return div.style.display = "none";
}
} else if (div && div.style.display === "none") {
div.style.display = "block";
if (pending_spinner_at === 0) {
pending_spinner_at = spnow;
return spinCheck(div, true);
}
}
};
window.setInterval(pendingURLStatus, 500);
/**
* HTTPRequest: Fire off a HTTP request.
* Args:
* - url: The URL to request (may be relative or absolute)
* - args:
* - - state: A callback stateful object
* - - data: Any form/JSON data to send along if POST (method is derived
* from whether data is attached or not)
* - - getdata: Any form vars to append to the URL as URI-encoded formdata
* - - datatype: 'form' or 'json' data?
* - - callback: function to call when request has returned a response
* - - snap: snap function in case of internal server error or similar
* - - nocreds: don't pass on cookies?
* Example POST request:
* HTTPRequest("/api/foo.lua", {
* state: {
* ponies: true
* },
* callback: foofunc,
* data: {
* list: "foo.bar"
* }
* })
*/
HTTPRequest = (function() {
function HTTPRequest(url1, args1) {
var r, tmp;
this.url = url1;
this.args = args1;
/* Set internal class data, determine request type */
this.state = this.args.state;
this.method = this.args.data ? 'POST' : 'GET';
this.data = this.args.data;
this.getdata = this.args.get;
this.datatype = this.args.datatype || 'form';
this.callback = this.args.callback;
this.snap = this.args.snap || pm_snap;
this.nocreds = this.args.nocreds || false;
this.uid = parseInt(Math.random() * 10000000).toString(16);
/* Construct request object */
if (window.XMLHttpRequest) {
this.request = new XMLHttpRequest();
} else {
this.request = new ActiveXObject("Microsoft.XMLHTTP");
}
/* Default to sending credentials */
if (!this.nocreds) {
this.request.withCredentials = true;
}
/* Determine what to send as data (if anything) */
this.rdata = null;
if (this.method === 'POST') {
if (this.datatype === 'json') {
this.rdata = JSON.stringify(this.data);
} else {
this.rdata = this.formdata(this.data);
}
}
/* If tasked with appending data to the URL, do so */
if (isHash(this.getdata)) {
tmp = this.formdata(this.getdata);
if (tmp.length > 0) {
/* Do we have form data here aleady? if so, append the new */
/* by adding an ampersand first */
if (this.url.match(/\?/)) {
this.url += "&" + tmp;
} else {
this.url += "?" + tmp;
}
}
}
/* Mark operation as pending result */
pending_url_operations[this.uid] = new Date().getTime();
/* Use @method on URL */
this.request.open(this.method, this.url, true);
/* Send data */
this.request.send(this.rdata);
/* Set onChange behavior */
r = this;
this.request.onreadystatechange = function() {
return r.onchange();
};
}
HTTPRequest.prototype.onchange = function() {
/* Mark operation as done */
var e;
if (this.request.readyState === 4) {
delete pending_url_operations[this.uid];
}
/* Internal Server Error: Try to call snap */
if (this.request.readyState === 4 && this.request.status === 500) {
if (this.snap) {
this.snap(this.state);
}
}
/* 200 OK, everything is okay, try to parse JSON response */
if (this.request.readyState === 4 && this.request.status === 200) {
if (this.callback) {
/* Try to parse as JSON and deal with cache objects, fall back to old style parse-and-pass */
try {
/* Parse JSON response */
this.response = JSON.parse(this.request.responseText);
/* If loginRequired (rare!), redirect to oauth page */
if (this.response && this.response.loginRequired) {
location.href = "/oauth.html";
return;
}
/* Otherwise, call the callback function */
return this.callback(this.response, this.state);
} catch (_error) {
e = _error;
console.log("Callback failed: " + e);
return this.callback(JSON.parse(this.request.responseText), this.state);
}
}
}
};
/* Standard form data joiner for POST data */
HTTPRequest.prototype.formdata = function(kv) {
var ar, k, v;
ar = [];
/* For each key/value pair (assuming this is a hash) */
if (isHash(kv)) {
for (k in kv) {
v = kv[k];
/* Only append if the value is non-empty */
if (v && v !== "") {
/* URI-Encode value and add to an array */
ar.push(k + "=" + encodeURIComponent(v));
}
}
}
/* Join the array with ampersands, so we get "foo=bar&foo2=baz" */
return ar.join("&");
};
return HTTPRequest;
})();
pm_snap = null;
/*
******************************************
Fetched from coffee/keyboard_shortcuts.coffee
******************************************
*/
/* dealWithKeyboard: Handles what happens when you hit the escape key */
dealWithKeyboard = function(e) {
var dp, splash;
splash = get('splash');
/* escape key: hide composer/settings/thread */
if (e.keyCode === 27) {
if (splash && splash.style.display === 'block') {
splash.style.display = "none";
} else if (location.href.search(/list\d?\.html/) !== -1) {
/* should only work for the list view */
/* If datepicker popup is shown, hide it on escape */
dp = get('datepicker_popup');
if (dp && dp.style.display === "block") {
dp.show(false);
} else if (ponymail_email_open.length > 0) {
/* Close the currently open email? */
if (ponymail_current_email) {
ponymail_current_email.hide();
} else {
/* Close all email ? */
while (ponymail_email_open.length > 0) {
ponymail_email_open[0].hide();
}
}
}
}
}
/* Make sure the below shortcuts don't interfere with normal operations */
if (splash && splash.style.display !== 'block' && document.activeElement.nodeName !== 'INPUT' && !e.ctrlKey) {
/* H key: show help */
if (e.keyCode === 72) {
popup("Keyboard shortcuts", "<pre><b>H:</b>Show this help menu<br/><b>C:</b>Compose a new email to the current list<br/><b>R:</b>Reply to the last opened email<br/><b>S:</b>Go to the quick search bar<br/><b>Esc:</b>Hide/collapse current email or thread<br/></pre>You can also, in some cases, use the mouse wheel to scroll up/down the list view", 10);
} else if (e.keyCode === 67) {
/* C key: compose */
compose(null, ponymail_list, 'new');
} else if (e.keyCode === 82) {
/* R key: reply */
if (ponymail_current_email && ponymail_email_open.length > 0) {
compose(last_opened_email, null, 'reply');
}
} else if (e.keyCode === 83) {
/* S key: quick search */
if (get('q')) {
get('q').focus();
}
}
}
/* Page Up - scroll list view if possible */
if (e.keyCode === 33 && ponymail_current_listview) {
ponymail_current_listview.swipe('up');
}
/* Page Down - scroll list view if possible */
if (e.keyCode === 34 && ponymail_current_listview) {
return ponymail_current_listview.swipe('down');
}
};
/* Add listener for keys (mostly for escape key for hiding stuff) */
window.addEventListener("keyup", dealWithKeyboard, false);
/*
******************************************
Fetched from coffee/listview_basic.coffee
******************************************
*/
/**
* Basic listview class, to be extended by other designs
*/
BasicListView = (function() {
/* json: from stats.lua, rpp = results per page, pos = starting position (from 0) */
function BasicListView(json1, rpp1, pos1) {
var date, hd, m, ref, y;
this.json = json1;
this.rpp = rpp1 != null ? rpp1 : 0;
this.pos = pos1 != null ? pos1 : 0;
/* @rpp == 0 == auto-compute num of items */
if (this.rpp === 0) {
this.rpp = Math.max(parseInt((window.innerHeight - 300) / 40), 5);
this.rpp = this.rpp - (this.rpp % 5);
}
/* Set the header first */
hd = get('header');
if (this.json.list) {
if (ponymail_month.length > 0) {
ref = ponymail_month.split("-", 2), y = ref[0], m = ref[1];
date = calendar_months[parseInt(m) - 1] + (", " + y);
hd.empty().inject([
this.json.list + " (" + date + "):", new HTML('a', {
href: "api/mbox.lua?list=" + ponymail_list + "&date=" + ponymail_month,
title: "Download as mbox archive"
}, new HTML('img', {
src: 'images/floppy.svg',
style: {
marginLeft: "10px",
width: "20px",
height: "20px",
verticalAlign: 'middle'
}
}))
]);
} else {
hd.empty().inject(this.json.list + ", past 30 days:");
}
}
/* Get and clear the list view */
this.lv = get('listview');
this.lv = this.lv.empty();
/* Set some internal vars */
this.listsize = 0;
/* If we got results, use scroll() to display from result 0 on */
if (isArray(this.json.thread_struct) && this.json.thread_struct.length > 0) {
/* Set some internal vars */
this.listsize = this.json.thread_struct.length;
/* Reverse thread struct, but only if we're not using an
* already reversed cache
*/
if (!this.json.cached) {
this.json.thread_struct.reverse();
}
this.scroll(this.rpp, this.pos);
} else {
/* No results, just say...that */
this.lv.inject("No emails found matching this criterion.");
}
ponymail_current_listview = this;
return this;
}
/* scroll: scroll to a position and show N emails/threads */
BasicListView.prototype.scroll = function(rpp, pos) {
var bj, dStat, diff, f, l, lastitem, nbutton, nno, now, np, pbutton, pno, pp, tmpthis, topButtons;
this.lastScroll = new Date().getTime();
/* Clear the list view */
this.lv = this.lv.empty();
topButtons = null;
this.rpp = rpp;
this.pos = pos;
/* Show how many threads out of how many we are showing */
f = pos + 1;
l = Math.min(this.listsize, pos + rpp);
dStat = new HTML('div', {
style: {
float: "left",
width: "100%",
fontSize: "80%",
textAlign: "center"
}
}, "Showing items " + f + " through " + l + " out of " + this.listsize + " results.");
this.lv.inject(dStat);
/* First, build the prev/next buttons if needed */
if (pos > 0 || (pos + rpp) < this.listsize) {
topButtons = new HTML('div', {
style: {
float: "left",
width: "100%"
}
});
if (pos > 0) {
pno = Math.min(rpp, pos);
pp = Math.max(0, pos - rpp);
pbutton = new HTML('input', {
type: 'button',
value: 'Previous ' + pno + " message" + (pno === 1 ? '' : 's'),
onclick: "ponymail_current_listview.scroll(" + rpp + ", " + pp + ");",
"class": "listview_button_green",
style: {
float: "left"
}
});
topButtons.inject(pbutton);
}
/* Next button */
if ((pos + rpp) < this.listsize) {
nno = Math.min(rpp, this.listsize - pos - rpp);
np = pos + rpp;
nbutton = new HTML('input', {
type: 'button',
value: 'Next ' + nno + " message" + (nno === 1 ? '' : 's'),
onclick: "ponymail_current_listview.scroll(" + rpp + ", " + np + ");",
"class": "listview_button_green",
style: {
float: "right"
}
});
topButtons.inject(nbutton);
}
this.lv.inject(topButtons);
}
lastitem = this.renderItems();
if (lastitem) {
bj = lastitem.getBoundingClientRect();
this.lvitems.style.minHeight = (this.rpp * (bj.height + 1)) + "px";
}
/* If we made buttons, clone them at the bottom */
if (topButtons) {
this.lv.inject(topButtons.cloneNode(true));
}
now = new Date().getTime();
diff = now - this.lastScroll;
if (this.json.cached) {
this.lv.inject("Fetched from cache (no updates detected), rendered in " + parseInt(diff) + "ms.");
} else {
this.lv.inject("Fetched in " + parseInt(this.json.took / 1000) + "ms, rendered in " + parseInt(diff) + "ms.");
}
tmpthis = this;
/* Finally, enable scrolling */
this.lv.addEventListener("mousewheel", function(e) {
return tmpthis.swipe(e);
}, false);
return this.lv.addEventListener("DOMMouseScroll", function(e) {
return tmpthis.swipe(e);
}, false);
};
/* renderItems: render the list view emails/theads */
BasicListView.prototype.renderItems = function() {
/* For each email result,... */
var i, item, j, lastitem, len, lvitem, original, ref;
this.lvitems = new HTML('div', {
"class": "listview_table"
});
lastitem = null;
ref = this.json.thread_struct.slice(this.pos, this.pos + this.rpp);
for (i = j = 0, len = ref.length; j < len; i = ++j) {
item = ref[i];
original = this.findEmail(item.tid);
/* Be sure we actually have an email here */
if (original) {
/* Call listViewItem to compile a list view HTML element */
lvitem = this.listViewItem(original, item, i + this.pos);
lastitem = lvitem;
/* Inject new item into the list view */
this.lvitems.inject(lvitem);
}
}
this.lv.inject(this.lvitems);
return lastitem;
};
/* findEmail: find an email given an ID */
BasicListView.prototype.findEmail = function(id) {
var email, j, len, ref;
ref = this.json.emails;
for (j = 0, len = ref.length; j < len; j++) {
email = ref[j];
if (email.id === id) {
return email;
}
}
return null;
};
/* countEmail: func for counting how many emails are in a thread */
BasicListView.prototype.countEmail = function(thread) {
var item, j, len, nc, ref;
nc = 0;
if (isArray(thread.children)) {
ref = thread.children;
for (j = 0, len = ref.length; j < len; j++) {
item = ref[j];
nc++;
if (item.children && isArray(item.children) && item.children.length > 0) {
nc += this.countEmail(item);
}
}
}
return nc;
};
/* countPeople: func for counting how many people are in a thread */
BasicListView.prototype.countPeople = function(thread, p) {
var email, eml, item, j, k, len, n, np, ref, t, v;
np = p || {};
n = 0;
if (thread.tid) {
eml = this.findEmail(thread.tid);
if (eml) {
np[eml.from] = true;
ref = (isArray(thread.children) ? thread.children : []);
for (j = 0, len = ref.length; j < len; j++) {
item = ref[j];
t = item.tid;
email = this.findEmail(t);
if (email) {
np[email.from] = true;
}
if (isArray(item.children) && item.children.length > 0) {
np = this.countPeople(item.children, np);
}
}
}
}
if (p) {
return np;
} else {
for (k in np) {
v = np[k];
n++;
}
return n;
}
};
BasicListView.prototype.listViewItem = function(original, thread, index) {
/* Be sure we actually have an email here */
var avatar, date, date_style, envelopeimg, item, noeml, now, people, peopleimg, readStyle, sender, sid, stats, subject, uid;
if (original && thread) {
now = new Date().getTime() / 1000;
/* Gather stats */
people = this.countPeople(thread);
noeml = this.countEmail(thread);
/* Render the email in the LV */
/* First set some data points for later */
uid = parseInt(Math.random() * 999999999999).toString(16);
/* Gravatar */
avatar = new HTML('img', {
"class": "gravatar",
src: "https://secure.gravatar.com/avatar/" + original.gravatar + ".png?s=24&r=g&d=mm"
});
/* Make sure subject and author is...something */
if (original.subject.length === 0) {
original.subject = "(No subject)";
}
if (original.from.length === 0) {
original.from = "(No author?)";
}
/* Sender, without the <foo@bar> part - just the name */
sender = new HTML('div', {
style: {
fontWeight: "bold"
}
}, original.from.replace(/\s*<.+>/, "").replace(/"/g, ''));
/* readStyle: bold if new email, normal if read before */
readStyle = "bold";
if (hasRead(thread.tid)) {
readStyle = "normal";
}
/* Subject, PLUS a bit of the body with a break before */
sid = shortenURL(thread.tid);
subject = new HTML('div', {}, [
new HTML('a', {
style: {
fontWeight: readStyle
},
href: "thread.html/" + sid,
onclick: "readEmail(this.parentNode.parentNode.parentNode); this.style.fontWeight = 'normal'; return false;"
}, original.subject), new HTML('br'), new HTML('span', {
"class": "listview_item_body"
}, thread.body)
]);
/* show number of replies and participants */
peopleimg = new HTML('img', {
src: 'images/avatar.png',
style: {
verticalAlign: 'middle',
width: "12px",
height: "12px"
}
});
envelopeimg = new HTML('img', {
src: 'images/envelope.png',
style: {
verticalAlign: 'middle',
width: "16px",
height: "12px"
}
});
stats = new HTML('div', {
"class": "listview_right"
}, [peopleimg, " " + people + " ", envelopeimg, " " + noeml]);
/* Add date; yellow if <= 1day, grey otherwise */
date_style = "listview_grey";
if ((now - 86400 * 4) < thread.epoch) {
date_style = "listview_yellow";
}
date = new HTML('div', {
"class": "listview_right " + date_style
}, new Date(thread.epoch * 1000).ISOBare());
/* Finally, pull it all together in a div and add that to the listview */
item = new HTML('div', {
id: uid,
data: thread.tid,
'data-index': index,
"class": "listview_item"
}, new HTML('div', {
"class": "listview_summary"
}, [avatar, sender, subject, date, stats]));
return item;
}
};
/* swipe: go to next or previous page of emails, depending on mouse wheel direction */
BasicListView.prototype.swipe = function(e) {
var direction, now, obj, scrollBar, style;
this.lastSwipe = this.lastSwipe || 0;
direction = "";
if (typeof e === 'string') {
direction = e;
} else {
direction = (e.wheelDelta || -e.detail) < 0 ? 'down' : 'up';
}
style = document.body.currentStyle || window.getComputedStyle(document.body, "");
/* Use the footer to determine whether scrollbar is present or not */
obj = get('footer').getBoundingClientRect();
scrollBar = window.innerHeight < obj.bottom;
/* Abort swiping if an email is open or scrollbar is present */
if (ponymail_email_open.length > 0 || scrollBar) {
return;
}
/* Make sure we don't swipe too fast! */
now = new Date().getTime();
if (now - this.lastSwipe < 300) {
return;
}
this.lastSwipe = now;
if (direction === 'down') {
/* Next page? */
if (this.listsize > (this.pos + this.rpp + 1)) {
return this.scroll(this.rpp, this.pos + this.rpp);
}
} else if (direction === 'up') {
/* Previous page? */
if (this.pos > 0) {
return this.scroll(this.rpp, Math.max(0, this.pos - this.rpp));
}
}
};
return BasicListView;
})();
ponymail_register_listview('default', 'Compact (threaded) theme', BasicListView);
/*
******************************************
Fetched from coffee/listview_basiclib.coffee
******************************************
*/
/**
* This is the listview basic library
*/
/* Generally, popping a window state should run a listView update */
window.onpopstate = function(event) {
return listView(null, true);
};
parseURL = function() {
var list, month, query, ref, ref1;
ref = window.location.search.substr(1).split(":", 3), list = ref[0], month = ref[1], query = ref[2];
ponymail_list = list;
ponymail_month = month || "";
ponymail_query = query || "";
return ref1 = list.split("@"), ponymail_listname = ref1[0], ponymail_domain = ref1[1], ref1;
};
listView = function(hash, reParse) {
/* Get the HTML filename */
var args, d, domain, etc, htmlfile, k, l, list, max, newhref, pargs, r, ref, ref1, ref2, ref3, since, v;
ref = location.href.split("?"), htmlfile = ref[0], etc = ref[1];
/* Do we need to call the URL parser here? */
if (reParse) {
parseURL();
}
/* Any new settings passed along? */
if (isHash(hash)) {
if (typeof hash.month !== 'undefined') {
ponymail_month = hash.month;
}
if (typeof hash.list !== 'undefined') {
ponymail_list = hash.list;
}
if (typeof hash.query !== 'undefined') {
ponymail_query = hash.query;
}
}
/* First, check that we have a list to view! */
if (!(ponymail_list && ponymail_list.match(/.+?@.+/))) {
/* Do we at least have a domain part? */
if (ponymail_list && ponymail_list.match(/.+?\..+/)) {
/* Check if there's a $default list in this domain */
ref1 = ponymail_list.split("@", 2), l = ref1[0], d = ref1[1];
if (!d) {
d = l;
}
/* Do we have this domain listed? If not, redirect to front page */
if (!d || !ponymail_lists[d]) {
location.href = "./";
return;
}
if (ponymail_lists[d] && !ponymail_lists[d][l]) {
/* we don't have a matching list, check for the default (or pick busiest) */
l = pm_config.default_list;
/* does the default exist ? */
if (!ponymail_lists[d][l]) {
/* If not, pick busiest */
max = -1;
ref2 = ponymail_lists[d];
for (k in ref2) {
v = ref2[k];
if (v > max) {
max = v;
l = k;
}
}
}
/* Redirect to the list */
location.href = htmlfile + "?" + l + "@" + d;
return;
} else {
/* No list specified, redirect to front page */
location.href = "./";
return;
}
} else {
/* No domain specified, redirect to front page */
location.href = "./";
return;
}
}
/* Set window title */
document.title = ponymail_list + " - Pony Mail!";
/* Construct arg list for URL */
args = "";
if (ponymail_list && ponymail_list.length > 0) {
args += ponymail_list;
}
if (ponymail_month && ponymail_month.length > 0) {
args += ":" + ponymail_month;
}
if (ponymail_query && ponymail_query.length > 0) {
args += ":" + ponymail_query;
}
/* Push a new history state using new args */
newhref = htmlfile + "?" + args;
if (location.href !== newhref) {
window.history.pushState({}, null, newhref);
}
ref3 = ponymail_list.split("@", 2), list = ref3[0], domain = ref3[1];
/* Request month view from API, send to list view callback */
pargs = "d=30";
if (ponymail_month && ponymail_month.length > 0) {
pargs = "s=" + ponymail_month + "&e=" + ponymail_month;
}
/* If we already fetched this URL once, only do an update check */
if (ponymail_list_json[newhref] && ponymail_list_json[newhref].unixtime > 0) {
since = ponymail_list_json[newhref].unixtime;
return r = new HTTPRequest("api/stats.lua?list=" + list + "&domain=" + domain + "&" + pargs + "&since=" + since, {
callback: renderListView,
state: {
href: newhref
}
});
} else {
return r = new HTTPRequest("api/stats.lua?list=" + list + "&domain=" + domain + "&" + pargs, {
callback: renderListView,
state: {
href: newhref
}
});
}
};
renderListView = function(json, state) {
/* If this is a cache check callback, and nothing has changed, use the old JSON */
var cal, lv;
if (state && state.href && typeof json.changed !== 'undefined' && json.changed === false) {
json = ponymail_list_json[state.href];
json.cached = true;
} else if (state && state.href) {
/* Save JSON in cache if new */
ponymail_list_json[state.href] = json;
}
/* Start by adding the calendar */
if (json.firstYear && json.lastYear) {
cal = new Calendar(json.firstYear, json.lastYear, ponymail_month);
get('calendar').empty().inject(cal);
}
return lv = new BasicListView(json);
};
/*
******************************************
Fetched from coffee/listview_single_email.coffee
******************************************
*/
/**
* Single email list view - extends BasicListView
*/
SingleListView = (function(superClass) {
extend(SingleListView, superClass);
/* json: from stats.lua, rpp = results per page, pos = starting position (from 0) */
function SingleListView(json1, rpp1, pos1) {
var date, hd, m, ref, y;
this.json = json1;
this.rpp = rpp1 != null ? rpp1 : 15;
this.pos = pos1 != null ? pos1 : 0;
/* Set the header first */
hd = get('header');
if (this.json.list) {
if (ponymail_month.length > 0) {
ref = ponymail_month.split("-", 2), y = ref[0], m = ref[1];
date = calendar_months[parseInt(m) - 1] + (", " + y);
hd.empty().inject([
this.json.list + " (" + date + "):", new HTML('a', {
href: "api/mbox.lua?list=" + ponymail_list + "&date=" + ponymail_month,
title: "Download as mbox archive"
}, new HTML('img', {
src: 'images/floppy.svg',
style: {
marginLeft: "10px",
width: "20px",
height: "20px",
verticalAlign: 'middle'
}
}))
]);
} else {
hd.empty().inject(this.json.list + ", past 30 days:");
}
}
/* Get and clear the list view */
this.lv = get('listview');
this.lv = this.lv.empty();
/* Set some internal vars */
this.listsize = 0;
/* If we got results, use scroll() to display from result 0 on */
if (isArray(this.json.emails) && this.json.emails.length > 0) {
/* Set some internal vars */
this.listsize = this.json.emails.length;
/* Reverse thread struct, but only if we're not using an
* already reversed cache
*/
if (!this.json.cached) {
this.json.emails.reverse();
}
this.scroll(this.rpp, this.pos);
} else {
/* No results, just say...that */
this.lv.inject("No emails found matching this criterion.");
}
ponymail_current_listview = this;
return this;
}
SingleListView.prototype.renderItems = function() {
/* For each email result,... */
var item, j, lastitem, len, original, ref;
this.lvitems = new HTML('div', {
"class": "listview_table"
});
lastitem = null;
ref = this.json.emails.slice(this.pos, this.pos + this.rpp);
for (j = 0, len = ref.length; j < len; j++) {
original = ref[j];
/* Be sure we actually have an email here */
if (original) {
/* Call listViewItem to compile a list view HTML element */
item = this.listViewItem(original, null);
lastitem = item;
/* Inject new item into the list view */
this.lvitems.inject(item);
}
}
this.lv.inject(this.lvitems);
return lastitem;
};
SingleListView.prototype.listViewItem = function(original, thread) {
/* Be sure we actually have an email here */
var avatar, date, date_style, item, now, readStyle, sender, sid, subject, uid;
if (original) {
now = new Date().getTime() / 1000;
/* Render the email in the LV */
/* First set some data points for later */
uid = parseInt(Math.random() * 999999999999).toString(16);
/* Gravatar */
avatar = new HTML('img', {
"class": "gravatar",
src: "https://secure.gravatar.com/avatar/" + original.gravatar + ".png?s=24&r=g&d=mm"
});
/* Sender, without the <foo@bar> part - just the name */
sender = new HTML('div', {
style: {
fontWeight: "bold"
}
}, original.from.replace(/\s*<.+>/, "").replace(/"/g, ''));
/* readStyle: bold if new email, normal if read before */
readStyle = "bold";
if (hasRead(original.id)) {
readStyle = "normal";
}
/* Subject, PLUS a bit of the body with a break before */
sid = shortenURL(original.id);
subject = new HTML('div', {}, [
new HTML('a', {
style: {
fontWeight: readStyle
},
href: "thread.html/" + sid,
onclick: "readEmail(this.parentNode.parentNode.parentNode); this.style.fontWeight = 'normal'; return false;"
}, original.subject)
]);
/* Add date; yellow if <= 1day, grey otherwise */
date_style = "listview_grey";
if ((now - 86400 * 4) < original.epoch) {
date_style = "listview_yellow";
}
date = new HTML('div', {
"class": "listview_right " + date_style
}, new Date(original.epoch * 1000).ISOBare());
/* Finally, pull it all together in a div and add that to the listview */
item = new HTML('div', {
id: uid,
data: original.id,
"class": "listview_item"
}, new HTML('div', {
"class": "listview_summary"
}, [avatar, sender, subject, date]));
return item;
}
};
return SingleListView;
})(BasicListView);
ponymail_register_listview('single', 'Single email theme', SingleListView);
/*
******************************************
Fetched from coffee/localstorage.coffee
******************************************
*/
/**
* Init: Test if localStorage is available or not
* If not, fall back to plain global var storage (not effective, but meh)
*/
pm_storage_available = false;
pm_storage_globvar = {};
try {
if (typeof window.localStorage !== "undefined") {
window.localStorage.setItem("pm_test", "1");
pm_storage_available = true;
}
} catch (_error) {
e = _error;
pm_storage_available = false;
}
/**
* dbWrite: Store a key/val pair
* Example: dbWrite("ponies", "They are awesome!")
*/
dbWrite = function(key, value) {
/* Can we use localStorage? */
var rv;
if (pm_storage_available) {
try {
rv = window.localStorage.setItem(key, value);
return rv;
} catch (_error) {
e = _error;
console.log("Could not save data to DB: " + e);
return null;
}
} else {
/* Guess not, fall back to (ineffective) global var */
pm_storage_globvar[key] = value;
return true;
}
};
/* dbRead: Given a key, read the corresponding value from storage */
dbRead = function(key) {
/* Do we have localStorage? */
if (pm_storage_available) {
return window.localStorage.getItem(key);
} else {
/* Nope, try global var */
return pm_storage_globvar[key];
}
};
hasRead = function(mid) {
if (dbRead("ponymail_read_" + mid)) {
return true;
} else {
return false;
}
};
markRead = function(mid) {
dbWrite("ponymail_read_" + mid, new Date().getTime());
return true;
};
/*
******************************************
Fetched from coffee/misc.coffee
******************************************
*/
/**
* Number prettification prototype:
* Converts 1234567 into 1,234,567 etc
*/
Number.prototype.pretty = function(fix) {
if (fix) {
return String(this.toFixed(fix)).replace(/(\d)(?=(\d{3})+\.)/g, '$1,');
}
return String(this.toFixed(0)).replace(/(\d)(?=(\d{3})+$)/g, '$1,');
};
/**
* Number padding
* usage: 123.pad(6) -> 000123
*/
Number.prototype.pad = function(n) {
var str;
str = String(this);
/* Do we need to pad? if so, do it using String.repeat */
if (str.length < n) {
str = "0".repeat(n - str.length) + str;
}
return str;
};
/* Func for converting a date to YYYY-MM-DD HH:MM */
Date.prototype.ISOBare = function() {
var M, d, h, m, y;
y = this.getFullYear();
m = (this.getMonth() + 1).pad(2);
d = this.getDate().pad(2);
h = this.getHours().pad(2);
M = this.getMinutes().pad(2);
return y + "-" + m + "-" + d + " " + h + ":" + M;
};
/* isArray: function to detect if an object is an array */
isArray = function(value) {
return value && typeof value === 'object' && value instanceof Array && typeof value.length === 'number' && typeof value.splice === 'function' && !(value.propertyIsEnumerable('length'));
};
/* isHash: function to detect if an object is a hash */
isHash = function(value) {
return value && typeof value === 'object' && !isArray(value);
};
/* Remove an array element by value */
Array.prototype.remove = function(val) {
var i, item, j, len;
for (i = j = 0, len = this.length; j < len; i = ++j) {
item = this[i];
if (item === val) {
this.splice(i, 1);
return this;
}
}
return this;
};
ponymail_url_regex = new RegExp("(" + "(?:(?:[a-z]+)://)" + "(?:\\S+(?::\\S*)?@)?" + "(?:" + "([01][0-9][0-9]|2[0-4][0-9]|25[0-5])" + "|" + "(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)" + "(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)*" + "(?:\\.(?:[a-z\\u00a1-\\uffff]{2,}))" + "\\.?" + ")" + "(?::\\d{2,5})?" + "(?:[/?#]([^,<>()\\[\\] \\t\\r\\n]|(<[^:\\s]*?>|\\([^:\\s]*?\\)|\\[[^:\\s]*?\\]))*)?" + ")\\.?", "mi");
ponymail_quote_regex = new RegExp("((?:\r?\n)((on .+ wrote:[\r\n]+)|(sent from my .+)|(>+([ \t][^\r\n]*|[ \t]*)\r?\n)+)+)+", "mi");
/**
* How many bits (of 7 chars each) do we want in our shortLink?
* The more bits, the more precise, the fewer bits, the shorter the link.
*/
shortBits = 3;
/* Shortener: cut MID into pieces, convert to base36 to save 3-4 bytes */
shortenURL = function(mid) {
var a, arr, i, j, num, out, ref, res;
arr = mid.split("@");
/* IF arr is 2 bits, it's fine to shorten it (medium/long generator). if 3, then potentially not (short generator) */
if (arr.length === 2 && (pm_config && pm_config.shortLinks)) {
out = "";
/* For each bit in $howlongdowewantthis ... */
for (i = j = 0, ref = shortBits - 1; 0 <= ref ? j <= ref : j >= ref; i = 0 <= ref ? ++j : --j) {
/* Cut off 8 chars, convert from base16 to base36 */
a = arr[0].substr(i * 8, 8);
num = parseInt(a, 16);
res = num.toString(36);
/* Padding for small numbers */
while (res.length < 7) {
res = '-' + res;
}
out += res;
}
return "PZ" + out;
}
return mid;
};
unshortenURL = function(mid) {
/* If new format ... */
var i, j, num, o, out, ref, res;
if (mid.substr(0, 2) === 'PZ') {
out = "";
/* For each 7-char bit, convert from base36 to base16, remove padding */
for (i = j = 0, ref = shortBits - 1; 0 <= ref ? j <= ref : j >= ref; i = 0 <= ref ? ++j : --j) {
num = parseInt(mid.substr(2 + (i * 7), 7).replace('-', ''), 36);
res = num.toString(16);
/* 0-padding for smaller numbers (<8 chars) */
while (res.length < 8) {
res = '0' + res;
}
out += res;
}
return out;
} else if (mid[0] === 'Z' || mid[0] === 'B') {
/* Old format from 0.9 and before */
out = "";
/* For each 7-char bit, convert from base36 to base16, remove padding */
for (i = o = 0; o <= 1; i = ++o) {
num = parseInt(mid.substr(1 + (i * 7), 7).replace('-', ''), 36);
res = num.toString(16);
/* 0-padding for smaller numbers (<9 chars) */
while (res.length < 9) {
res = '0' + res;
}
out += res;
}
return out;
} else {
return mid;
}
};
/*
******************************************
Fetched from coffee/preferences.coffee
******************************************
*/
/* maxLists: default max lists to show in top menu before resorting to 'more lists' */
maxLists = 2;
setupAccount = function(json, state) {
var domain, j, len, li, list, lists, lmenu, myDomain, number, ref, ref1, sortedList;
myDomain = [];
/* run parseURL for fetch list and domain */
parseURL();
/* set up the list of...lists */
if (json && isHash(json.lists)) {
ref = json.lists;
for (domain in ref) {
lists = ref[domain];
ponymail_lists[domain] = lists;
/* if current domain, set up the top menu to use it */
if (domain === ponymail_domain) {
myDomain = lists;
}
}
}
/* Are we on list.html? */
if (state.listview) {
/* Generate the lists part of the top menu */
lmenu = get('listmenu');
if (lmenu) {
/* Make an array of mailing lists */
sortedList = [];
for (list in myDomain) {
number = myDomain[list];
sortedList.push(list);
}
/* Sort descending by business */
sortedList.sort((function(_this) {
return function(a, b) {
if (myDomain[a] < myDomain[b]) {
return 1;
} else {
return -1;
}
};
})(this));
ref1 = sortedList.slice(0, +(maxLists - 1) + 1 || 9e9);
for (j = 0, len = ref1.length; j < len; j++) {
list = ref1[j];
li = new HTML('li', {}, new HTML('a', {
href: "?" + list + "@" + ponymail_domain,
onclick: "listView({month: '', list: '" + list + "@" + ponymail_domain + "'}); return false;"
}, list + '@'));
lmenu.inject(li);
}
/* Do we have more lists?? */
if (sortedList.length > maxLists) {
/* Remove the first N lists, sort the rest by name */
sortedList.splice(0, maxLists);
sortedList.sort();
li = new HTML('li', {}, "More lists ⌄");
lmenu.inject(li);
}
}
/* Call listView to fetch email */
return listView(null, true);
}
};
/*
******************************************
Fetched from coffee/scaffolding.coffee
******************************************
*/
/* This is the basic scaffolding for all pages */
headerScaffolding = function() {
/* Start off by making the top menu */
var logo, menu, ul;
menu = new HTML('div', {
id: "topMenu"
});
document.body.inject(menu);
ul = new HTML('ul', {
id: 'listmenu'
});
logo = new HTML('li', {
"class": 'logo'
}, new HTML('a', {
href: "./"
}, new HTML('img', {
src: "images/logo.png",
style: {
paddingRight: "10px",
height: "38px",
width: "auto"
}
})));
ul.inject(logo);
return menu.inject(ul);
};
footerScaffolding = function() {
/* Add a footer */
var footer;
footer = new HTML('div', {
id: "footer"
});
document.body.inject(footer);
return footer.inject([
"Powered by ", new HTML('a', {
href: 'https://ponymail.incubator.apache.org/'
}, "Apache Pony Mail (Incubating) v/" + ponymail_version), ". Copyright 2016, the Apache Software Foundation."
]);
};
listviewScaffolding = function() {
var calHolder, header, listDiv, mainDiv, qs, r;
parseURL(true);
/* Header scaffolding */
headerScaffolding();
/* Now, make the base div */
mainDiv = new HTML('div', {
id: "contents"
});
document.body.inject(mainDiv);
/* Quick Search Bar */
qs = quickSearchBar();
mainDiv.inject(qs);
/* Make the title */
header = new HTML('h2', {
id: "header"
}, "Loading list data...");
mainDiv.inject(header);
/* Then make the calendar placeholder */
calHolder = new HTML('div', {
id: "calendar"
});
mainDiv.inject(calHolder);
calHolder.inject(new HTML('h3', {}, "Archive:"));
/* Finally, make the list view placeholder */
listDiv = new HTML('div', {
id: "listview",
"class": "sbox"
});
mainDiv.inject(listDiv);
/* Footer */
footerScaffolding();
/* Make an API call to the preferences script, have it call back to listView once done */
return r = new HTTPRequest("api/preferences.lua", {
callback: setupAccount,
state: {
listview: true
}
});
};
/* Permalink view callback */
scaffoldingEmailCallback = function(json, state) {
e = new ThreadedEmailDisplay(null, null, null, json.thread);
};
/* Permalink view */
threadScaffolding = function() {
/* Header scaffolding */
var mainDiv, mid, r;
headerScaffolding();
/* Now, make the base div */
mainDiv = new HTML('div', {
id: "email_placeholder",
style: {
width: "90%"
}
});
document.body.inject(mainDiv);
/* Footer */
footerScaffolding();
/* Make an API call to the preferences script, have it call back to listView once done */
mid = location.href.match(/thread\.html\/(.+)/)[1];
return r = new HTTPRequest("api/thread.lua?id=" + unshortenURL(mid), {
callback: scaffoldingEmailCallback
});
};
/*
******************************************
Fetched from coffee/search.coffee
******************************************
*/
/* Quick Search bar creation */
quickSearchBar = function() {
var advanced, button, datedata, input, list, listdata, listname, options, qs, span;
qs = new HTML('form', {
"class": "quicksearch",
onsubmit: 'quickSearch(); return false;'
});
/* Cog */
/* The blue search button */
cog = new HTML('input', {
type: 'submit',
"class": 'qs_cog',
title: "Search settings"
});
/* Options area */
options = new HTML('div', {
"class": 'qs_options'
});
/* Timespan to search within */
datedata = "lte=1M";
span = new HTML('a', {
id: 'qs_span',
data: datedata,
href: 'javascript:void(0);'
}, "Less than 1 month ago");
/* Lists(s) to search */
listname = 'this list';
listdata = ponymail_listname;
if (ponymail_listname.length === 0) {
listname = 'all lists';
listdata = "*@*";
}
list = new HTML('a', {
id: 'qs_list',
href: 'javascript:void(0);',
data: listdata
}, listname);
options.inject([span, new HTML('br'), list]);
/* Input field for text search */
input = new HTML('input', {
type: "text",
id: 'qs_input',
"class": "qs_input",
placeholder: "Search " + listname + "..."
});
/* The blue search button */
button = new HTML('input', {
type: 'submit',
"class": 'qs_button'
});
/* Link to advanced search */
advanced = new HTML('a', {
href: 'javascript:void(advancedSearch());',
"class": "qs_link"
}, new HTML('img', {
src: 'images/advanced.png',
style: {
verticalAlign: 'middle',
height: "24px",
marginTop: "-1px"
}
}));
/* Add it all to the form */
qs.inject(cog);
qs.inject(input);
qs.inject(button);
qs.inject(advanced);
return qs;
};
/* Quick Search function */
quickSearch = function() {
/* Get the QS input */
};
/*
******************************************
Fetched from coffee/test.coffee
******************************************
*/
testCoffee = function() {
/* Get main div from HTML */
var cal, div, hider, item, j, len, li, logo, menu, p, parent, ref, ul;
parent = get('testdiv');
menu = new HTML('div', {
id: "topMenu"
});
parent.inject(menu);
ul = new HTML('ul');
logo = new HTML('li', {
"class": 'logo'
}, new HTML('a', {
href: "./"
}, new HTML('img', {
src: "images/logo.png",
style: {
paddingRight: "10px",
height: "38px",
width: "auto"
}
})));
ul.inject(logo);
ref = ['Home', 'Lists', 'Third item'];
for (j = 0, len = ref.length; j < len; j++) {
item = ref[j];
li = new HTML('li', {}, item);
ul.inject(li);
}
menu.inject(ul);
div = new HTML('div', {
"class": "sbox"
});
parent.inject(div);
cal = new Calendar('2010-5', '2016-9');
div.inject(cal);
p = new HTML('p', {
"class": "foo",
style: {
textAlign: 'center'
}
}, "Text goes here");
div.inject(p);
p.inject([". Here's a textNode added afterwards", new HTML('br')]);
hider = new HTML('b', {
onclick: 'testToggle(this);'
}, "Click here to hide this text for a second!");
return p.inject(hider);
};
testToggle = function(div) {
div.show();
return window.setTimeout(function() {
return div.show();
}, 1000);
};