Merge in the jquery-tmpl branch, cause we want to go that way.
authorBlake Winton <bwinton@latte.ca>
Wed, 09 Jun 2010 16:24:12 -0400
changeset 46 3f7d9dcc56091ffb0ed08f6ddd0e13df58996a15
parent 42 2c86ef7e243a154cb4b3d49c1a459e401e0c256f (current diff)
parent 45 0c5e7509c4910db6bdfc0b75ff20c0c3c5f779d2 (diff)
child 49 1d143a869bb02d97dac274478c626772a9026d17
push id40
push userbwinton@latte.ca
push dateWed, 09 Jun 2010 20:28:49 +0000
Merge in the jquery-tmpl branch, cause we want to go that way.
content/hometab.xhtml
new file mode 100644
--- /dev/null
+++ b/.hgpatchinfo/jquery-tmpl.dep
@@ -0,0 +1,1 @@
+2c86ef7e243a154cb4b3d49c1a459e401e0c256f
\ No newline at end of file
--- a/content/hometab-browser.js
+++ b/content/hometab-browser.js
@@ -52,27 +52,22 @@ function addCategories(data) {
 
 function clearContent() {
   $("#preview").html("");
 }
 
 function setFolders(folders) {
   clearContent();
   let content = $("<ol class='folders'/>").appendTo($("#preview"));
-  for (let index in folders) {
-    let folder = folders[index];
-    let li = $("<li class='folder'/>");
-    li.addClass((folder.unread > 0) ? "unread" : "read");
-    li.attr("id", folder.id);
-    li.append($("<span class='name'/>").text(folder.name + " "));
-    if (folder.unread > 0)
-      li.append($("<span class='count'/>").text(folder.unread));
-    content.append(li);
-    li.bind("click", function (e) {showConversations($(this))});
-  }
+  $.each(folders, function(i, e) {
+    e.count = e.unread || "";
+    e.extraClass = e.unread > 0 ? "unread" : "read";
+  });
+  $("#foldertmpl").render(folders).appendTo(content);
+  content.children("li").click(function (e) {showConversations($(this))});
 }
 
 function addContent(data) {
   let conversations = $("ol.conversations");
   if (conversations.length == 0)
     conversations = $('<ol class="conversations"/>').appendTo($("#preview"))
 
   let entry = $('<li class="conversation"/>').appendTo(conversations);
@@ -94,63 +89,46 @@ function addContent(data) {
 
   for (let unread in data["unread"])
     addMessage(messages, data["unread"][unread]).addClass("unread");
   for (let read in data["messages"])
     addMessage(messages, data["messages"][read]).addClass("read");
   entry.bind("click", function (e) {showMessages($(this))});
 }
 
-function getMessageBody(aMessageHeader) {
-  try {
-  let messenger = Components.classes["@mozilla.org/messenger;1"]
-                            .createInstance(Components.interfaces.nsIMessenger);
-  let listener = Components.classes["@mozilla.org/network/sync-stream-listener;1"]
-                           .createInstance(Components.interfaces.nsISyncStreamListener);
-  let uri = aMessageHeader.folder.getUriForMsg(aMessageHeader);
-  messenger.messageServiceFromURI(uri)
-           .streamMessage(uri, listener, null, null, false, "");
-  let folder = aMessageHeader.folder;
-  return folder.getMsgTextFromStream(listener.inputStream,
-                                     aMessageHeader.Charset,
-                                     65536,
-                                     32768,
-                                     false,
-                                     true,
-                                     { });
-  } catch (e) {
-    dump("Caught error in getMessageBody.  e="+e+"\n");
-    return "";
-  }
-}
-
 function addMessage(message) {
   let conversations = $("ol.conversations");
   if (conversations.length == 0)
     conversations = $('<ol class="conversations"/>').appendTo($("#preview"))
 
   let entry = $('<li class="conversation"/>').appendTo(conversations);
 
   entry.addClass(message.read ? "read" : "unread");
   entry.attr("id", message.id);
 
   let msg = $('<li class="message"/>').appendTo(entry);
   $('<span class="from"/>').text(message.from.value).appendTo(msg);
   $('<span class="date"/>').text(""+message.date).appendTo(msg);
 
-  let body = getMessageBody(message.folderMessage);
-  $('<div class="fullbody"/>').appendTo(msg).text(body).css("display", "none");
-  let synopsis = body;
-  if (message.indexedBodyText)
-    synopsis = message.indexedBodyText;
-  $('<div class="synopsis">').appendTo(msg).text(synopsis.substr(0, 140));
+  let body = $('<div class="fullbody"/>').appendTo(msg).css("display", "none");
+  let synopsis = $('<div class="synopsis">').appendTo(msg);
+  if (message.indexedBodyText) {
+    synopsis.text(message.indexedBodyText.substr(0, 140));
+    synopsis = null;
+  }
+//  populateMessageBody(message.folderMessage, body, synopsis);
 
   msg.bind("click", function (e) {showMessage($(this))});
 }
 
+function populateMessageBody(id, data) {
+  let body = $("#"+id).find(".fullbody");
+  body.html(data.documentElement.children);
+}
+
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 const Cu = Components.utils;
 
 /**
  * addEventListener betrayals compel us to establish our link with the
  *  outside world from inside.  NeilAway suggests the problem might have
rename from content/hometab.xhtml
rename to content/hometab.html
--- a/content/hometab.xhtml
+++ b/content/hometab.html
@@ -31,46 +31,49 @@
   - use your version of this file under the terms of the MPL, indicate your
   - decision by deleting the provisions above and replace them with the notice
   - and other provisions required by the GPL or the LGPL. If you do not delete
   - the provisions above, a recipient may use your version of this file under
   - the terms of any one of the MPL, the GPL or the LGPL.
   -
   - ***** END LICENSE BLOCK ***** -->
 
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
-  "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" [
-<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
-%brandDTD;
-<!ENTITY % aboutDTD SYSTEM "chrome://global/locale/about.dtd">
-%aboutDTD;
-<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
-%globalDTD;
-<!ENTITY % hometabDTD SYSTEM "chrome://hometab/locale/hometab.dtd">
-%hometabDTD;
-]>
+<!DOCTYPE HTML>
 <html xmlns="http://www.w3.org/1999/xhtml"
-    xmlns:html="http://www.w3.org/1999/xhtml"
-    xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-    version="-//W3C//DTD XHTML 1.1//EN" xml:lang="en">
+      xmlns:html="http://www.w3.org/1999/xhtml"
+      xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+      version="-//W3C//DTD XHTML 1.1//EN" xml:lang="en">
 <head>
   <!-- Themes -->
   <link rel="stylesheet" type="text/css"
       href="chrome://hometab/skin/hometab.css" />
 </head>
 <body dir="&locale.dir;" onload="reachOutAndTouchFrame('home')">
   <div id="results-box">
     <div id="category-box">
       <ol id="categories" onclick="updateTab(event)"/>
     </div>
     <div id="preview-box">
       <div id="preview"/>
     </div>
   </div>
+
 </body>
+
   <!-- Libs -->
   <script type="application/javascript;version=1.8"
       src="chrome://messenger/content/jquery.js"></script>
   <script type="application/javascript;version=1.8"
+      src="chrome://hometab/content/jquery.tmpl.js"></script>
+  <script type="application/javascript;version=1.8"
       src="chrome://messenger/content/protovis-r2.6-modded.js"></script>
   <script type="application/javascript;version=1.8"
       src="chrome://hometab/content/hometab-browser.js"></script>
+
+  <!-- Templates!  Woo! -->
+  <script id="foldertmpl" type="text/html">
+    <li class="folder ${extraClass}" id="${id}">
+      <span class="name">${name}</span>
+      <span class="count">${count}</span>
+    </li>
+  </script>
+
 </html>
--- a/content/hometab.js
+++ b/content/hometab.js
@@ -30,22 +30,26 @@
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
-Components.utils.import("resource://app/modules/virtualFolderWrapper.js");
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://app/modules/virtualFolderWrapper.js");
 
 // -- Import modules we need.
-Components.utils.import("resource://app/modules/gloda/public.js");
-Components.utils.import("resource://app/modules/MailUtils.js");
-Components.utils.import("resource://app/modules/errUtils.js");
+Cu.import("resource://app/modules/gloda/public.js");
+Cu.import("resource://app/modules/MailUtils.js");
+Cu.import("resource://app/modules/errUtils.js");
 
 var hometab = {
 
   _modes: {"Unread": function ht_unread() {
             let map = [];
             let currentFolder = gFolderTreeView.getSelectedFolders()[0];
             const outFolderFlagMask = nsMsgFolderFlags.SentMail |
               nsMsgFolderFlags.Drafts | nsMsgFolderFlags.Queue |
@@ -149,22 +153,92 @@ var hometab = {
     // The following call fails because glodaList isn't a recognized
     // tab mode for some reason.
     tabmail.openTab("messageList", {
       id: id,
       title: subject,
     });
   },
 
+  tempFolder: null,
+
+  populateMessageBody: function populateMessageBody(aMessageHeader) {
+    try {
+    let messenger = Cc["@mozilla.org/messenger;1"]
+                      .createInstance(Ci.nsIMessenger);
+    let listener = Cc["@mozilla.org/network/sync-stream-listener;1"]
+                     .createInstance(Ci.nsISyncStreamListener);
+    let uri = aMessageHeader.folderMessageURI;
+    let self = this;
+    let neckoURL = {};
+    messenger.messageServiceFromURI(uri).GetUrlForUri(uri, neckoURL, null);
+    let iframe = document.createElementNS(
+        "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+        "iframe");
+    iframe.setAttribute("type", "content");
+    //iframe.setAttribute("display", "hidden");
+    iframe.setAttribute("id", aMessageHeader.id);
+    /* The xul:iframe automatically loads about:blank when it is added
+     * into the tree. We need to wait for the document to be loaded before
+     * doing things.
+     *
+     * Why do we do that ? Basically because we want the <xul:iframe> to
+     * have a docShell and a webNavigation. If we don't do that, and we
+     * set directly src="about:blank" in the XML above, sometimes we are
+     * too fast and the docShell isn't ready by the time we get there. */
+    iframe.addEventListener("load", function f_temp2(event) {
+      iframe.removeEventListener("load", f_temp2, true);
+
+      /* The second load event is triggered by loadURI with the URL
+       * being the necko URL to the given message. */
+      iframe.addEventListener("load", function f_temp1(event) {
+        iframe.removeEventListener("load", f_temp1, true);
+
+        /* The part below is all about quoting */
+        let iframeDoc = iframe.contentDocument;
+        self.tempFolder = iframeDoc;
+        self.conversationDoc.populateMessageBody(aMessageHeader.id, iframeDoc);
+        /* And remove ourselves. */
+        document.getElementById("mailContent").removeChild(iframe);
+
+        /* Here ends the chain of event listeners, nothing happens
+         * after this. */
+        }, true); /* end document.addEventListener */
+
+      let url = neckoURL.value;
+
+      let cv = iframe.docShell.contentViewer;
+      cv.QueryInterface(Ci.nsIMarkupDocumentViewer);
+      cv.hintCharacterSet = "UTF-8";
+      cv.hintCharacterSetSource = 10; // kCharsetFromMetaTag
+      iframe.docShell.appType = Ci.nsIDocShell.APP_TYPE_MAIL;
+      iframe.webNavigation.loadURI(url.spec+"?header=quotebody",
+                                   iframe.webNavigation.LOAD_FLAGS_IS_LINK,
+                                   null, null, null);
+    }, true); /* end document.addEventListener */
+    document.getElementById("mailContent").appendChild(iframe);
+    } catch (e) {
+      dump("\n\nCaught error in populateMessageBody.  e="+e+"\n");
+      dump(e.stack);
+      dump("\n");
+    }
+  },
+
+  addMessages: function addMessages(aDoc, aMessages) {
+    for (var i in aMessages) {
+      aDoc.addMessage(aMessages[i]);
+      this.populateMessageBody(aMessages[i]);
+    }
+  },
+
   showMessagesInConversation: function showMessagesInConversation(aTab, flag) {
     let doc = hometab.conversationDoc;
     let id = aTab.id;
     if (aTab.results != null) {
-      for (var id in aTab.results)
-        doc.addMessage(aTab.results[id]);
+      this.addMessages(doc, aTab.results);
       return;
     }
     let self = this;
     let query = Gloda.newQuery(Gloda.NOUN_MESSAGE);
     query.conversation(id)
     query.orderBy("date");
     query.getCollection({
       onItemsAdded: function _onItemsAdded(aItems, aCollection) {
@@ -173,20 +247,20 @@ var hometab = {
       },
       onItemsRemoved: function _onItemsRemoved(aItems, aCollection) {
       },
       /* called when our database query completes */
       onQueryCompleted: function _onQueryCompleted(messages) {
         aTab.results = [];
         for (var i in messages.items)
           aTab.results.push(messages.items[i]);
-        for (var i in aTab.results)
-          doc.addMessage(aTab.results[i]);
+        self.addMessages(doc, aTab.results);
       }});
   },
+
 };
 
 var homeTabType = {
   // TabType attributes
   name: "HomeTab",
   panelId: "mailContent",
   modes: {
     // "home" tab type.
@@ -250,64 +324,64 @@ var homeTabType = {
       },
     },
 
     // "folderList" tab type.
     folderList: {
       type: "folderList",
       isDefault: false,
 
-      openTab: function ml_openTab(aTab, aArgs) {
+      openTab: function fl_openTab(aTab, aArgs) {
         let folder = MailUtils.getFolderForURI(aArgs.id, true);
         aTab.title = folder.prettyName;
         aTab.id = aArgs.id;
         window.title = aTab.title;
         document.getElementById("browser").hidden = true;
         document.getElementById("conversation").hidden = true;
         document.getElementById("folder").hidden = false;
         hometab.folderDoc.clearContent();
         hometab.showConversationsInFolder(aTab, folder)
       },
 
-      htmlLoadHandler: function ml_htmlLoadHandler(doc) {
+      htmlLoadHandler: function fl_htmlLoadHandler(doc) {
         hometab.folderDoc = doc;
       },
 
-      showTab: function ml_showTab(aTab) {
+      showTab: function fl_showTab(aTab) {
         window.title = aTab.title;
         document.getElementById("browser").hidden = true;
         document.getElementById("conversation").hidden = true;
         document.getElementById("folder").hidden = false;
         hometab.folderDoc.clearContent();
         let folder = MailUtils.getFolderForURI(aTab.id, true);
         hometab.showConversationsInFolder(aTab, folder);
       },
 
-      onTitleChanged: function ml_onTitleChanged(aTab) {
+      onTitleChanged: function fl_onTitleChanged(aTab) {
         window.title = aTab.title;
       },
-      closeTab: function ml_closeTab(aTab) {
+      closeTab: function fl_closeTab(aTab) {
       },
-      saveTabState: function ml_saveTabState(aTab) {
+      saveTabState: function fl_saveTabState(aTab) {
       },
-      persistTab: function ml_persistTab(aTab) {
+      persistTab: function fl_persistTab(aTab) {
       },
-      restoreTab: function ml_restoreTab(aTabmail, aPersistedState) {
+      restoreTab: function fl_restoreTab(aTabmail, aPersistedState) {
       },
-      supportsCommand: function ml_supportsCommand(aCommand, aTab) {
+      supportsCommand: function fl_supportsCommand(aCommand, aTab) {
         return false;
       },
-      isCommandEnabled: function ml_isCommandEnabled(aCommand, aTab) {
+      isCommandEnabled: function fl_isCommandEnabled(aCommand, aTab) {
         return false;
       },
-      doCommand: function ml_doCommand(aCommand, aTab) {
+      doCommand: function fl_doCommand(aCommand, aTab) {
       },
-      onEvent: function ml_onEvent(aEvent, aTab) {
+      onEvent: function fl_onEvent(aEvent, aTab) {
       },
-      getBrowser: function ml_getBrowser(aCommand, aTab) {
+      getBrowser: function fl_getBrowser(aCommand, aTab) {
         return null;
       },
     },
 
     // "messageList" tab type.
     messageList: {
       type: "messageList",
       isDefault: false,
--- a/content/hometab.xul
+++ b/content/hometab.xul
@@ -46,23 +46,24 @@
       <box id="mailContent" orient="vertical" flex="1">
         <!--<textbox id="searchInput"-->
         <!--         searchCriteria="true"-->
         <!--         type="search"-->
         <!--         searchbutton="true"-->
         <!--         autocompletesearch="gloda"-->
         <!--         autocompletepopup="PopupGlodaAutocomplete"-->
         <!--         />-->
-        <!--<progressmeter class="progressmeter-statusbar"-->
-        <!--               id="statusbar-icon"-->
-        <!--               mode="normal"-->
-        <!--               value="0"-->
-        <!--               hidden="true"/>-->
+        <!-- We need the progressmeter or the tabs won't work correctly. -->
+        <progressmeter class="progressmeter-statusbar"
+                       id="statusbar-icon"
+                       mode="normal"
+                       value="0"
+                       hidden="true"/>
         <browser id="browser" flex="1" disablehistory="true"
-                 src="chrome://hometab/content/hometab.xhtml"/>
+                 src="chrome://hometab/content/hometab.html"/>
         <browser id="folder" flex="1" hidden="true" disablehistory="true"
                  src="chrome://hometab/content/folderView.xhtml"/>
         <browser id="conversation" flex="1" hidden="true" disablehistory="true"
                  src="chrome://hometab/content/conversationView.xhtml"/>
       </box>
     </tabpanels>
   </tabbrowser>
   <script type="application/javascript"
new file mode 100644
--- /dev/null
+++ b/content/jquery.tmpl.js
@@ -0,0 +1,133 @@
+/*
+ * jQuery Templating Plugin
+ *   NOTE: Created for demonstration purposes.
+ * Copyright 2010, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ */
+(function(jQuery){
+	// Override the DOM manipulation function
+	var oldManip = jQuery.fn.domManip;
+	
+	jQuery.fn.extend({
+		render: function( data ) {
+			return this.map(function(i, tmpl){
+				return jQuery.render( tmpl, data );
+			});
+		},
+		
+		// This will allow us to do: .append( "template", dataObject )
+		domManip: function( args ) {
+			// This appears to be a bug in the appendTo, etc. implementation
+			// it should be doing .call() instead of .apply(). See #6227
+			if ( args.length > 1 && args[0].nodeType ) {
+				arguments[0] = [ jQuery.makeArray(args) ];
+			}
+
+			if ( args.length === 2 && typeof args[0] === "string" && typeof args[1] !== "string" ) {
+				arguments[0] = [ jQuery.render( args[0], args[1] ) ];
+			}
+			
+			return oldManip.apply( this, arguments );
+		}
+	});
+	
+	jQuery.extend({
+		render: function( tmpl, data ) {
+			var fn;
+			
+			// Use a pre-defined template, if available
+			if ( jQuery.templates[ tmpl ] ) {
+				fn = jQuery.templates[ tmpl ];
+				
+			// We're pulling from a script node
+			} else if ( tmpl.nodeType ) {
+				var node = tmpl, elemData = jQuery.data( node );
+				fn = elemData.tmpl || jQuery.tmpl( node.innerHTML );
+			}
+
+			fn = fn || jQuery.tmpl( tmpl );
+			
+			// We assume that if the template string is being passed directly
+			// in the user doesn't want it cached. They can stick it in
+			// jQuery.templates to cache it.
+
+			if ( jQuery.isArray( data ) ) {
+				return jQuery.map( data, function( data, i ) {
+					return fn.call( data, jQuery, data, i );
+				});
+
+			} else {
+				return fn.call( data, jQuery, data, 0 );
+			}
+		},
+		
+		// You can stick pre-built template functions here
+		templates: {},
+
+		/*
+		 * For example, someone could do:
+		 *   jQuery.templates.foo = jQuery.tmpl("some long templating string");
+		 *   $("#test").append("foo", data);
+		 */
+
+		tmplcmd: {
+			each: {
+				_default: [ null, "$i" ],
+				prefix: "jQuery.each($1,function($2){with(this){",
+				suffix: "}});"
+			},
+			if: {
+				prefix: "if($1){",
+				suffix: "}"
+			},
+			else: {
+				prefix: "}else{"
+			},
+			html: {
+				prefix: "_.push(typeof $1==='function'?$1.call(this):$1);"
+			},
+			"=": {
+				_default: [ "this" ],
+				prefix: "_.push($.encode(typeof $1==='function'?$1.call(this):$1));"
+			}
+		},
+
+		encode: function( text ) {
+			return text != null ? document.createTextNode( text.toString() ).nodeValue : "";
+		},
+
+		tmpl: function(str, data, i) {
+			// Generate a reusable function that will serve as a template
+			// generator (and which will be cached).
+			var fn = new Function("jQuery","$data","$i",
+				"var $=jQuery,_=[];_.data=$data;_.index=$i;" +
+
+				// Introduce the data as local variables using with(){}
+				"with($data){_.push('" +
+
+				// Convert the template into pure JavaScript
+				str
+					.replace(/[\r\t\n]/g, " ")
+					.replace(/\${([^}]*)}/g, "{{= $1}}")
+					.replace(/{{(\/?)(\w+|.)(?:\((.*?)\))?(?: (.*?))?}}/g, function(all, slash, type, fnargs, args) {
+						var tmpl = jQuery.tmplcmd[ type ];
+
+						if ( !tmpl ) {
+							throw "Template not found: " + type;
+						}
+
+						var def = tmpl._default;
+
+						return "');" + tmpl[slash ? "suffix" : "prefix"]
+							.split("$1").join(args || def[0])
+							.split("$2").join(fnargs || def[1]) + "_.push('";
+					})
+				+ "');}return $(_.join('')).get();");
+
+			// Provide some basic currying to the user
+			return data ? fn.call( this, jQuery, data, i ) : fn;
+		}
+	});
+})(jQuery);
+
+
--- a/skin/mac/hometab.css
+++ b/skin/mac/hometab.css
@@ -145,24 +145,24 @@ div { border: 1px solid green }
   text-shadow: 0 1px 0 #626d7f;
   font-weight: bold;
   color: HighLightText;
   border-top: 1px solid #5694d3;
   border-bottom: 1px solid #1d6aba;
 }
 
 li#Unread.category[selected="true"] {
-  background-image: url("i/unread.gif"),
+  background-image: url("chrome://messenger/skin/icons/folder-closed.png"),
                     -moz-linear-gradient(top,#6ca4dd,#236ebc);
   background-position: 10px center, center center;
   background-repeat: no-repeat, no-repeat;
 }
 
 li#Folders.category {
-  background-image: url("i/folder.png");
+  background-image: url("chrome://messenger/skin/icons/folder-closed.png");
   background-repeat: no-repeat;
   background-position: 10px center;
   padding-left: 30px;
 }
 
 #preview {
   margin: 0;
 }
@@ -200,24 +200,24 @@ li.folder:hover,
   font-weight: bold;
   text-shadow: 0 1px 0 #626d7f;  
   border-top: 1px solid #5694d3;
   border-bottom: 1px solid #1d6aba;
 }
 
 li.folder.unread {
   font-weight: bold;
-  background-image: url("i/folder.png");
+  background-image: url("chrome://messenger/skin/icons/folder-closed.png");
   background-repeat: no-repeat;
   background-position: 10px center;
   padding-left: 30px;
 }
 
 li.folder.unread:hover {  
-  background-image: url("i/folder.png"),
+  background-image: url("chrome://messenger/skin/icons/folder-closed.png"),
                     -moz-linear-gradient(top,#6ca4dd,#236ebc);
   background-repeat: no-repeat, no-repeat;
   background-position: 10px center, center center;
 }
 
 .conversations {
   margin: 0;
   padding: 0;