changed by_header_id/by_mailing_list to by_list_id/by_mailing_list, even though this is a little confusing I upgraded my couchdb by accident and so the python bindings seem to overwrite the by_header_id instead of add a new one.
authorBryan Clark <clarkbw@gnome.org>
Tue, 10 Feb 2009 17:54:57 -0800
changeset 44 ac56e880eb6456211e157c0a68bcf13af3470530
parent 43 afc84851c10a47f658cf04adf866059b23358afd
child 45 3f9d0c9550d46bc05e5dbdcb34536cc59c4a30e0
push id1
push userroot
push dateWed, 08 Apr 2009 01:46:05 +0000
changed by_header_id/by_mailing_list to by_list_id/by_mailing_list, even though this is a little confusing I upgraded my couchdb by accident and so the python bindings seem to overwrite the by_header_id instead of add a new one. added from_contacts, to_contacts, cc_contacts, and involves_contacts to the Message model with some changes to the grok_address so it would return tuples instead of just the contact. The tuples mean that I can know what email the message was sent to, even if we found the contact (who could have multiple identities) via lookup. The new *_contacts are structured to provide just enough information about the person so a message can be displayed or a full lookup done. i.e.: $id : { name : $name, email : $email } Added some structure to the index.xhtml page so that it moves more toward the look that we want. Inserted a $("#conversations).empty() function in the onclick handler of the mailing list so there is some instant feedback after clicking. That might make more sense in an onmouseup() handler instead.
client/index.xhtml
client/searchResults.css
server/python/junius/getmail.py
server/python/junius/model.py
--- a/client/index.xhtml
+++ b/client/index.xhtml
@@ -49,50 +49,51 @@
         }
         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.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", {
+      dbMessages.view("by_list_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.value["name"] + " (" + row.value["count"] + ")\n" + row.value["id"])
                                             .attr("href","#/discussion/" + row.key)
                                             .mousedown(function() { 
                                                           var c = document.getElementById("constraints"); 
                                                           c.clear(); 
+                                                          $("#conversations").empty();
                                                           c.addDiscussion(row.value);
                                                           var query = document.getElementById("query");
                                                           query.updateConstraints(c);
                                                         }
                                                       )
-                                            .text(row.value["id"] + " ")
+                                            .text(row.value["id"])
                                             .append($(document.createElement("span"))
                                                               .addClass("count")
                                                               .text("(" + row.value["count"] + ")")
                                                   )
                                             )
                                   );
           });
         }
@@ -145,26 +146,29 @@
                        .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="mc">
-    <div id="left">
-      <div id="static-mailboxes">
-      </div>
-      <div id="dynamic-mailboxes">
-      </div>
+  <div id="middle">
+    <div class="left">
+      <div id="brand"></div>
+      <div id="static-mailboxes"></div>
+      <div id="dynamic-mailboxes"></div>
     </div>
-    <div id="right">
-      <div id="conversations">
-      </div>
+    <div class="right">
+      <div id="autocomplete"></div>
+      <div id="constraints"/>
+      <div id="query"/>  
+      <div id="description"></div>
+      <div class="toolbar top"></div>
+      <div id="conversations"></div>
+      <div class="toolbar bottom"></div>
     </div>
   </div>
+  <div class="section" id="footer"></div>
+
 </body>
 </html>
--- a/client/searchResults.css
+++ b/client/searchResults.css
@@ -2,29 +2,36 @@
 /*body { margin: 0em; padding: 0em; font-family: sans-serif; font-size: 10pt; }*/
 /*#window { position: relative; }*/
 
 
 
 /* BASICS */
 body { font-family: sans-serif; font-size: 80%; margin: 0px; padding: 0px; 
        background-color: #fff;
-     }
+}
+
+#header { }
+#middle { }
+#footer { }
+.left { display: table-cell; width: 10em; padding: 0.2em 1em 1ex 1em; }
+.right { display: table-cell; }
+
 
 /* CONVERSATIONS + TOOLBOX */
 #toolbox, .conversations { padding: 0.3em; margin: 0px; }
 #toolbox, .conversations { border: 0.2em solid #cee1ef; background-color: #eaf3fa; }
 #toolbox, .conversations { min-width: 600px; max-width: 900px; }
 
 #toolbox { position: fixed; margin-left: 1em; top: 0em; -moz-border-radius: 0.3em;
            -moz-border-radius-topleft: 0px; -moz-border-radius-topright: 0px; 
            border-top: none;
          }
 
-.conversations { margin: 1em; -moz-border-radius: 0.4em;  }
+.conversations { margin: 1em; -moz-border-radius: 0.4em;  border: 2px solid lightsteelblue; -moz-border-radius: 4px; }
 [hidden="true"] { display: none; }
 
 /* CONVERSATION */
 conversation { padding: 1em 1px; margin: 0px;
                border-top: 1px solid transparent; border-bottom: 1px solid #ddd; 
                display: block;
                color: #555; background-color: #f5f6f7; /* default read */
               }
@@ -104,18 +111,16 @@ 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; position: relative; }
--- a/server/python/junius/getmail.py
+++ b/server/python/junius/getmail.py
@@ -71,17 +71,17 @@ class JuniusAccount(object):
                 contacts = model.Contact.by_identity(self.dbs.contacts,
                                                      key=['email', address])
                 if len(contacts):
                     # the contact exists, use it
                     contact = list(contacts)[0]
                     if contact.id in seen_contacts:
                         contact = seen_contacts[contact.id]
                     else:
-                        involved_list.append(contact)
+                        involved_list.append( (address,contact) )
                         seen_contacts[contact.id] = contact
                 else:
                     # the contact does't exist, create it
                     if not name:
                         name = address
                     else:
                         try:
                             pieces = email.header.decode_header(name)
@@ -90,19 +90,19 @@ class JuniusAccount(object):
                         except (LookupError, UnicodeError):
                             name = u""+name
 
                     contact = model.Contact(
                         name=name,
                         identities=[{'kind': 'email', 'value': address}]
                     )
                     contact.store(self.dbs.contacts)
-                    involved_list.append(contact)
+                    involved_list.append( (address,contact) )
                     seen_contacts[contact.id] = contact
-                cur_results.append(contact)
+                cur_results.append( (address,contact) )
             result_lists.append(cur_results)
         result_lists.append(involved_list)
         return result_lists
     
     def extract_message_id(self, message_id_string, acceptNonDelimitedReferences):
         # this is a port of my fix for bug 466796, the comments should be ported
         #  too if we keep this logic...
         whitespaceEndedAt = None
@@ -159,17 +159,17 @@ class JuniusAccount(object):
         self_message = None
         header_message_ids = self.extract_message_ids(refs_str)
         unseen = set(header_message_ids)
 
         # save off the list of referenced messages
         references = header_message_ids[:]
         # see if the self-message already exists...
         header_message_ids.append(self_header_message_id)
-        
+
         messages = model.Message.by_header_id(self.dbs.messages,
                                               keys=header_message_ids)
         for message in messages:
             if message.header_message_id == self_header_message_id:
                 self_message = message
             else:
                 unseen.remove(message.header_message_id)
             conversation_id = message.conversation_id
@@ -196,45 +196,51 @@ class JuniusAccount(object):
         
         # XXX the gocept header logic unfortunately is case-sensitive...
         # XXX also, doesn't support repeated values...
         # (but we can live with these limitations for now)
         
         from_contacts, to_contacts, cc_contacts, involves_contacts = self.grok_email_addresses(
             imsg.headers.get('From', ''), imsg.headers.get('To', ''),
             imsg.headers.get('Cc', ''))
-        
+
         conversation_id, existing_message, references = self.grok_message_conversation(imsg)
         
         timestamp = email.utils.mktime_tz(email.utils.parsedate_tz(imsg.headers['Date']))
-        
-        
+
         cmsg = model.Message(
             account_id=self.account_def.id,
             storage_path=imsg.parent.path,
             storage_id=int(imsg.UID),
             #
             conversation_id=conversation_id,
             header_message_id=imsg.headers.get('Message-Id')[1:-1],
             references=references,
             #
-            from_contact_id=from_contacts[0].id,
-            to_contact_ids=[c.id for c in to_contacts],
-            cc_contact_ids=[c.id for c in cc_contacts],
-            involves_contact_ids=[c.id for c in involves_contacts],
+            from_contact_id=from_contacts[0][1].id,
+            to_contact_ids=[c.id for k,c in to_contacts],
+            cc_contact_ids=[c.id for k,c in cc_contacts],
+            involves_contact_ids=[c.id for k,c in involves_contacts],
+            #
+            from_contact=dict([ (from_contacts[0][1].id, dict([ ("name",from_contacts[0][1].name), 
+                                                                ("email",from_contacts[0][0]) ]) ) ]),
+            to_contacts=dict([ (c.id , dict([("name" , c.name),("email",k)]) ) for k,c in to_contacts]),
+            cc_contacts=dict([ (c.id , dict([("name" , c.name),("email",k)]) ) for k,c in cc_contacts]),
+            involves_contacts=dict([ (c.id , dict([("name" , c.name),("email",k)]) ) for k,c in involves_contacts]),
             #
             date=datetime.datetime.utcfromtimestamp(timestamp),
             timestamp=timestamp,
             #
             read=r'\Seen' in imsg.flags,
             #
             headers=dict(imsg.headers),
             bodyPart=bodyPart,
             _attachments=attachments
         )
+
         if existing_message:
             cmsg.id = existing_message.id
             # this is ugly, we should really just have the logic above use a
             #  style that allows it to work with new or existing...
             cmsg._data['_rev'] = existing_message.rev
         
         cmsg.store(self.dbs.messages)
         
--- a/server/python/junius/model.py
+++ b/server/python/junius/model.py
@@ -63,17 +63,23 @@ class Message(schema.Document):
     references = WildField()
     
     # canonical contacts
     from_contact_id = schema.TextField()
     to_contact_ids = schema.ListField(schema.TextField())
     cc_contact_ids = schema.ListField(schema.TextField())
     # convenience contacts with enough semantics to not just map it (for now)
     involves_contact_ids = schema.ListField(schema.TextField())
-    
+
+    # actual contact objects, a little duplication; but for good, not evil
+    from_contact = WildField()
+    to_contacts = WildField(default={})
+    cc_contacts = WildField(default={})
+    involves_contacts = WildField(default={})
+
     date = schema.DateTimeField()
     timestamp = schema.IntegerField()
 
     # general attribute info...
     read = schema.BooleanField()
     
     # user-added meta-information
     tags = WildField()
@@ -183,17 +189,17 @@ class Message(schema.Document):
     # 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', '''\
+    by_mailing_list = schema.View('by_list_id', '''\
         function(doc) {
           if (doc.headers && doc.headers["List-Id"]) {
             var parts = doc.headers["List-Id"].match(/([\\W\\w]*)\\s*<(.+)>.*/);
             var values = {"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"]) {