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:22 UTC

[6/6] incubator-ponymail git commit: regen JS

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 */