Merge in the jquery-tmpl branch, cause we want to go that way.
Merge in the jquery-tmpl branch, cause we want to go that way.
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;