You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ponymail.apache.org by hu...@apache.org on 2016/09/04 16:24:17 UTC
[1/6] incubator-ponymail git commit: CSS for email sneak peek and
listview buttons
Repository: incubator-ponymail
Updated Branches:
refs/heads/coffee-and-cake ea465c628 -> bc5371d4f
CSS for email sneak peek and listview buttons
Project: http://git-wip-us.apache.org/repos/asf/incubator-ponymail/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-ponymail/commit/0dcb7b51
Tree: http://git-wip-us.apache.org/repos/asf/incubator-ponymail/tree/0dcb7b51
Diff: http://git-wip-us.apache.org/repos/asf/incubator-ponymail/diff/0dcb7b51
Branch: refs/heads/coffee-and-cake
Commit: 0dcb7b51cb9c1bfb8387567e29b2e51dec67bb94
Parents: ea465c6
Author: Daniel Gruno <hu...@apache.org>
Authored: Sun Sep 4 17:43:25 2016 +0200
Committer: Daniel Gruno <hu...@apache.org>
Committed: Sun Sep 4 17:43:25 2016 +0200
----------------------------------------------------------------------
site/css/ponymail2.css | 24 ++++++++++++++++++++++--
1 file changed, 22 insertions(+), 2 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-ponymail/blob/0dcb7b51/site/css/ponymail2.css
----------------------------------------------------------------------
diff --git a/site/css/ponymail2.css b/site/css/ponymail2.css
index fcd59d8..53877d7 100644
--- a/site/css/ponymail2.css
+++ b/site/css/ponymail2.css
@@ -61,7 +61,7 @@
}
body, html {
- background: #BBB;
+ background: #DDD;
margin: 0;
}
@@ -347,6 +347,26 @@ h3, h2, h1 {
background: #777;
}
+.listview_item_body {
+ color: #999;
+ font-size: 90%;
+}
+
+.listview_button_green {
+ border-radius: 3px;
+ background: #449D44;
+ color: #FFF;
+ font-size: 90%;
+ border: none;
+ padding: 5px;
+ margin-bottom: 4px;
+}
+.listview_button_green:hover {
+ cursor: pointer;
+ background: #50BF50;
+ user-select: none;
+}
+
a {
color: #248;
-}
\ No newline at end of file
+}
[6/6] incubator-ponymail git commit: regen JS
Posted by hu...@apache.org.
regen JS
Project: http://git-wip-us.apache.org/repos/asf/incubator-ponymail/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-ponymail/commit/bc5371d4
Tree: http://git-wip-us.apache.org/repos/asf/incubator-ponymail/tree/bc5371d4
Diff: http://git-wip-us.apache.org/repos/asf/incubator-ponymail/diff/bc5371d4
Branch: refs/heads/coffee-and-cake
Commit: bc5371d4f479f3f13c50fbf4df15cb848a7bf89d
Parents: b1ccbfa
Author: Daniel Gruno <hu...@apache.org>
Authored: Sun Sep 4 18:24:03 2016 +0200
Committer: Daniel Gruno <hu...@apache.org>
Committed: Sun Sep 4 18:24:03 2016 +0200
----------------------------------------------------------------------
site/js/ponymail-coffee.js | 559 +++++++++++++++++++++++++++++++++-------
1 file changed, 465 insertions(+), 94 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-ponymail/blob/bc5371d4/site/js/ponymail-coffee.js
----------------------------------------------------------------------
diff --git a/site/js/ponymail-coffee.js b/site/js/ponymail-coffee.js
index 1999271..e0174e6 100644
--- a/site/js/ponymail-coffee.js
+++ b/site/js/ponymail-coffee.js
@@ -1,5 +1,5 @@
// Generated by CoffeeScript 1.9.3
-var BasicListView, Calendar, HTML, HTTPRequest, calendar_months, cog, dbRead, dbWrite, e, genColors, get, hasRead, hsl2rgb, isArray, isHash, listView, listviewScaffolding, markRead, parseURL, pendingURLStatus, pending_spinner_at, pending_url_operations, pm_snap, pm_storage_available, pm_storage_globvar, ponymail_list, ponymail_lists, ponymail_month, ponymail_query, ponymail_version, readEmail, renderListView, set, setupAccount, spinCheck, testCoffee, testToggle, toggleMonth, toggleYear, txt;
+var BasicListView, Calendar, HTML, HTTPRequest, calendar_months, cog, dbRead, dbWrite, dealWithKeyboard, e, genColors, get, hasRead, 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_listview, ponymail_domain, ponymail_email_open, ponymail_list, ponymail_list_json, ponymail_listname, ponymail_lists, ponymail_month, ponymail_query, ponymail_version, readEmail, renderListView, set, setupAccount, spinCheck, testCoffee, testToggle, toggleMonth, toggleYear, txt;
calendar_months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
@@ -104,7 +104,7 @@ 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.getAttribute('data').split("-"), sYear = ref[0], eYear = ref[1];
+ 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];
@@ -285,6 +285,16 @@ ponymail_month = "";
ponymail_query = "";
+ponymail_listname = "";
+
+ponymail_domain = "";
+
+ponymail_list_json = {};
+
+ponymail_current_listview = null;
+
+ponymail_email_open = false;
+
/*
Licensed to the Apache Software Foundation (ASF) under one or more
@@ -322,9 +332,13 @@ ponymail_query = "";
HTML = (function() {
function HTML(type, params, children) {
- /* create the raw element */
+ /* create the raw element, or clone if passed an existing element */
var child, j, key, len, subkey, subval, val;
- this.element = document.createElement(type);
+ if (typeof type === 'object') {
+ this.element = type.cloneNode();
+ } else {
+ this.element = document.createElement(type);
+ }
/* If params have been passed, set them */
if (isHash(params)) {
@@ -775,6 +789,102 @@ pm_snap = null;
*/
+/* dealWithKeyboard: Handles what happens when you hit the escape key */
+
+dealWithKeyboard = function(e) {
+ var dp, i, j, kiddos, len, splash, thread, tid;
+ 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\.html/) !== -1) {
+
+ /* should only work for the list view */
+
+ /* If datepicker popup is shown, hide it on escape */
+ tid = (current_thread || "").toString().replace(/@<.+>/, "");
+ thread = get('thread_' + tid) || get('thread_treeview_' + tid);
+
+ /* minimize datepicker if shown */
+ dp = get('datepicker_popup');
+ if (dp && dp.style.display === "block") {
+ dp.show(false);
+ } else if (thread) {
+
+ /* Otherwise, collapse a thread ?? */
+ if (thread.style.display === 'block') {
+ if (prefs.displayMode === 'treeview') {
+ toggleEmails_threaded(current_thread, true);
+ toggleEmails_treeview(current_thread, true);
+ } else {
+ toggleEmails_threaded(current_thread, true);
+ }
+ } else {
+
+ /* Close all threads? */
+ kiddos = [];
+ traverseThread(document.body, '(thread|helper)_', 'DIV');
+ for (j = 0, len = kiddos.length; j < len; j++) {
+ i = kiddos[j];
+ kiddos[i].style.display = 'none';
+ }
+ }
+ }
+ }
+ }
+
+ /* Make sure the below shortcuts don't interfere with normal operations */
+ if (splash.style.display !== 'block' && document.activeElement.nodeName !== 'INPUT' && !e.ctrlKey) {
+
+ /* H key: show help */
+ if (e.keyCode === 72) {
+ return 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 */
+ return compose(null, ponymail_list, 'new');
+ } else if (e.keyCode === 82) {
+
+ /* R key: reply */
+ if (openEmail() && last_opened_email) {
+ return compose(last_opened_email, null, 'reply');
+ }
+ } else if (e.keyCode === 83) {
+
+ /* S key: quick search */
+ if (get('q')) {
+ return get('q').focus();
+ }
+ }
+ }
+};
+
+
+/* Add listener for keys (mostly for escape key for hiding stuff) */
+
+window.addEventListener("keyup", dealWithKeyboard, false);
+
+
+/*
+ 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.
+ */
+
+
/**
* Basic listview class, to be extended by other designs
*/
@@ -783,7 +893,7 @@ BasicListView = (function() {
/* json: from stats.lua, rpp = results per page, pos = starting position (from 0) */
function BasicListView(json1, rpp1, pos1) {
- var hd;
+ var date, hd, m, ref, y;
this.json = json1;
this.rpp = rpp1 != null ? rpp1 : 15;
this.pos = pos1 != null ? pos1 : 0;
@@ -791,36 +901,128 @@ BasicListView = (function() {
/* Set the header first */
hd = get('header');
if (this.json.list) {
- hd.empty().inject(this.json.list + ", past 30 days:");
+ 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) {
- this.json.thread_struct.reverse();
+
+ /* 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 avatar, date, date_style, diff, item, j, len, noeml, now, original, people, readStyle, ref, sender, stats, subject, uid;
+ var dStat, diff, f, item, j, l, len, lvitems, nbutton, nno, now, np, original, pbutton, pno, pp, ref, tmpthis, topButtons;
this.lastScroll = new Date().getTime();
- now = new Date().getTime() / 1000;
/* 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, pos + rpp);
+ dStat = new HTML('div', {
+ style: {
+ float: "left",
+ width: "100%",
+ fontSize: "80%",
+ textAlign: "center"
+ }
+ }, "Showing threads " + f + " through " + l + " out of " + this.listsize);
+ this.lv.inject(dStat);
+
+ /* First, build the prev/next buttons if needed */
+ if (pos > 0 || (pos + rpp) < this.json.thread_struct.length) {
+ 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.json.thread_struct.length) {
+ nno = Math.min(rpp, this.json.thread_struct.length - 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);
+ }
/* For each email result,... */
+ lvitems = new HTML('div', {
+ "class": "listview_table"
+ });
ref = this.json.thread_struct.slice(pos, pos + rpp);
for (j = 0, len = ref.length; j < len; j++) {
item = ref[j];
@@ -828,72 +1030,36 @@ BasicListView = (function() {
/* Be sure we actually have an email here */
if (original) {
- people = this.countPeople(item);
- noeml = this.countEmail(item);
-
- /* 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', {
- src: "https://secure.gravatar.com/avatar/" + original.gravatar + ".png?s=24&r=g&d=mm"
- });
-
- /* Sender, without the <fo...@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(item.tid)) {
- readStyle = "normal";
- }
-
- /* Subject, PLUS a bit of the body with a break before */
- subject = new HTML('div', {}, [
- new HTML('a', {
- style: {
- fontWeight: readStyle
- },
- href: "thread.html/" + item.tid,
- onclick: "readEmail(this.parentNode.parentNode); return false;"
- }, original.subject), new HTML('br'), new HTML('span', {
- style: {
- color: "#999",
- fontSize: "0.7rem"
- }
- }, item.body)
- ]);
+ /* Call listViewItem to compile a list view HTML element */
+ item = this.listViewItem(original, item);
- /* replies and authors */
- stats = new HTML('div', {
- "class": "listview_right"
- }, " " + people + " people, " + noeml + " replies");
-
- /* Add date; yellow if <= 1day, grey otherwise */
- date_style = "listview_grey";
- if ((now - 86400 * 4) < item.epoch) {
- date_style = "listview_yellow";
- }
- date = new HTML('div', {
- "class": "listview_right " + date_style
- }, new Date(item.epoch * 1000).ISOBare());
- item = new HTML('div', {
- id: uid,
- data: item.tid,
- "class": "listview_item"
- }, [avatar, sender, subject, date, stats]);
- this.lv.inject(item);
+ /* Inject new item into the list view */
+ lvitems.inject(item);
}
}
+ this.lv.inject(lvitems);
+
+ /* 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;
- return this.lv.inject("Fetched in " + parseInt(this.json.took / 1000) + "ms, rendered in " + parseInt(diff) + "ms.");
+ 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);
};
@@ -966,6 +1132,125 @@ BasicListView = (function() {
}
};
+ BasicListView.prototype.listViewItem = function(original, thread) {
+
+ /* Be sure we actually have an email here */
+ var avatar, date, date_style, envelopeimg, item, noeml, now, people, peopleimg, readStyle, sender, 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"
+ });
+
+ /* Sender, without the <fo...@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 */
+ subject = new HTML('div', {}, [
+ new HTML('a', {
+ style: {
+ fontWeight: readStyle
+ },
+ href: "thread.html/" + thread.tid,
+ onclick: "readEmail(this.parentNode.parentNode); 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,
+ "class": "listview_item"
+ }, [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, obj, scrollBar, style;
+ 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 || scrollBar) {
+ return;
+ }
+ 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;
})();
@@ -1001,17 +1286,18 @@ window.onpopstate = function(event) {
};
parseURL = function() {
- var list, month, query, ref;
+ 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 || "";
- return ponymail_query = query || "";
+ 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, l, list, newhref, pargs, r, ref, ref1, ref2;
+ var args, d, domain, etc, htmlfile, l, list, newhref, pargs, r, ref, ref1, ref2, since;
ref = location.href.split("?"), htmlfile = ref[0], etc = ref[1];
/* Do we need to call the URL parser here? */
@@ -1021,13 +1307,13 @@ listView = function(hash, reParse) {
/* Any new settings passed along? */
if (isHash(hash)) {
- if (hash.month) {
+ if (typeof hash.month !== 'undefined') {
ponymail_month = hash.month;
}
- if (hash.list) {
+ if (typeof hash.list !== 'undefined') {
ponymail_list = hash.list;
}
- if (hash.query) {
+ if (typeof hash.query !== 'undefined') {
ponymail_query = hash.query;
}
}
@@ -1087,15 +1373,40 @@ listView = function(hash, reParse) {
if (ponymail_month && ponymail_month.length > 0) {
pargs = "s=" + ponymail_month + "&e=" + ponymail_month;
}
- return r = new HTTPRequest("api/stats.lua?list=" + list + "&domain=" + domain + "&" + pargs, {
- callback: renderListView
- });
+
+ /* 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) {
- /* Start by adding the calendar */
+ /* 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);
@@ -1259,14 +1570,16 @@ Number.prototype.pad = function(n) {
};
-/* Func for converting a date to YYYY-MM-DD */
+/* Func for converting a date to YYYY-MM-DD HH:MM */
Date.prototype.ISOBare = function() {
- var d, m, y;
+ var M, d, h, m, y;
y = this.getFullYear();
- m = this.getMonth() + 1;
- d = this.getDate();
- return y + "-" + m.pad(2) + "-" + d.pad(2);
+ 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;
};
@@ -1301,16 +1614,78 @@ isHash = function(value) {
limitations under the License.
*/
+
+/* maxLists: default max lists to show in top menu before resorting to 'more lists' */
+
+maxLists = 2;
+
setupAccount = function(json, state) {
- var domain, lists, ref;
- if (json && isArray(json.lists)) {
+ 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 \u2304");
+ lmenu.inject(li);
+ }
+ }
+
+ /* Call listView to fetch email */
return listView(null, true);
}
};
@@ -1339,12 +1714,14 @@ setupAccount = function(json, state) {
listviewScaffolding = function() {
/* Start off by making the top menu */
- var calHolder, footer, header, item, j, len, li, listDiv, logo, mainDiv, menu, r, ref, ul;
+ var calHolder, footer, header, listDiv, logo, mainDiv, menu, r, ul;
menu = new HTML('div', {
id: "topMenu"
});
document.body.inject(menu);
- ul = new HTML('ul');
+ ul = new HTML('ul', {
+ id: 'listmenu'
+ });
logo = new HTML('li', {
"class": 'logo'
}, new HTML('a', {
@@ -1358,12 +1735,6 @@ listviewScaffolding = function() {
}
})));
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);
/* Now, make the base div */
[4/6] incubator-ponymail git commit: start work on keyboard shortcuts
Posted by hu...@apache.org.
start work on keyboard shortcuts
this doesn't actually work yet...
Project: http://git-wip-us.apache.org/repos/asf/incubator-ponymail/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-ponymail/commit/d2a1cacb
Tree: http://git-wip-us.apache.org/repos/asf/incubator-ponymail/tree/d2a1cacb
Diff: http://git-wip-us.apache.org/repos/asf/incubator-ponymail/diff/d2a1cacb
Branch: refs/heads/coffee-and-cake
Commit: d2a1cacbbd8449817226fb889d0fb32ea14b1a9c
Parents: 4f22ce5
Author: Daniel Gruno <hu...@apache.org>
Authored: Sun Sep 4 17:45:30 2016 +0200
Committer: Daniel Gruno <hu...@apache.org>
Committed: Sun Sep 4 17:45:30 2016 +0200
----------------------------------------------------------------------
site/js/coffee/keyboard_shortcuts.coffee | 87 +++++++++++++++++++++++++++
1 file changed, 87 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-ponymail/blob/d2a1cacb/site/js/coffee/keyboard_shortcuts.coffee
----------------------------------------------------------------------
diff --git a/site/js/coffee/keyboard_shortcuts.coffee b/site/js/coffee/keyboard_shortcuts.coffee
new file mode 100644
index 0000000..ddb3129
--- /dev/null
+++ b/site/js/coffee/keyboard_shortcuts.coffee
@@ -0,0 +1,87 @@
+###
+ 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.
+###
+
+### dealWithKeyboard: Handles what happens when you hit the escape key ###
+dealWithKeyboard = (e) ->
+ splash = get('splash')
+
+ ### escape key: hide composer/settings/thread ###
+ if (e.keyCode == 27)
+
+ if (splash and splash.style.display == 'block')
+ splash.style.display = "none"
+ #saveDraft()
+ else if (location.href.search(/list\.html/) != -1)
+ ### should only work for the list view ###
+
+ ### If datepicker popup is shown, hide it on escape ###
+ tid = (current_thread||"").toString().replace(/@<.+>/, "")
+ thread = get('thread_' + tid) || get('thread_treeview_' + tid)
+
+ ### minimize datepicker if shown ###
+ dp = get('datepicker_popup')
+ if dp and dp.style.display == "block"
+ dp.show(false)
+
+ else if thread
+ ### Otherwise, collapse a thread ?? ###
+ if (thread.style.display == 'block')
+ if (prefs.displayMode == 'treeview')
+ toggleEmails_threaded(current_thread, true)
+ toggleEmails_treeview(current_thread, true)
+ else
+ toggleEmails_threaded(current_thread, true)
+
+ else
+ ### Close all threads? ###
+ kiddos = []
+ traverseThread(document.body, '(thread|helper)_', 'DIV')
+ for i in kiddos
+ kiddos[i].style.display = 'none';
+
+
+ ### Make sure the below shortcuts don't interfere with normal operations ###
+ if splash.style.display != 'block' and document.activeElement.nodeName != 'INPUT' and not 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 (openEmail() && last_opened_email)
+ compose(last_opened_email, null, 'reply')
+ else if e.keyCode == 83
+ ### S key: quick search ###
+ if get('q')
+ get('q').focus()
+
+
+### Add listener for keys (mostly for escape key for hiding stuff) ###
+window.addEventListener("keyup", dealWithKeyboard, false);
[3/6] incubator-ponymail git commit: some more defaults
Posted by hu...@apache.org.
some more defaults
Project: http://git-wip-us.apache.org/repos/asf/incubator-ponymail/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-ponymail/commit/4f22ce58
Tree: http://git-wip-us.apache.org/repos/asf/incubator-ponymail/tree/4f22ce58
Diff: http://git-wip-us.apache.org/repos/asf/incubator-ponymail/diff/4f22ce58
Branch: refs/heads/coffee-and-cake
Commit: 4f22ce588e0f8839e9910e0f162bfb6ead1c1da1
Parents: 1d76d32
Author: Daniel Gruno <hu...@apache.org>
Authored: Sun Sep 4 17:45:11 2016 +0200
Committer: Daniel Gruno <hu...@apache.org>
Committed: Sun Sep 4 17:45:11 2016 +0200
----------------------------------------------------------------------
site/js/coffee/defaults.coffee | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-ponymail/blob/4f22ce58/site/js/coffee/defaults.coffee
----------------------------------------------------------------------
diff --git a/site/js/coffee/defaults.coffee b/site/js/coffee/defaults.coffee
index ffac413..8e686f9 100644
--- a/site/js/coffee/defaults.coffee
+++ b/site/js/coffee/defaults.coffee
@@ -25,4 +25,7 @@ ponymail_query = ""
ponymail_listname = ""
ponymail_domain = ""
-ponymail_list_json = {}
\ No newline at end of file
+ponymail_list_json = {}
+
+ponymail_current_listview = null # Current listview class
+ponymail_email_open = false # Is the user viewing an email right now? (disable scrolling then)
\ No newline at end of file
[2/6] incubator-ponymail git commit: Move list view item generation
to own func, add scrolling
Posted by hu...@apache.org.
Move list view item generation to own func, add scrolling
Simplifying extending this class by moving the list view
item generation to its own function (which can then be modified
separately). Also enabling the mouse scroll feature and adding
buttons to next/prev page of emails.
Project: http://git-wip-us.apache.org/repos/asf/incubator-ponymail/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-ponymail/commit/1d76d320
Tree: http://git-wip-us.apache.org/repos/asf/incubator-ponymail/tree/1d76d320
Diff: http://git-wip-us.apache.org/repos/asf/incubator-ponymail/diff/1d76d320
Branch: refs/heads/coffee-and-cake
Commit: 1d76d320cf415d4831c199d32b2d5744f4c836b3
Parents: 0dcb7b5
Author: Daniel Gruno <hu...@apache.org>
Authored: Sun Sep 4 17:45:01 2016 +0200
Committer: Daniel Gruno <hu...@apache.org>
Committed: Sun Sep 4 17:45:01 2016 +0200
----------------------------------------------------------------------
site/js/coffee/listview_basic.coffee | 183 ++++++++++++++++++++++--------
1 file changed, 133 insertions(+), 50 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-ponymail/blob/1d76d320/site/js/coffee/listview_basic.coffee
----------------------------------------------------------------------
diff --git a/site/js/coffee/listview_basic.coffee b/site/js/coffee/listview_basic.coffee
index c61dc2b..7f956bd 100644
--- a/site/js/coffee/listview_basic.coffee
+++ b/site/js/coffee/listview_basic.coffee
@@ -52,77 +52,84 @@ class BasicListView
else
### No results, just say...that ###
@lv.inject("No emails found matching this criterion.")
-
+
+ ### Set some internal vars ###
+ @listsize = @json.thread_struct.length
+
+ # set current list view to this class
+ ponymail_current_listview = this
+ return this
### scroll: scroll to a position and show N emails/threads ###
scroll: (rpp, pos) ->
@lastScroll = new Date().getTime()
- now = new Date().getTime()/1000
### Clear the list view ###
@lv = @lv.empty()
+ topButtons = null
- ### Make some pre-defined html object templates ###
- 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"}})
+ @rpp = rpp
+ @pos = pos
+ ### First, build the prev/next buttons if needed ###
+ if pos > 0 or (pos+rpp) < @json.thread_struct.length
+ topButtons = new HTML('div', { style: {float: "left", width: "100%"}})
+ ## Prev button
+ if pos > 0
+ pno = Math.min(rpp, pos)
+ pp = Math.max(0, pos-rpp)
+ pbutton = new HTML('input', {
+ type: 'button'
+ value: 'Previous ' + pno + " message" + (if pno == 1 then '' else 's')
+ onclick: "ponymail_current_listview.scroll(#{rpp}, #{pp});"
+ class: "listview_button_green"
+ style: { float: "left" }
+ })
+ topButtons.inject(pbutton)
+ ### Next button ###
+ if (pos+rpp) < @json.thread_struct.length
+ nno = Math.min(rpp, @json.thread_struct.length - pos - rpp)
+ np = pos+rpp
+ nbutton = new HTML('input', {
+ type: 'button'
+ value: 'Next ' + nno + " message" + (if nno == 1 then '' else 's')
+ onclick: "ponymail_current_listview.scroll(#{rpp}, #{np});"
+ class: "listview_button_green"
+ style: { float: "right" }
+ })
+ topButtons.inject(nbutton)
+ @lv.inject(topButtons)
+
### For each email result,...###
for item in @json.thread_struct[pos...(pos+rpp)]
original = @findEmail(item.tid)
-
### Be sure we actually have an email here ###
if original
- people = @countPeople(item)
- noeml = @countEmail(item)
-
- ### 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 <fo...@bar> part - just the name ###
- sender = new HTML('div', {style: {fontWeight: "bold"}}, original.from.replace(/\s*<.+>/, "").replace(/"/g, ''))
+ ### Call listViewItem to compile a list view HTML element ###
+ item = @listViewItem(original, item)
- ### readStyle: bold if new email, normal if read before ###
- readStyle = "bold"
- if hasRead(item.tid)
- readStyle = "normal"
-
- ### Subject, PLUS a bit of the body with a break before ###
- subject = new HTML('div', {}, [
- new HTML('a', { style: {fontWeight: readStyle}, href: "thread.html/#{item.tid}", onclick: "readEmail(this.parentNode.parentNode); return false;"}, original.subject),
- new HTML('br'),
- new HTML('span', { style: { color: "#999", fontSize: "0.7rem"}}, item.body)
- ])
-
- ### show number of replies and participants ###
- stats = new HTML('div', {class:"listview_right"}, [
- peopleimg.cloneNode(),
- " #{people} ",
- envelopeimg.cloneNode(),
- " #{noeml}"
- ])
-
- ### Add date; yellow if <= 1day, grey otherwise ###
- date_style = "listview_grey"
- if (now-86400*4) < item.epoch
- date_style = "listview_yellow"
- date = new HTML('div', {class:"listview_right #{date_style}"}, new Date(item.epoch*1000).ISOBare())
-
-
- ### Finally, pull it all together in a div and add that to the listview ###
- item = new HTML('div', {id: uid, data: item.tid, class: "listview_item"}, [avatar, sender, subject, date, stats])
+ ### Inject new item into the list view ###
@lv.inject(item)
+ ### If we made buttons, clone them at the bottom ###
+ if topButtons
+ @lv.inject(topButtons.cloneNode(true))
+
now = new Date().getTime()
diff = now - @lastScroll
if @json.cached
@lv.inject("Fetched from cache (no updates detected), rendered in " + parseInt(diff) + "ms.")
else
@lv.inject("Fetched in " + parseInt(@json.took/1000) + "ms, rendered in " + parseInt(diff) + "ms.")
+
+ tmpthis = this
+ ### Finally, enable scrolling ###
+ @lv.addEventListener("mousewheel", (e) ->
+ tmpthis.swipe(e)
+ , false);
+ @lv.addEventListener("DOMMouseScroll", (e) ->
+ tmpthis.swipe(e)
+ , false);
+
### findEmail: find an email given an ID ###
findEmail: (id) ->
for email in @json.emails
@@ -161,4 +168,80 @@ class BasicListView
for k,v of np
n++
return n
-
\ No newline at end of file
+
+ listViewItem: (original, thread) ->
+ ### Be sure we actually have an email here ###
+ if original and thread
+ now = new Date().getTime()/1000
+
+ ### Gather stats ###
+ people = @countPeople(thread)
+ noeml = @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"})
+
+ ### Sender, without the <fo...@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 ###
+ subject = new HTML('div', {}, [
+ new HTML('a', { style: {fontWeight: readStyle}, href: "thread.html/#{thread.tid}", onclick: "readEmail(this.parentNode.parentNode); 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, class: "listview_item"}, [avatar, sender, subject, date, stats])
+ return item
+
+ ### swipe: go to next or previous page of emails, depending on mouse wheel direction ###
+ swipe: (e) ->
+ direction = if ((e.wheelDelta || -e.detail) < 0) then 'down' else '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 or scrollBar
+ return
+
+ if direction == 'down'
+ ### Next page? ###
+ if @listsize > (@pos+@rpp+1)
+ @scroll(@rpp, @pos+@rpp)
+ else if direction == 'up'
+ ### Previous page? ###
+ if @pos > 0
+ @scroll(@rpp, Math.max(0,@pos-@rpp))
+
\ No newline at end of file
[5/6] incubator-ponymail git commit: conform to the bootstrap-like
table model
Posted by hu...@apache.org.
conform to the bootstrap-like table model
Project: http://git-wip-us.apache.org/repos/asf/incubator-ponymail/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-ponymail/commit/b1ccbfa8
Tree: http://git-wip-us.apache.org/repos/asf/incubator-ponymail/tree/b1ccbfa8
Diff: http://git-wip-us.apache.org/repos/asf/incubator-ponymail/diff/b1ccbfa8
Branch: refs/heads/coffee-and-cake
Commit: b1ccbfa828c147ae0188a1f1499b79f338c99b5f
Parents: d2a1cac
Author: Daniel Gruno <hu...@apache.org>
Authored: Sun Sep 4 18:23:54 2016 +0200
Committer: Daniel Gruno <hu...@apache.org>
Committed: Sun Sep 4 18:23:54 2016 +0200
----------------------------------------------------------------------
site/css/ponymail2.css | 32 ++++++++++++++++++++++++++-----
site/js/coffee/listview_basic.coffee | 19 +++++++++++++++---
2 files changed, 43 insertions(+), 8 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-ponymail/blob/b1ccbfa8/site/css/ponymail2.css
----------------------------------------------------------------------
diff --git a/site/css/ponymail2.css b/site/css/ponymail2.css
index 53877d7..fb11cd0 100644
--- a/site/css/ponymail2.css
+++ b/site/css/ponymail2.css
@@ -288,16 +288,14 @@ h3, h2, h1 {
/* listview styles */
.listview_item {
- border-radius: 2px;
- border: 1px solid rgba(0,0,0,0.3);
padding: 2px;
- margin: 1px;
+ margin: 0px;
float: left;
width: calc(100% - 8px);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
- background: #FAFAFA;
+ background: #FFF;
}
.listview_item img.gravatar {
@@ -323,7 +321,7 @@ h3, h2, h1 {
}
.listview_item div:nth-child(3) {
- width: calc(100% - 450px);
+ width: calc(100% - 380px);
}
.listview_right {
@@ -367,6 +365,30 @@ h3, h2, h1 {
user-select: none;
}
+.listview_table {
+ border-radius: 3px;
+ border: 1px solid rgba(0,0,0,0.15);
+ float: left;
+ width: 100%;
+}
+.listview_table .listview_item:hover {
+ background: #F0F0F0;
+}
+
+.listview_table .listview_item {
+ border-bottom: 1px solid rgba(0,0,0,0.15);
+ border-top-left-radius: 3px;
+ border-top-right-radius: 3px;
+ background: #FFF;
+
+}
+
+.listview_table .listview_item:last-child {
+ border-bottom: none;
+ border-bottom-left-radius: 3px;
+ border-bottom-right-radius: 3px;
+}
+
a {
color: #248;
}
http://git-wip-us.apache.org/repos/asf/incubator-ponymail/blob/b1ccbfa8/site/js/coffee/listview_basic.coffee
----------------------------------------------------------------------
diff --git a/site/js/coffee/listview_basic.coffee b/site/js/coffee/listview_basic.coffee
index 7f956bd..2b5665e 100644
--- a/site/js/coffee/listview_basic.coffee
+++ b/site/js/coffee/listview_basic.coffee
@@ -42,8 +42,15 @@ class BasicListView
@lv = get('listview')
@lv = @lv.empty()
+ ### Set some internal vars ###
+ @listsize = 0
+
### If we got results, use scroll() to display from result 0 on ###
if isArray(@json.thread_struct) and @json.thread_struct.length > 0
+
+ ### Set some internal vars ###
+ @listsize = @json.thread_struct.length
+
### Reverse thread struct, but only if we're not using an
# already reversed cache ###
if not @json.cached
@@ -53,8 +60,6 @@ class BasicListView
### No results, just say...that ###
@lv.inject("No emails found matching this criterion.")
- ### Set some internal vars ###
- @listsize = @json.thread_struct.length
# set current list view to this class
ponymail_current_listview = this
@@ -70,6 +75,12 @@ class BasicListView
@rpp = rpp
@pos = pos
+ ### Show how many threads out of how many we are showing ###
+ f = pos+1
+ l = Math.min(@listsize - pos, pos+rpp)
+ dStat = new HTML('div', { style: {float: "left", width: "100%", fontSize: "80%", textAlign: "center"}}, "Showing threads #{f} through #{l} out of #{@listsize}")
+ @lv.inject(dStat)
+
### First, build the prev/next buttons if needed ###
if pos > 0 or (pos+rpp) < @json.thread_struct.length
topButtons = new HTML('div', { style: {float: "left", width: "100%"}})
@@ -100,6 +111,7 @@ class BasicListView
@lv.inject(topButtons)
### For each email result,...###
+ lvitems = new HTML('div', { class: "listview_table" })
for item in @json.thread_struct[pos...(pos+rpp)]
original = @findEmail(item.tid)
### Be sure we actually have an email here ###
@@ -108,7 +120,8 @@ class BasicListView
item = @listViewItem(original, item)
### Inject new item into the list view ###
- @lv.inject(item)
+ lvitems.inject(item)
+ @lv.inject(lvitems)
### If we made buttons, clone them at the bottom ###
if topButtons