more autoconstraint progress; we need to deal with some XBL issues though; the internal/external stuff is becoming a serious problem and needs to be normalized.
authorAndrew Sutherland <asutherland@asutherland.org>
Sun, 11 Jan 2009 05:55:21 -0800
changeset 4 bf188e8e220c8da301e6c55af5bbfaa0a1bc31d8
parent 3 84f4b77c4e159eefacca9355e31cf30428727fe6
child 5 66f9fe7b2df5025c5c439a4469b3ad08baf07b01
push id1
push userroot
push dateWed, 08 Apr 2009 01:46:05 +0000
more autoconstraint progress; we need to deal with some XBL issues though; the internal/external stuff is becoming a serious problem and needs to be normalized.
client/autocomplete.xml
client/bindings.css
client/bubbles.css
client/bubbles.xml
client/cloda-completers.js
client/index.xhtml
client/md5.js
client/searchResults.css
--- a/client/autocomplete.xml
+++ b/client/autocomplete.xml
@@ -49,17 +49,16 @@
           $(this._resultDiv).hide();
         },
         clearResultsList: function() {
           $(this._resultDiv).empty();
         },
         selectedIndex: null,
         setSelectedIndex: function(aDesiredIndex) {
           var children = this._resultDiv.childNodes;
-          console.log("Index change, cur", this.selectedIndex, "desired", aDesiredIndex, "length", children.length);
           if (children.length == 0)
             aDesiredIndex = null;
           else if (aDesiredIndex === undefined)
             aDesiredIndex = 0;
           else if (aDesiredIndex < 0)
             aDesiredIndex = 0;
           else if (aDesiredIndex >= children.length)
             aDesiredIndex = children.length - 1;
@@ -68,86 +67,107 @@
             return;
           
           if (this.selectedIndex != null)
             $(children[this.selectedIndex]).removeClass(this.SELECTED_CLASS);
           this.selectedIndex = aDesiredIndex;
           if (this.selectedIndex != null)
             $(children[this.selectedIndex]).addClass(this.SELECTED_CLASS);
         },
+        /* ===== Action ===== */
+        actionListener: null,
+        actOnSelected: function() {
+          console.log("Taking action on index", this.selectedIndex);
+          if (this.selectedIndex != null) {
+            var selected = this._resultDiv.childNodes[this.selectedIndex];
+            if (this.actionListener != null)
+              this.actionListener(selected);
+            else
+              console.log("Warning! There was no action listener!", this, this.actionListener);
+          }
+          console.log("Actiong taking done!");
+        },
         
         /* ===== House-keeping ==== */
         xblBindingAttached: function () {
           console.log("autocompleter binding and what not", this);
           this._entry = this.boundElement.getElementById("entry");
           this._resultDiv = $("<div/>").hide().css("position", "absolute")
                              .addClass("auco_results")
                              .appendTo(document.body)[0];
           console.log("done binding");
         },
       })
     ]]></xbl:implementation>
     <xbl:handlers>
       <xbl:handler event="textInput"><![CDATA[
-        console.log("text input!", this, event);
         var text = this._entry.value + event.data;
         this.goFish(text);
       ]]></xbl:handler>
       <xbl:handler event="keydown"><![CDATA[
         if (event.keyIdentifier == "Enter") {
+          this.actOnSelected();
           this.hideResultsList();
         }
         else if (event.keyIdentifier == "Up") {
           if (this.selectedIndex == 0)
-            this.setSelectedInex(null);
+            this.setSelectedIndex(null);
           else
             this.setSelectedIndex(this.selectedIndex - 1);
         }
         else if (event.keyIdentifier == "Down") {
           if (this.selectedIndex == null)
             this.setSelectedIndex(0);
           else
             this.setSelectedIndex(this.selectedIndex + 1);
         }
+        else if (event.keyIdentifier == "\u001b") {
+          this.hideResultsList();
+        }
       ]]></xbl:handler>
       <xbl:handler event="blur"><![CDATA[
       ]]></xbl:handler>
 
     </xbl:handlers>
   </xbl:binding>
   <!-- Contact -->
   <xbl:binding id="contact-completion">
     <xbl:template>
       <img id="picture" class="contactpic"/>
-      <span id="name"></span>
+      <span id="name"></span><br />
       <span id="emails"></span>
     </xbl:template>
     <xbl:resources>
       <xbl:style><![CDATA[
         .contactpic {
           width: 32;
           height: 32;
+          float: left;
         }
       ]]></xbl:style>
     </xbl:resources>
     <xbl:implementation><![CDATA[
       ({
+        type: "contact",
         contact: null,
         setContact: function(aContact) {
           this._contact = aContact;
           console.log("setContact", this);
           this._nName.textContent = this._contact.name;
           var bestEmail = null;
           var emailText = this._contact.identities.map(function (identity) {
             if (identity.kind == "email")
               bestEmail = identity.value;
             return identity.value;
           }).join(", ");
           this._nEmails.textContent = emailText;
           if (bestEmail) {
+            this._nPicture.setAttribute("src",
+              "http://www.gravatar.com/avatar/" + hex_md5(bestEmail) +
+              ".jpg?r=pg&d=identicon&s=32");
             // XXX soon, let's point at the gravatar
             // XXX later, let's use a pic if it exists, or gravatar push if it
             //  doesn't.  (or maybe have a daemon that takes care of it?)
           }
         },
         xblBindingAttached: function () {
           console.log("attaching contact result");
           this.boundElement._nPicture = this.boundElement.getElementById("picture");
--- a/client/bindings.css
+++ b/client/bindings.css
@@ -1,11 +1,15 @@
 #autocomplete {
   binding: url("autocomplete.xml#autocomplete");
 }
 
+#constraints {
+  binding: url("bubbles.xml#constraint-list");
+}
+
 .message {
   binding: url("messages.xml#message");
 }
 
 .auco_contact {
   binding: url("autocomplete.xml#contact-completion");
 }
new file mode 100644
--- /dev/null
+++ b/client/bubbles.css
@@ -0,0 +1,26 @@
+.bubble {
+  -moz-border-radius: 4px;
+  margin: 0px 2px 0px 0px;
+  padding: 0.1em 0.5em;
+  color: black !important;
+}
+
+.bubble[type="contact"] {
+  background-color: #FFAA78;
+  border: 1px solid #FF7D31;
+}
+
+.bubble[type="contact-collection"] {
+  background-color: #FFC357;
+  border: 1px solid #FFAB0F;
+}
+
+.bubble[type="folder"] {
+  background-color: #faf0b8;
+  border: 1px solid #b19065;
+}
+
+.bubble[type="tag"] {
+  background-color: #eeeeff;
+  border: 1px solid #8888cc;
+}
new file mode 100644
--- /dev/null
+++ b/client/bubbles.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xbl:xbl
+  xmlns="http://www.w3.org/1999/xhtml"
+  xmlns:xbl="http://www.w3.org/ns/xbl">
+
+  <xbl:binding id="constraint-list">
+    <xbl:template>
+      <div id="holder">
+      </div>
+    </xbl:template>
+    <xbl:implementation><![CDATA[
+      ({
+        _nConstraints: null,
+        addContact: function(aContact) {
+          var node = $("<div/>").addClass("bubble")[0];
+          ElementXBL.prototype.addBinding.call(node, "bubbles.xml#constraint-contact");
+          node.setContact(aContact);
+          $(this._nConstraints).append(node);
+          console.log("Contact", aContact, "added");
+        },
+        init: function() {
+          this._nConstraints = this.getElementById("holder");
+        },
+        xblBindingAttached: function () {
+          this.boundElement.init();
+        },
+      })
+    ]]></xbl:implementation>
+    <xbl:handlers>
+      <xbl:handler event="click"><![CDATA[
+      ]]></xbl:handler>
+    </xbl:handlers>
+  </xbl:binding>
+
+  <xbl:binding id="constraint-contact">
+    <xbl:template>
+      <img id="picture" class="con_contactpic"/>
+      <span id="name"></span>
+    </xbl:template>
+    <xbl:resources>
+      <xbl:style><![CDATA[
+        .con_contactpic {
+          width: 18;
+          height: 18;
+        }
+      ]]></xbl:style>
+    </xbl:resources>
+    <xbl:implementation><![CDATA[
+      ({
+        contact: null,
+        setContact: function(aContact) {
+          this.contact = aContact;
+          console.log("setContact", this);
+          this.getElementById("name").textContent = this.contact.name;
+          var bestEmail = null;
+          var emailText = this.contact.identities.forEach(function (identity) {
+            if (identity.kind == "email")
+              bestEmail = identity.value;
+          });
+          if (bestEmail) {
+            this.getElementById("picture").setAttribute("src",
+              "http://www.gravatar.com/avatar/" + hex_md5(bestEmail) +
+              ".jpg?r=pg&d=identicon&s=18");
+          }
+        },
+      })
+    ]]></xbl:implementation>
+  </xbl:binding>
+</xbl:xbl>
--- a/client/cloda-completers.js
+++ b/client/cloda-completers.js
@@ -6,17 +6,17 @@ var ContactCompleter = {
       endkey: aText + "\u9999",
       include_docs: true,
       limit: 10,
       success: function(result) {
         var seen = {};
         var nodes = [];
         result.rows.forEach(function (row) {
           if (!(row.id in seen)) {
-            node = $("<div/>").addClass("auco_contact")[0];
+            var node = $("<div/>").addClass("auco_contact")[0];
             ElementXBL.prototype.addBinding.call(node, "autocomplete.xml#contact-completion");
             node.setContact(row.doc);
             nodes.push(node);
 
             seen[row.id] = true;
           }
         });
         console.log("Want to tell dude about:", aAutocomplete);
--- a/client/index.xhtml
+++ b/client/index.xhtml
@@ -2,29 +2,43 @@
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
 <head>
   <title>Junius</title>
   <script src="/_utils/script/json2.js"></script>
   <script src="/_utils/script/jquery.js?1.2.6"></script>
   <script src="/_utils/script/jquery.couch.js?0.9.0"></script>
   <script src="xbl-src.js"></script>
+  <script src="md5.js"></script>
   <link rel="stylesheet" href="bindings.css" type="text/css"></link>
   <link rel="stylesheet" href="autocomplete.css" type="text/css"></link>
+  <link rel="stylesheet" href="bubbles.css" type="text/css"></link>
+  <link rel="stylesheet" href="searchResults.css" type="text/css"></link>
   <script src="cloda.js"></script>
   <script src="cloda-completers.js"></script>
   <script type="text/javascript" charset="utf-8"><![CDATA[
     function funkyInit() {
       var autocompleter = document.getElementById("autocomplete");
       console.log("autocompleter", autocompleter);
       autocompleter.addCompleter(ContactCompleter);
+      console.log("contact completer registered");
+      
+      var constraints = document.getElementById("constraints");
+      autocompleter.actionListener = function (item) {
+        console.log("!!!Action Listener!!!", item);
+        if (item.type == "contact")
+          constraints.addContact(item.contact);
+        else
+          return;
+      };
+      console.log("constrainer registered");
     }
 
     $(function() {
-      window.setTimeout(funkyInit, 100);
+      window.setTimeout(funkyInit, 1000);
       //DocumentXBL.prototype.loadBindingDocument.call(document, "messages.xml");
       /*
       var dbMessages = $.couch.db("messages");
       dbMessages.view("by_timestamp/by_timestamp", {
         include_docs: true,
         success: function(json) {
           var parent = $("#messages");
           json.rows.forEach(function(row) {
@@ -39,12 +53,13 @@
         }
       })
       */
     });
   ]]></script>
 </head>
 <body>
   <div id="autocomplete"></div>
+  <div id="constraints"/>
   <div id="messages">
   </div>
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/client/md5.js
@@ -0,0 +1,256 @@
+/*
+ * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
+ * Digest Algorithm, as defined in RFC 1321.
+ * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002.
+ * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
+ * Distributed under the BSD License
+ * See http://pajhome.org.uk/crypt/md5 for more info.
+ */
+
+/*
+ * Configurable variables. You may need to tweak these to be compatible with
+ * the server-side, but the defaults work in most cases.
+ */
+var hexcase = 0;  /* hex output format. 0 - lowercase; 1 - uppercase        */
+var b64pad  = ""; /* base-64 pad character. "=" for strict RFC compliance   */
+var chrsz   = 8;  /* bits per input character. 8 - ASCII; 16 - Unicode      */
+
+/*
+ * These are the functions you'll usually want to call
+ * They take string arguments and return either hex or base-64 encoded strings
+ */
+function hex_md5(s){ return binl2hex(core_md5(str2binl(s), s.length * chrsz));}
+function b64_md5(s){ return binl2b64(core_md5(str2binl(s), s.length * chrsz));}
+function str_md5(s){ return binl2str(core_md5(str2binl(s), s.length * chrsz));}
+function hex_hmac_md5(key, data) { return binl2hex(core_hmac_md5(key, data)); }
+function b64_hmac_md5(key, data) { return binl2b64(core_hmac_md5(key, data)); }
+function str_hmac_md5(key, data) { return binl2str(core_hmac_md5(key, data)); }
+
+/*
+ * Perform a simple self-test to see if the VM is working
+ */
+function md5_vm_test()
+{
+  return hex_md5("abc") == "900150983cd24fb0d6963f7d28e17f72";
+}
+
+/*
+ * Calculate the MD5 of an array of little-endian words, and a bit length
+ */
+function core_md5(x, len)
+{
+  /* append padding */
+  x[len >> 5] |= 0x80 << ((len) % 32);
+  x[(((len + 64) >>> 9) << 4) + 14] = len;
+
+  var a =  1732584193;
+  var b = -271733879;
+  var c = -1732584194;
+  var d =  271733878;
+
+  for(var i = 0; i < x.length; i += 16)
+  {
+    var olda = a;
+    var oldb = b;
+    var oldc = c;
+    var oldd = d;
+
+    a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
+    d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
+    c = md5_ff(c, d, a, b, x[i+ 2], 17,  606105819);
+    b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
+    a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
+    d = md5_ff(d, a, b, c, x[i+ 5], 12,  1200080426);
+    c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
+    b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
+    a = md5_ff(a, b, c, d, x[i+ 8], 7 ,  1770035416);
+    d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
+    c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
+    b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
+    a = md5_ff(a, b, c, d, x[i+12], 7 ,  1804603682);
+    d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
+    c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
+    b = md5_ff(b, c, d, a, x[i+15], 22,  1236535329);
+
+    a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
+    d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
+    c = md5_gg(c, d, a, b, x[i+11], 14,  643717713);
+    b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
+    a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
+    d = md5_gg(d, a, b, c, x[i+10], 9 ,  38016083);
+    c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
+    b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
+    a = md5_gg(a, b, c, d, x[i+ 9], 5 ,  568446438);
+    d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
+    c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
+    b = md5_gg(b, c, d, a, x[i+ 8], 20,  1163531501);
+    a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
+    d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
+    c = md5_gg(c, d, a, b, x[i+ 7], 14,  1735328473);
+    b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);
+
+    a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
+    d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
+    c = md5_hh(c, d, a, b, x[i+11], 16,  1839030562);
+    b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
+    a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
+    d = md5_hh(d, a, b, c, x[i+ 4], 11,  1272893353);
+    c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
+    b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
+    a = md5_hh(a, b, c, d, x[i+13], 4 ,  681279174);
+    d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
+    c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
+    b = md5_hh(b, c, d, a, x[i+ 6], 23,  76029189);
+    a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
+    d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
+    c = md5_hh(c, d, a, b, x[i+15], 16,  530742520);
+    b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);
+
+    a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
+    d = md5_ii(d, a, b, c, x[i+ 7], 10,  1126891415);
+    c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
+    b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
+    a = md5_ii(a, b, c, d, x[i+12], 6 ,  1700485571);
+    d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
+    c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
+    b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
+    a = md5_ii(a, b, c, d, x[i+ 8], 6 ,  1873313359);
+    d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
+    c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
+    b = md5_ii(b, c, d, a, x[i+13], 21,  1309151649);
+    a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
+    d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
+    c = md5_ii(c, d, a, b, x[i+ 2], 15,  718787259);
+    b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);
+
+    a = safe_add(a, olda);
+    b = safe_add(b, oldb);
+    c = safe_add(c, oldc);
+    d = safe_add(d, oldd);
+  }
+  return Array(a, b, c, d);
+
+}
+
+/*
+ * These functions implement the four basic operations the algorithm uses.
+ */
+function md5_cmn(q, a, b, x, s, t)
+{
+  return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
+}
+function md5_ff(a, b, c, d, x, s, t)
+{
+  return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
+}
+function md5_gg(a, b, c, d, x, s, t)
+{
+  return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
+}
+function md5_hh(a, b, c, d, x, s, t)
+{
+  return md5_cmn(b ^ c ^ d, a, b, x, s, t);
+}
+function md5_ii(a, b, c, d, x, s, t)
+{
+  return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
+}
+
+/*
+ * Calculate the HMAC-MD5, of a key and some data
+ */
+function core_hmac_md5(key, data)
+{
+  var bkey = str2binl(key);
+  if(bkey.length > 16) bkey = core_md5(bkey, key.length * chrsz);
+
+  var ipad = Array(16), opad = Array(16);
+  for(var i = 0; i < 16; i++)
+  {
+    ipad[i] = bkey[i] ^ 0x36363636;
+    opad[i] = bkey[i] ^ 0x5C5C5C5C;
+  }
+
+  var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz);
+  return core_md5(opad.concat(hash), 512 + 128);
+}
+
+/*
+ * Add integers, wrapping at 2^32. This uses 16-bit operations internally
+ * to work around bugs in some JS interpreters.
+ */
+function safe_add(x, y)
+{
+  var lsw = (x & 0xFFFF) + (y & 0xFFFF);
+  var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
+  return (msw << 16) | (lsw & 0xFFFF);
+}
+
+/*
+ * Bitwise rotate a 32-bit number to the left.
+ */
+function bit_rol(num, cnt)
+{
+  return (num << cnt) | (num >>> (32 - cnt));
+}
+
+/*
+ * Convert a string to an array of little-endian words
+ * If chrsz is ASCII, characters >255 have their hi-byte silently ignored.
+ */
+function str2binl(str)
+{
+  var bin = Array();
+  var mask = (1 << chrsz) - 1;
+  for(var i = 0; i < str.length * chrsz; i += chrsz)
+    bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32);
+  return bin;
+}
+
+/*
+ * Convert an array of little-endian words to a string
+ */
+function binl2str(bin)
+{
+  var str = "";
+  var mask = (1 << chrsz) - 1;
+  for(var i = 0; i < bin.length * 32; i += chrsz)
+    str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask);
+  return str;
+}
+
+/*
+ * Convert an array of little-endian words to a hex string.
+ */
+function binl2hex(binarray)
+{
+  var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
+  var str = "";
+  for(var i = 0; i < binarray.length * 4; i++)
+  {
+    str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) +
+           hex_tab.charAt((binarray[i>>2] >> ((i%4)*8  )) & 0xF);
+  }
+  return str;
+}
+
+/*
+ * Convert an array of little-endian words to a base-64 string
+ */
+function binl2b64(binarray)
+{
+  var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+  var str = "";
+  for(var i = 0; i < binarray.length * 4; i += 3)
+  {
+    var triplet = (((binarray[i   >> 2] >> 8 * ( i   %4)) & 0xFF) << 16)
+                | (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 )
+                |  ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF);
+    for(var j = 0; j < 4; j++)
+    {
+      if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
+      else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
+    }
+  }
+  return str;
+}
new file mode 100644
--- /dev/null
+++ b/client/searchResults.css
@@ -0,0 +1,109 @@
+
+/*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;
+     }
+
+/* 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;  }
+[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 */
+              }
+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: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; }
+conversation > .target { cursor: pointer; }
+
+/* CHECKBOX */
+.checkbox { float: left; text-align: center; width: 24px; height: 24px; 
+            padding: 0.1em 0.2em }
+.checkbox > input { vertical-align: middle; }
+
+/* HEADER */
+.header { margin-bottom: 0.5em; }
+.header .meta { float: right; padding-left: 2em; text-align: right; color: #999; font-size: 90%; }
+.header .meta .attachments { padding-right: 18px; background: url("chrome://messenger/skin/icons/attachment.png") transparent no-repeat center right; display: none; }
+.header .meta .attachments[count] { display: inline; }
+.header .meta .attachments:before { content: "("; }
+.header .meta .attachments:after { content: ")"; }
+.header .addressing .verb { font-size: 90%; color: #777; }
+.header .addressing .date { color: #999; font-size: 90%; }
+.header .addressing .date:before { content: "\2014  "; }
+
+.recipients { display: inline; color: #222; }
+.recipients identity:first-child:before { content: ""; }
+.recipients identity:after { content: ", "; }
+.recipients identity:last-child:after { content: ""; }
+/* this will limit the number of recipients shown to the first 7 
+   also adding an elipsis '...' after the 6th (if exists) */
+.recipients identity:nth-child(n+7) { display: none; }
+.recipients identity:nth-child(0n+6):after { content: "..."; }
+.recipients .you { }
+
+conversation > .target > .header > .subject { white-space: nowrap; overflow: hidden; 
+                                              font-size: 115%; font-weight: bold; 
+                                              color: #555; }
+conversation > .target > .body { color: #555; padding-left: 1em; }
+
+conversation > .target > .tags span { -moz-border-radius: 0.3em; padding: 0em 0.4em; }
+conversation > .target > .folder { background-color: #faf0b8; border: 1px solid #ede4af; }
+
+/* REPLIES */
+.replies { padding: 0em 3em; }
+
+/* REPLY */
+reply { display: list-item; list-style: none; margin: 0.5em 0em; }
+reply:hover { list-style: none outside url("chrome://messenger/skin/icons/arrow/arrow-right.png"); }
+reply { cursor: pointer; }
+
+reply > .author > .date { color: #adb0a9; font-size: 90%; }
+reply > .author > .date:before { content: "\2014 "; }
+reply > .body { padding-left: 1em; color: #666; white-space: nowrap; overflow: hidden;  }
+
+/* UNKNOWN SENDERS */
+/* !inAddressBook && !inRemoteImageLoaded */
+conversation[sender="unknown"] { }
+conversation[sender="unknown"] .body { display: none; }   
+
+/* UNREAD CONVERSATIONS */      
+conversation[unread="true"] { background-color: #fff; }
+conversation[unread="true"] > .target > .header > .subject { color: #111; }
+conversation[unread="true"] > .target > .header { color: #111; }
+
+/* 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; }