Integrate Thunzilla.
authorShawn Wilsher <sdwilsh@shawnwilsher.com>
Wed, 21 Apr 2010 13:50:27 -0700
changeset 52 cb0488c922ad627f7243810a120174e890689217
parent 51 b1e7a7c55ea0c9c872ab8fc62fe31aa0f4910f8d
child 53 e654c64067b55145bf24ae3d849417cc8b0d650d
push id26
push usersdwilsh@shawnwilsher.com
push dateWed, 21 Apr 2010 21:00:51 +0000
Integrate Thunzilla.
content/overlay.js
content/overlay.xul
--- a/content/overlay.js
+++ b/content/overlay.js
@@ -15,84 +15,150 @@
  *
  * The Initial Developer of the Original Code is
  * Mozilla Foundation.
  * Portions created by the Initial Developer are Copyright (C) 2009
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Shawn Wilsher <me@shawnwilsher.com>
+ *   Justin Dolske <dolske@mozilla.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * 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 ***** */
 
-var BugzillaReply = {
+var BugzillaHelper = {
   //////////////////////////////////////////////////////////////////////////////
   //// Constants
 
-  kFromAddress: "bugzilla-daemon@mozilla.org",
-  kSubjectRegEx: /^\[Bug \d+\]/,
   kBugURIRegEx: /^https:\/\/bugzilla.mozilla.org\/show_bug.cgi\?id=(\d+)/,
   kCommentStartRegEx: /^--- Comment #(\d+) from .+ ---$/,
   kCommentEndRegEx: /^--\s+$/,
+  kBugzillaURL: "https://bugzilla.mozilla.org/",
 
   //////////////////////////////////////////////////////////////////////////////
   //// Functions
 
   initialize: function BR_initialize()
   {
     const REPLACE_FUNCTIONS = [
       "MsgReplySender",
       "MsgReplyToAllMessage",
     ];
     // Replace each function with our replacement.
     REPLACE_FUNCTIONS.forEach(function(name) {
       let func = window[name];
 
       // Define our replacement function.
       this[name] = function(aEvent) {
-        if (!BugzillaReply.reply())
+        if (!BugzillaHelper.reply())
           func(aEvent);
       };
 
       // And swap.
       window[name] = this[name];
     });
+
+    const NEEDED_HEADERS = [
+      "x-bugzilla-url",
+      "x-bugzilla-who",
+      "x-bugzilla-component",
+      "x-bugzilla-product",
+      "x-bugzilla-assigned-to",
+      "x-bugzilla-status",
+    ];
+    const HEADERS_PREF = "mailnews.customDBHeaders";
+    // We also need to add the headers we want to mailnews.customDBHeaders
+    // preference so that they are indexed and available to our column handler.
+    let pb = Components.classes["@mozilla.org/preferences-service;1"].
+             getService(Components.interfaces.nsIPrefBranch);
+    let headers = pb.getCharPref(HEADERS_PREF).split(" ");
+    let updated = false;
+    for each (let newPref in NEEDED_HEADERS) {
+      if (headers.some(function(oldPref) oldPref == newPref)) {
+        continue;
+      }
+      headers.push(newPref);
+      updated = true;
+    }
+
+    // Notify the user to rebuild their index if we updated the preferences.
+    if (updated) {
+      pb.setCharPref(HEADERS_PREF, headers.join(" "));
+      BugzillaHelper.notify(
+        "You must rebuild the indexes for each bugmail folder in order for old mail to work with Bugzilla Helper.",
+        "rebuild indexes",
+        [
+          {
+            label: "OK",
+          },
+        ]
+      );
+    }
+
+    // Need to set ourselves up as an observer for creating a dbview to add the
+    // right columns.
+    let os = Components.classes["@mozilla.org/observer-service;1"].
+             getService(Components.interfaces.nsIObserverService);
+    os.addObserver(BugzillaHelper, "MsgCreateDBView", false);
+
+    // Check to see if Thunzilla is installed and offer to uninstall it.
+    let (guid = "thunzilla@dolske.net") {
+      let em = Components.classes["@mozilla.org/extensions/manager;1"].
+               getService(Components.interfaces.nsIExtensionManager);
+      if (em.getInstallLocation(guid)) {
+        // Prompt with a notification box
+        BugzillaHelper.notify(
+          "Bugzilla Helper includes all of the functionality of Thunzilla, and there may be conflicts.",
+          "thunzilla conflict",
+          [
+            {
+              label: "Uninstall",
+              callback: function() {
+                em.uninstallItem(guid);
+                // We have to manually show the extension manager so the user
+                // knows to restart.
+                window.openAddonsMgr();
+              },
+            },
+          ]
+        );
+      }
+    }
   },
 
   reply: function BR_reply()
   {
     // Determine if this is a reply to a bugzilla message.  If it is, we'll
     // intercept this!
     let msgURIs = gFolderDisplay.selectedMessageUris;
     // We only support replying to one message.
     if (msgURIs.length == 1) {
       let msgURI = msgURIs[0];
       let header = messenger.msgHdrFromURI(msgURI);
-      if (header.author == BugzillaReply.kFromAddress &&
-          header.subject.match(BugzillaReply.kSubjectRegEx)) {
+      if (header.getProperty("x-bugzilla-url") == BugzillaHelper.kBugzillaURL) {
         // We will now intercept the normal reply action here.
 
         let account = header.recipients;
-        let body = BugzillaReply.getMessageBody(header);
-        let commentDetails = BugzillaReply.getComment(body);
+        let body = BugzillaHelper.getMessageBody(header);
+        let commentDetails = BugzillaHelper.getComment(body);
         if (commentDetails) {
           let [bug, number, comment] = commentDetails;
-          BugzillaReply.openReply(bug, number, comment);
+          BugzillaHelper.openReply(bug, number, comment);
           return true;
         }
       }
     }
 
     // We did not intercept this reply, so carry on like normal.
     return false;
   },
@@ -126,17 +192,17 @@ var BugzillaReply = {
     let foundEnd = false;
     let bug, commentNumber = 0, emptyLines = 0;
     let commentLines = lines.filter(function(line) {
       // If we have already found the end, we don't want this line.
       if (foundEnd)
         return false;
 
       if (!foundURI) {
-        let match = BugzillaReply.kBugURIRegEx.exec(line);
+        let match = BugzillaHelper.kBugURIRegEx.exec(line);
         if (!match)
           return false;
         bug = match[1];
         foundURI = true;
         return false;
       }
 
       // Two empty lines is the header for a new bug, so track them and check.
@@ -146,26 +212,26 @@ var BugzillaReply = {
         emptyLines++;
       if (emptyLines == 2) {
         foundHeader = true;
         return false;
       }
 
       // If we haven't found the header yet, check for it.
       if (!foundHeader) {
-        let match = BugzillaReply.kCommentStartRegEx.exec(line);
+        let match = BugzillaHelper.kCommentStartRegEx.exec(line);
         if (!match)
           return false;
         commentNumber = match[1];
         foundHeader = true;
         return false;
       }
 
       // We have found our header, so make sure this isn't the end.
-      if (BugzillaReply.kCommentEndRegEx.test(line)) {
+      if (BugzillaHelper.kCommentEndRegEx.test(line)) {
         foundEnd = true;
         return false;
       }
       return true;
     });
 
     if (commentLines.length == 0 || !foundURI || !foundHeader || !foundEnd)
       return null;
@@ -197,11 +263,92 @@ var BugzillaReply = {
     let ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
                        .getService(Components.interfaces.nsIWindowWatcher);
     ww.openWindow(window,
                   "chrome://bugzilla-helper/content/reply.xul",
                   null,
                   "chrome,dialog=yes,resizable,centerscreen",
                   params);
   },
+
+  notify: function BR_notify(aMessage,
+                             aIdentifier,
+                             aButtons)
+  {
+    // Notify the user that an error occurred as well.
+    let wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].
+             getService(Components.interfaces.nsIWindowMediator);
+    let win = wm.getMostRecentWindow("mail:3pane");
+    if (!win)
+      return;
+    let nb = win.document.getElementById("mail-notification-box");
+    nb.appendNotification(aMessage, aIdentifier, "",
+                          nb.PRIORITY_WARNING_MEDIUM, aButtons);
+  },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// nsIObserver
+
+  observe: function BR_observe(aSubject,
+                               aTopic,
+                               aData)
+  {
+    // Helper object for each column.
+    function ColHandler(aHeader, aSpecial)
+    {
+      this._header  = aHeader;
+      this._special = aSpecial;
+    }
+
+    ColHandler.prototype = {
+      _fixupString: function(text) {
+        if (this._header == "x-bugzilla-assigned-to" &&
+            text == "nobody@mozilla.org") {
+          text = "";
+        }
+        else if (this._special == "shorten") {
+          text = text.replace(/@.+$/, "");
+        }
+        else if (this._special == "adjustcase" && text.length) {
+          text = text[0].toUpperCase() + text.substr(1).toLowerCase();
+        }
+        return text;
+      },
+
+      getCellText: function(row, col) {
+        let key = gDBView.getKeyAt(row);
+        let hdr = gDBView.db.GetMsgHdrForKey(key);
+        let text = hdr.getStringProperty(this._header);
+        return this._fixupString(text);
+      },
+
+      getSortStringForRow: function(hdr) {
+        let text = hdr.getStringProperty(this._header);
+        return this._fixupString(text);
+      },
+
+      isString          : function()                { return true; },
+      getCellProperties : function(row, col, props) {              },
+      getRowProperties  : function(row, props)      {              },
+      getImageSrc       : function(row, col)        { return null; },
+      getSortLongForRow : function(hdr)             { return 0;    },
+    }
+
+    // Add the handler for each header we care about.
+    gDBView.addColumnHandler("colBugzillaUser",
+                             new ColHandler("x-bugzilla-who", "shorten"));
+    gDBView.addColumnHandler("colBugzillaUserFull",
+                             new ColHandler("x-bugzilla-who"));
+    gDBView.addColumnHandler("colBugzillaProduct",
+                             new ColHandler("x-bugzilla-product"));
+    gDBView.addColumnHandler("colBugzillaComponent",
+                             new ColHandler("x-bugzilla-component"));
+    gDBView.addColumnHandler("colBugzillaAssignee",
+                             new ColHandler("x-bugzilla-assigned-to",
+                                            "shorten"));
+    gDBView.addColumnHandler("colBugzillaAssigneeFull",
+                             new ColHandler("x-bugzilla-assigned-to"));
+    gDBView.addColumnHandler("colBugzillaStatus",
+                             new ColHandler("x-bugzilla-status", "adjustcase"));
+  },
 };
 
-window.addEventListener("load", BugzillaReply.initialize, false);
+window.addEventListener("load", BugzillaHelper.initialize, false);
--- a/content/overlay.xul
+++ b/content/overlay.xul
@@ -38,9 +38,63 @@
    - ***** END LICENSE BLOCK ***** -->
 
 <overlay id="bugzilla-helper-overlay"
          xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
   <script type="text/javascript"
           src="chrome://bugzilla-helper/content/overlay.js"/>
 
+  <tree id="threadTree">
+    <treecols id="threadCols">
+      <treecol id="colBugzillaUser"
+               persist="hidden ordinal width"
+               currentView="unthreaded"
+               flex="2"
+               label="User"
+               tooltiptext="The short Bugzilla username (email) responsible for the email."/>
+      <splitter class="tree-splitter" />
+      <splitter class="tree-splitter" />
+      <treecol id="colBugzillaUserFull"
+               persist="hidden ordinal width"
+               currentView="unthreaded"
+               flex="2"
+               label="User (Full)"
+               tooltiptext="The full Bugzilla username (email) responsible for the email."/>
+      <splitter class="tree-splitter" />
+      <treecol id="colBugzillaProduct"
+               persist="hidden ordinal width"
+               currentView="unthreaded"
+               flex="2"
+               label="Product"
+               tooltiptext="The bug's Product field."/>
+      <splitter class="tree-splitter" />
+      <treecol id="colBugzillaComponent"
+               persist="hidden ordinal width"
+               currentView="unthreaded"
+               flex="2"
+               label="Component"
+               tooltiptext="The bug's Component field."/>
+      <splitter class="tree-splitter" />
+      <treecol id="colBugzillaAssignee"
+               persist="hidden ordinal width"
+               currentView="unthreaded"
+               flex="2"
+               label="Assignee"
+               tooltiptext="The bug's assignee."/>
+      <splitter class="tree-splitter" />
+      <treecol id="colBugzillaAssigneeFull"
+               persist="hidden ordinal width"
+               currentView="unthreaded"
+               flex="2"
+               label="Assignee (Full)"
+               tooltiptext="The bug's assignee."/>
+      <splitter class="tree-splitter" />
+      <treecol id="colBugzillaStatus"
+               persist="hidden ordinal width"
+               currentView="unthreaded"
+               flex="2"
+               label="Status"
+               tooltiptext="The bug's status."/>
+    </treecols>
+  </tree>
+
 </overlay>