--- a/client/bubbles.xml
+++ b/client/bubbles.xml
@@ -20,16 +20,23 @@
},
addTagName: function(aTagName) {
var node = $("<div/>").addClass("bubble").attr("type", "tag")[0];
ElementXBL.prototype.addBinding.call(node, "bubbles.xml#constraint-tag");
node.setTagName(aTagName);
$(this._nConstraints).append(node);
console.log("Tag name constraint", aTagName, "added");
},
+ addDiscussion: function(aDiscussion) {
+ var node = $("<div/>").addClass("bubble").attr("type", "discussion")[0];
+ ElementXBL.prototype.addBinding.call(node, "bubbles.xml#constraint-discussion");
+ node.setDiscussion(aDiscussion);
+ $(this._nConstraints).append(node);
+ console.log("Discussion constraint", aDiscussion, "added");
+ },
getContacts: function() {
var contacts = [];
$(this._nConstraints).children().map(function (index, item) {
if (item.getType() == "contact") {
contacts.push(item.getContact());
}
});
return contacts;
@@ -38,16 +45,28 @@
var tagNames = [];
$(this._nConstraints).children().map(function (index, item) {
if (item.getType() == "tag") {
tagNames.push(item.getTagName());
}
});
return tagNames;
},
+ getDiscussions: function() {
+ var discussions = [];
+ $(this._nConstraints).children().map(function (index, item) {
+ if (item.getType() == "discussion") {
+ discussions.push(item.getDiscussion());
+ }
+ });
+ return discussions;
+ },
+ clear: function() {
+ $(this._nConstraints).empty();
+ },
xblBindingAttached: function () {
this._nConstraints = this.shadowTree.getElementById("holder");
},
})
]]></xbl:implementation>
</xbl:binding>
<xbl:binding id="constraint-contact">
@@ -102,9 +121,34 @@
},
setTagName: function(aTagName) {
this.tagname = aTagName;
this.shadowTree.getElementById("tagname").textContent = aTagName;
}
})
]]></xbl:implementation>
</xbl:binding>
+ <!-- Discussion -->
+ <xbl:binding id="constraint-discussion">
+ <xbl:template>
+ <span id="discussion"></span>
+ </xbl:template>
+ <xbl:implementation><![CDATA[
+ ({
+ getType: function() { return "discussion"; },
+ _discussion: null,
+ getDiscussion: function() {
+ return this._discussion;
+ },
+ _getAttr : function(attr) {
+ return this._discussion[attr];
+ },
+ getName: function() {
+ return this._getAttr("name");
+ },
+ setDiscussion: function(aDiscussion) {
+ this._discussion = aDiscussion;
+ this.shadowTree.getElementById("discussion").textContent = this.getName();
+ }
+ })
+ ]]></xbl:implementation>
+ </xbl:binding>
</xbl:xbl>
--- a/client/cloda.js
+++ b/client/cloda.js
@@ -276,17 +276,17 @@ var MAX_TIMESTAMP = 4000000000;
var Gloda = {
dbContacts: $.couch.db("contacts"),
dbMessages: $.couch.db("messages"),
_init: function () {
},
- queryByStuff: function(aInvolvedContactIds, aTagNames, aCallback, aCallbackThis) {
+ queryByStuff: function(aInvolvedContactIds, aTagNames, aDiscussionIds, aCallback, aCallbackThis) {
// -- for each involved person, get the set of conversations they're in
var constraints = [];
if (aInvolvedContactIds && aInvolvedContactIds.length) {
constraints = constraints.concat(aInvolvedContactIds.map(function (contact) {
return {
view: "by_involves/by_involves",
startkey: [contact._id, 0], endkey: [contact._id, MAX_TIMESTAMP]
};
@@ -295,16 +295,24 @@ var Gloda = {
if (aTagNames && aTagNames.length) {
constraints = constraints.concat(aTagNames.map(function (tagName) {
return {
view: "by_tags/by_tags",
startkey: [tagName, 0], endkey: [tagName, MAX_TIMESTAMP]
};
}, this));
}
+ if (aDiscussionIds && aDiscussionIds.length) {
+ constraints = constraints.concat(aDiscussionIds.map(function (discussion) {
+ return {
+ view: "by_mailing_list/by_list_id",
+ startkey: [discussion.id, 0], endkey: [discussion.id, MAX_TIMESTAMP]
+ };
+ }, this));
+ }
var query = new GlodaConvQuery();
query.queryForConversations(constraints, aCallback, aCallbackThis);
// -- intersect all those conversations
// -- (fetch the conversation meta-info)
// -- fetch the messages in the conversations
}
};
--- a/client/index.xhtml
+++ b/client/index.xhtml
@@ -47,16 +47,58 @@
constraints.addTagName(tagName);
console.log("added tag name", tagName);
}
else
return;
query.updateConstraints(constraints);
});
console.log("constrainer registered");
+
+ ru = new RainUtil();
+ ru.addStaticMailbox("Inbox","inbox");
+ ru.addStaticMailbox("Drafts","drafts");
+ ru.addStaticMailbox("Archives","archives");
+ ru.addStaticMailbox("Sent","sent");
+ ru.addStaticMailbox("Trash","trash");
+ ru.addStaticMailbox("Junk","junk");
+
+ ru.addDynamicMailbox("People","people-by-frecency");
+ ru.addDynamicMailbox("Search","searches");
+ ru.addDynamicMailbox("Discussion","discussion");
+
+
+ var dbMessages = $.couch.db("messages");
+ dbMessages.view("by_header_id/by_mailing_list", {
+ include_docs: false,
+ group : true,
+ success: function(json) {
+ var parent = $("#discussion");
+ json.rows.forEach(function(row) {
+ var li = $(document.createElement("li")).addClass("discussion-list");
+ parent.append(li
+ .append($(document.createElement("a"))
+ .addClass("discussion-list")
+ .attr("title", row.key["name"] + " (" + row.value + ")")
+ .attr("href","#/discussion/" + row.key["id"])
+ .mousedown(function() {
+ var c = document.getElementById("constraints");
+ c.clear();
+ c.addDiscussion(row.key);
+ var query = document.getElementById("query");
+ query.updateConstraints(c);
+ }
+ )
+ .text(row.key["name"] + " (" + row.value + ")")
+ )
+ );
+ });
+ }
+ });
+
}
$(function() {
window.setTimeout(funkyInit, 1000);
//DocumentXBL.prototype.loadBindingDocument.call(document, "messages.xml");
/*
var dbMessages = $.couch.db("messages");
dbMessages.view("by_timestamp/by_timestamp", {
@@ -71,18 +113,54 @@
node.setMessage(row.doc);
//fDocumentXBL_addBindings(node);
return node;
});
}
})
*/
});
- ]]></script>
+
+ function RainUtil() { }
+ RainUtil.prototype = {
+ addStaticMailbox: function(aTitle, aQuery) {
+ var a = document.createElement("a");
+ $("#static-mailboxes").append($(a).addClass("sMailbox").attr("href","#" + aQuery).text(aTitle));
+ },
+ addDynamicMailbox: function(aTitle, aQuery) {
+ $("#dynamic-mailboxes").append(this._dynamicMailbox(aTitle,aQuery));
+ },
+ _dynamicMailbox: function(aTitle,aQuery) {
+ var d = document.createElement("div"),
+ dt = document.createElement("div"),
+ span = document.createElement("span"),
+ ul = document.createElement("ul"),
+ li = document.createElement("li");
+ return $(d).addClass("dMailbox").append(
+ $(dt).addClass("title").text(aTitle).append(
+ $(span).addClass("toggle")
+ .text("-")
+ .click(function() { $(ul).slideToggle(); })),
+ $(ul).addClass("results").attr("id", aQuery));
+ },
+
+ };
+ ]]></script>
</head>
<body>
<div id="autocomplete"></div>
<div id="constraints"/>
<div id="query"/>
- <div id="conversations">
+
+ <div id="mc">
+ <div id="left">
+ <div id="static-mailboxes">
+ </div>
+ <div id="dynamic-mailboxes">
+ </div>
+ </div>
+ <div id="right">
+ <div id="conversations">
+ </div>
+ </div>
</div>
</body>
</html>
--- a/client/messages.xml
+++ b/client/messages.xml
@@ -18,16 +18,17 @@
node.setConversation(conversation);
return node;
}));
},
updateConstraints: function(aConstraints) {
console.log("updating constraints");
Gloda.queryByStuff(aConstraints.getContacts(),
aConstraints.getTagNames(),
+ aConstraints.getDiscussions(),
this.resultsAhoy, this);
console.log("constraints updated");
}
})
]]></xbl:implementation>
<xbl:handlers>
<xbl:handler event="click"><![CDATA[
--- a/client/searchResults.css
+++ b/client/searchResults.css
@@ -27,17 +27,17 @@ conversation { padding: 1em 1px; margin:
border-top: 1px solid transparent; border-bottom: 1px solid #ddd;
display: block;
color: #555; background-color: #f5f6f7; /* default read */
}
conversation:focus { border: 1px dotted #111; padding: 1em 0px; }
conversation[unread="true"]:focus { border: 1px dotted #111; padding: 1em 0px; }
conversation:focus:last-child { border: 1px dotted #111; padding: 1em 0px; }
conversation:focus:first-child { border: 1px dotted #111; padding: 1em 0px; }
-conversation:last-child { border-bottom: 1px solid transparent; }
+conversation:last-child { border-color: transparent; }
conversation:first-child { border-top: 1px solid #ddd; }
conversation > .target > .header,
conversation > .target > .body,
conversation > .target > .subject,
conversation > .replies { margin-left: 24px; font-size: 95%; }
conversation > .target { display: block; padding: 0.2em 0em; padding-right: 1em; }
@@ -102,8 +102,20 @@ conversation[unread="true"] > .target >
/* BUTTON TOOLBOX */
search-toolbox { display: block; }
button.archive { font-weight: bold; }
button[type="menu"] { font-size: small; border: 1px solid #bed1df; background-color: #cee1ef; color: #666; }
search-toolbox > .notifications { float: right; }
.notifications div { padding: 0.3em 1em; -moz-border-radius: 0.3em; }
.notifications .alert { background-color: #fcaf3e; color: #111; }
.notifications .info { background-color: inherit; color: #666; }
+
+#mc { display: table; }
+#left { display: table-cell; width: 10em; padding: 0.2em 1em 1ex 1em; }
+#right { display: table-cell; border: 2px solid lightsteelblue; -moz-border-radius: 4px; }
+.sMailbox { display: block; margin: 0.2ex 0px; }
+#dynamic-mailboxes { margin: 1ex 0px; max-width: 10em; }
+.dMailbox { margin: 1ex 0px; padding: 0px; border: 1px solid lightsteelblue; -moz-border-radius: 0.5em; }
+.dMailbox .title { background-color: steelblue; color: white; padding: 0.2ex 0.4em; position: relative; }
+.dMailbox .title { -moz-border-radius-topright: 0.2em; -moz-border-radius-topleft: 0.2em; }
+.dMailbox .title .toggle { position: absolute; right: 0.5em; font-weight: bold; cursor: pointer; }
+.dMailbox .results { list-style: none; margin: 0px; padding: 0.4ex 0.2em; font-size: small; white-space: nowrap; overflow: hidden; }
+.dMailbox .results li { padding: 0.2ex 0px; }
--- a/server/python/junius/model.py
+++ b/server/python/junius/model.py
@@ -182,17 +182,44 @@ class Message(schema.Document):
# -- storage info views
# so, this key is theoretically just wildly expensive
# no ghosts!
by_storage = schema.View('by_storage', '''\
function(doc) {
if (doc.timestamp)
emit([doc.account_id, doc.storage_path, doc.storage_id], null);
}''', include_docs=False)
+
+ by_mailing_list = schema.View('by_header_id', '''\
+ function(doc) {
+ if (doc.headers && doc.headers["List-Id"]) {
+ var parts = doc.headers["List-Id"].match(/([\W\w]*)\s*<(.+)>.*/);
+ var keys = {"List-Id" : doc.headers["List-Id"],
+ "id" : parts[2],
+ "name" : parts[1] };
+ for each (var headerId in ["List-Post","List-Archive","List-Help",
+ "List-Subscribe","List-Unsubscribe"]) {
+ if (doc.headers[headerId])
+ keys[headerId] = doc.headers[headerId];
+ }
+ emit(keys, 1);
+ }
+ }''', '''\
+ function(keys, values, rereduce) {
+ return sum(values);
+ }''', include_docs=False, group=True, group_level=1)
+ by_list_id = schema.View('by_mailing_list', '''\
+ function(doc) {
+ if (doc.headers && doc.headers["List-Id"]) {
+ var parts = doc.headers["List-Id"].match(/[\W\w]\s*<(.+)>.*/);
+ emit([parts[1], doc.timestamp], doc.conversation_id);
+ }
+ }''', include_docs=True)
+
DATABASES = {
# the app database proper, no real data
'junius': None,
#
'accounts': Account,
'contacts': Contact,
'messages': Message,
}