aboutsummaryrefslogblamecommitdiffstats
path: root/data/tweaks/bug-page-mod.js
blob: f5d154e360dbe1cf1b5219f63c6baa164c5a85d4 (plain) (tree)
1
2
3
4
5
6
7
8
9
10

                                                                                

                                                                              

                                                                               

                                                                             

                                                                               


                                        


                                                                             
  

                                                                            

                                                                          









                                                                                
  

                                
 
                                                      






                             
                          



                                                     






                                                                 
 

                                                               


                                                                 






                                                                 





























                                                                                








                                                     
 


                                                                                 
                             
 
                                          
                                                                         



                                          
                                              
                                         
                                            







                                                                                             

                                                              

                                         
                                            



                                                                                                                           
                                                              
 

                                                              
                                                       
                                                        







                                                                
                                                                            
















                                                                  
         















                                                    
     


                                           
 
                                      
 
                                                              
                                                    
 
                                                
                                                                     
 

                                                                     

                              
 
                         
 
 


                                                                 
                                                                  
 



























                                                                                        














                                                                            










                                                                 
 
                                        
 




                                                                                
 
                                                



                              









                                                  
       


     
 



                                                       
 

                                                                     
 

                                                                            






                                                                         
   



                                                        







                                                                        


                                                                           

                                                        

                                                                      
                                                   
                        

                                                                         










                                                                                      
         














                                                                                  


                                       
                                             

                                                       



                                                       
                                                 






                                                                 
 
                  


                                                                             
                                                 



                                                                 


                                                              
     
 
                                                  





                                                                       
                                                             

                                                                    
                                                          
 
   

                                              
                                                                             







                                                                                        
                                                          



                                                         
                                                   



                                                                   
         

                      
 


                                                                               
                                                   



                                                                   


                                                            
       
 

                                                
                      
                                                               

                                                                      
                                                            






                                                       
                                                                      
                                 
                                     
 
 
                                                      

                                 
                                                                              

                                                 
                                                                                                                

                                           
                                                                       

                                       
                                                                                 
      
                                                        






























                                                                                                              


     

                                                                                  
                                        

                                         
                                              

            


                      
                                        
                                                                        
                                                                                    

















                                                                                           
      
                                                           




                                                                          
         


                 


     

                                                                          
                                        

                                        
                                          

             


                                                                   
                                                     
                   
                                                    





                           
     

                                                   
     

                    
                                                                                         





                                                           

 

                                                                            
                          
                               


                               

                            


                            






                                                            



                                                                 
                                                         
 

                                                                                  
                                      

                              

                                       

                                                              

























                                                                                  
                        








                                                       
                 
               
 






                                                                   
                     


                                             
                 








                                                                 
                                                                   

                                                                       
                                                          






                                                                                  
             
           
         
       

                                       






















                                                                       
             
           
         

                            
      
                             




                                                                     
                                                   



                                                                             
         
       

                                      



                                                                          
 









                                                        
         

                   


                      
                                          

                                                      
                                                            
                                                                           
                                                                    
                                                     
                                                           


                               


                          

                                


                                                                              
                        
                                               


                               
                                                    













                                               
             

                                                 
         


                                         
                                

                                         
                                     











                      



                                    
                                              
                
                         
         


                                                     



                                                   
                               


                                  


                                                 
                                                

                                                    

                                                                  


                         
                                      



                            
                  


                                                                   
                                            

                     
     

  

                                                   













                                                                                                     
                                                                                    





                                                              

                                                                             

                                  

                                    


                           

                                  



                                                              
                                                                          

                                                   
                                                            












                                                            
       
                                                                                








                                                                       
       
                         
                                                                        

























                                                                                             
                                     
                      

                                
                                            
                 

                                         
             


           





















                                                                           

  
                                      
                                                                     
                                                                












                                                     
     



                                  


                                   
                                                            


                          



                                       

 

                                                                                 


                           
                                      


                                           

                                                            



                                                                               
                              
                                                                             
                                                         






                   

                                                                          


                                    
/*******************************************************************************
 * ***** BEGIN LICENSE BLOCK Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with the
 * License. You may obtain a copy of the License at http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
 * the specific language governing rights and limitations under the License.
 *
 * The Original Code is Bugzilla Tweaks.
 *
 * The Initial Developer of the Original Code is Mozilla Foundation. Portions
 * created by the Initial Developer are Copyright (C) 2010 the Initial
 * Developer. All Rights Reserved.
 *
 * Contributor(s): Johnathan Nightingale <johnath@mozilla.com> Ehsan Akhgari
 * <ehsan@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 *****
 */

function TweakOnMessageHandler(msg, nextHandlerList) {
  switch (msg.cmd) {
  case "Unhandled":
    break;
  case "returnedHistory":
    processHistory(msg.data);
    break;
  default:
    if (nextHandlerList) {
      var nextHandler = nextHandlerList.splice(0, 1);
      if (nextHandler[0]) {
        nextHandler[0](msg, nextHandlerList);
      }
    }
    else {
      console.error("Error: unknown RPC call " + msg.toSource());
    }
  break;
  }
}

/**
 * generate XML-RPC call to collect complete history of the bug
 *
 * @param XMLRPCURL
 *          URL of the XML-RPC gateway on the particular bugzilla
 * @returns nothing
 *
 * As part of the message is name of the signal "returnedHistory"
 */
function collectHistory(rpcURL) {
  // https://bugzilla.redhat.com/docs/en/html/api/Bugzilla\
  // /WebService/Bug.html#Bug_Information
  /*
  This is a heads up on a change that we will be making soon with Bugzilla. The
  component, version and target release fields will become multi-select fields.
  This means a bug will have one or more values for these fields (currently they
  can only have one value for each field).
  If you use the RPC scripts, this change will affect the results you send and
  receive to it. For example, in the Bug.get function, the XML for the
  target_release field currently is:
             {{{
             <member>
                 <name>target_release</name>
                 <value><string>release_1</string></value>
             </member>
             }}}
             the new XML will read:
             {{{
             <member><name>target_release</name>
                 <value><array><data>
                     <value><string>release_1</string></value>
                     <value><string>release_1</string></value>
                 </data></array></value>
             </member>
             }}}
  We plan to release this change on our test server -
  https://partner-bugzilla.redhat.com/ - this Wednesday April 20th EDT. An
  announcement will be sent to bugzilla-announce-list redhat com when we make
  the change.
  The change will be made to the production servers in early May, with
  notification to the announce list prior to the change.
   */
  var bugId = getBugNo();
  self.postMessage(new Message("MakeJSONRPCall", {
    url : rpcURL.replace("xmlrpc.cgi","jsonrpc.cgi"),
    login : getLogin(),
    method : "Bug.history",
    params : { "ids": [ bugId ] },
    callRPC : "returnedHistory"
  }));
}

function tweakBugzilla(things, cData) {
  var atts = things.attachments; // FIXME compatibility crutch, should be removed
  // when this rewrite is done
  viewAttachmentSource(atts);

  // Mark up history along right hand edge
  var historyLink = document.querySelector("link[title='Bug Activity']");
  if (!historyLink)
    return;

  // Add our own style for bugzilla-tweaks
  var style = document.createElement("style");
  style.setAttribute("type", "text/css");
  style.appendChild(document.createTextNode(
      ".bztw_history { border: none; font-weight: normal; width: 58em; margin-left: 5em; }" +
      ".bztw_inlinehistory { font-weight: normal; width: 56em; }" +
      ".bztw_history .old, .bztw_inlinehistory .old { text-decoration: line-through; }" +
      ".bztw_history .sep:before { content: \" \"; }" +
      ".bztw_unconfirmed { font-style: italic; }" +
      "tr.bz_tr_obsolete.bztw_plusflag { display: table-row !important; }" +
      '.bztw_historyitem + .bztw_historyitem:before { content: "; "; }'
  ));
  document.getElementsByTagName("head")[0].appendChild(style);
  style = document.createElement("style");
  style.setAttribute("type", "text/css");
  style.id = "bztw_cc";
  style.appendChild(document.createTextNode(
      ".bztw_cc { display: none; }" +
      '.bztw_historyitem.bztw_cc + .bztw_historyitem:before { content: ""; }' +
      '.bztw_historyitem:not([class~="bztw_cc"]) ~ .bztw_historyitem.bztw_cc + .bztw_historyitem:before { content: "; "; }'
  ));
  document.getElementsByTagName("head")[0].appendChild(style);

  // collect the flag names, FIXME this is exactly the same as
  // mozFlags._init() function in mozpage.js
  var flagNames = [], flags = {}, flagOccurrences = {};
  var flagRows = document.querySelectorAll("#flags tr");
  for (var i = 0; i < flagRows.length; ++i) {
    var item = flagRows[i].querySelectorAll("td");
    if (!item[1])
      continue;
    var name = trimContent(item[1]).replace('\u2011', '-', 'g');
    flagNames.push(name);
    flags[name] = item[1];
  }
  flagRows = document.querySelectorAll(".field_label[id^=field_label_cf_]");
  for (var i = 0; i < flagRows.length; ++i) {
    var name = trimContent(flagRows[i]).replace(/\:$/, '')
    .replace('\u2011', '-', 'g');
    flagNames.push(name);
    flags[name] = flagRows[i];
  }
  var flagCounter = 1;

  // =================================================
  function findFlag(item) {
    function lookup(name) {
      name = name.replace('\u2011', '-', 'g');
      for (var i = 0; i < flagNames.length; ++i) {
        var quotedFlagName = flagNames[i].replace('.', '\\.', 'g')
        .replace('\u2011', '-', 'g');
        if ((new RegExp('^' + quotedFlagName)).test(name)) {
          return [flagNames[i]];
        }
      }
      return [];
    }
    var base = item[4] ? 2 : 0;
    // handle normal flags
    if (trimContent(item[base]) == 'Flag') {
      var result = [];
      var tmp = lookup(trimContent(item[base + 1]));
      if (tmp.length) {
        result.push(tmp[0]);
      }
      tmp = lookup(trimContent(item[base + 2]));
      if (tmp.length) {
        result.push(tmp[0]);
      }
      return result;
    }
    // handle special pseudo-flags
    return lookup(trimContent(item[base]));
  }

  var DataStore = new DataStoreCtor();

  var AttachmentFlagHandler = new AttachmentFlagHandlerCtor();
  AttachmentFlagHandler.determineInterestingFlags();

  var CheckinComment = new CheckinCommentCtor();
  CheckinComment.initialize(AttachmentFlagHandler._interestingFlags);

  if (document.location.hostname in cData.XMLRPCData) {
    var XMLRPCUrl = cData.XMLRPCData[document.location.hostname].url;
    collectHistory(XMLRPCUrl);
  }

  tbplbotSpamCollapser();
}

// ===================================================
function processHistory(history) {
  // FIXME Remove remaining code to special function ... callback
  // preprocessDuplicateMarkers(document, iframe.contentDocument);

  /*
   * This is an example of the history we get: { "version": "1.1", "result": {
   * "bugs": [ { "history": [ { "when": "2011-04-04T00:19:04Z", "who":
   * "cebbert@redhat.com", "changes": [ { "removed": "", "added":
   * "xgl-maint@redhat.com", "field_name": "cc", "field": "CC" }, { "removed":
   * "kernel", "added": "xorg-x11-drv-ati", "field_name": "component", "field":
   * "Component" }, { "removed": "kernel-maint@redhat.com", "added":
   * "xgl-maint@redhat.com", "field_name": "assigned_to", "field": "AssignedTo" } ] }, {
   * "when": "2011-04-12T22:48:22Z", "who": "mcepl@redhat.com", "changes": [ {
   * "attachment_id": 488889, "removed": "application/octet-stream", "added":
   * "text/plain", "field_name": "attachments.mimetype", "field": "Attachment
   * mime type" } ] }, { "when": "2011-04-13T17:07:04Z", "who":
   * "mcepl@redhat.com", "changes": [ { "removed": "", "added":
   * "needinfo?(suckfish@ihug.co.nz)", "field_name": "flagtypes.name", "field":
   * "Flags" } ] }, { "when": "2011-04-21T12:17:33Z", "who": "mcepl@redhat.com",
   * "changes": [ { "removed": "xgl-maint@redhat.com", "added":
   * "jglisse@redhat.com", "field_name": "assigned_to", "field": "AssignedTo" } ] }, {
   * "when": "2011-04-28T22:53:58Z", "who": "mcepl@redhat.com", "changes": [ {
   * "attachment_id": 488889, "removed": "text/plain", "added":
   * "application/octet-stream", "field_name": "attachments.mimetype", "field":
   * "Attachment mime type" } ] }, { "when": "2011-04-28T22:59:18Z", "who":
   * "mcepl@redhat.com", "changes": [ { "attachment_id": 488889, "removed":
   * "application/octet-stream", "added": "text/plain", "field_name":
   * "attachments.mimetype", "field": "Attachment mime type" } ] } ], "id":
   * 692250, "alias": [
   *  ] } ], "faults": [
   *  ] } }
   */
  // UserNameCache
  var userNameCache = {};
  function getUserName(email) {
    if (email in userNameCache) {
      return userNameCache[email];
    }
    var emailLink = document.querySelectorAll("a.email");
    for (var i = 0; i < emailLink.length; ++i) {
      if (emailLink[i].href == "mailto:" + email) {
        return userNameCache[email] = htmlEncode(trimContent(emailLink[i]));
      }
    }
    return email;
  }

//   MC $$$ NOT DONE YET
//  // Sometimes the history will stack several changes together,
//  // and we'll want to append the data from the Nth item to the
//  // div created in N-1
//  if (history) {
//    history.bugs.forEach(function (historyBug) {
//      historyBug.history.forEach(function (historyItem) {
//        processHistoryItem(comments, historyItem);
//      });
//    });
//  }

//  handleEmptyCollapsedBoxes(document);

  // // Set the latest flag links if necessary
  // for (var flagName in flagOccurrences) {
  // flags[flagName].innerHTML = '<a href="#' + flagOccurrences[flagName] + '">'
  // + flags[flagName].innerHTML + '</a>';
  // }

  // AttachmentFlagHandler.setupLinks(document);
  // END OF load event handler

}

/*
  {
    "when": "2011-04-13T17:07:04Z",
    "who": "mcepl@redhat.com",
    "changes": [
      {
        "removed": "",
        "added": "needinfo?(suckfish@ihug.co.nz)",
        "field_name": "flagtypes.name",
        "field": "Flags"
      }
    ]
  },
*/

function displayCommentActions (comment, historyItem) {
  if (historyItem.who == comment.who) {
  }
}

function processHistoryItem(objects, itemRaw) {
  console.log("processHistoryItem: itemRaw = " + itemRaw.toSource());

return ; // FIXME just to get rid of this unfinished and unanalyzed function

  var idx = itemRaw.when; // Given the mid-air protection we could assume
  // history time to be unique per second.
  if (idx in objects.attachment) {
    displayAttachmentActions(objects.attachment[idx], itemRaw)
  }
  if (idx in objects.comment) {
    displayCommentActions(objects.comment[idx], itemRaw)
  }
  // FIXME anything else?
  displayRemainingActions(itemRaw);

// =====================================================
    var commentHead = commentTimes[j].parentNode;

    var mainUser = commentHead.querySelector(".bz_comment_user a.email")
    .href
    .substr(7);
    var user = trimContent(item[0]);
    var mainTime = trimContent(commentTimes[j]);
    var time = trimContent(item[1]);

//    =====================================================================
//    $$$ FIXME the change is made by commenter? is that it?
    var inline = (mainUser == user && time == mainTime);


//    §§§§ This is a function addToInlineHistory or something TODO
    var currentDiv = document.createElement("div");
    var userPrefix = '';
    if (inline) { 
      // assume that the change was made by the same user  // XXX? §§§
      commentHead.appendChild(currentDiv);
      currentDiv.setAttribute("class", "bztw_inlinehistory");
    } else {
      // the change was made by another user
      if (!reachedEnd) {
        var parentDiv = commentHead.parentNode;
        if (parentDiv.previousElementSibling &&
            parentDiv.previousElementSibling.className.indexOf("bztw_history") >= 0) {
          currentDiv = parentDiv.previousElementSibling;
        } else {
          parentDiv.parentNode.insertBefore(currentDiv, parentDiv);
        }
      } else {
        var parentDiv = commentHead.parentNode;
        if (parentDiv.nextElementSibling &&
            parentDiv.nextElementSibling.className.indexOf("bztw_history") >= 0) {
          currentDiv = parentDiv.nextElementSibling;
        } else {
          parentDiv.parentNode.appendChild(currentDiv);
        }
      }
      currentDiv.setAttribute("class", "bz_comment bztw_history");
      userPrefix += "<a class=\"email\" href=\"mailto:" +
      htmlEncode(trimContent(item[0])) + "\" title=\"" +
      htmlEncode(trimContent(item[1])) +"\">" +
      getUserName(trimContent(item[0])) + "</a>: ";
    }
    // XXX END OF if (inline) CONSTRUCT

// XXX flags
    // check to see if this is a flag setting
    flagsFound = findFlag(item); // XXX findFlag call 2
    // XXX Add <a name> around every flag comment
    for (var idx = 0; idx < flagsFound.length; ++idx) {
      var flag = flagsFound[idx];
      flagOccurrences[flag] = 'flag' + flagCounter;
      if (inline) {
        var anchor = document.createElement("a");
        anchor.setAttribute("name", "flag" + flagCounter);
        commentHead.insertBefore(anchor, commentHead.firstChild);
      } else {
        userPrefix += '<a name="flag' + flagCounter + '"></a>';
      }
      ++flagCounter;
    }

// XXX attachments
    var attachmentFlagAnchors = AttachmentFlagHandler.handleItem(user, item);
    if (inline) {
      for (var idx = 0; idx < attachmentFlagAnchors.length; ++idx) {
        var anchor = document.createElement("a");
        anchor.setAttribute("name", attachmentFlagAnchors[idx]);
        commentHead.insertBefore(anchor, commentHead.firstChild);
      }
    } else {
      userPrefix += attachmentFlagAnchors.map(function(name) {
        return '<a name="' + name + '"></a>';
      }).join("");
    }

// XXX just adding/removing sometbody from CC list
    var ccOnly = (trimContent(item[2]) == 'CC');
    var ccPrefix = ccOnly ? '<span class="bztw_cc bztw_historyitem">' :
      '<span class="bztw_historyitem">',
      ccSuffix = '</span>';
    var html = userPrefix +
    ccPrefix +
    transformType(trimContent(item[2]), trimContent(item[3]),
        trimContent(item[4])) + ": " +
        formatTransition(trimContent(item[3]), trimContent(item[4]),
            trimContent(item[2]), iframe.contentDocument);

// 
    var nextItemsCount = item[0].rowSpan;
    for (var k = 1; k < nextItemsCount; ++k) {
    // XXX doing once more the same for non-first elements of the imte array.
      ccOnly = false;
      item = historyItems[++i].querySelectorAll("td")
      ccPrefix = (trimContent(item[0]) == 'CC') ?
          '<span class="bztw_cc bztw_historyitem">' : '<span class="bztw_historyitem">';
      // avoid showing a trailing semicolon if the previous entry
      // wasn't a CC and this one is
      var prefix = ccSuffix + ccPrefix;
      // check to see if this is a flag setting
      flagsFound = findFlag(item); // TODO findFlag call 1
      for (var idx = 0; idx < flagsFound.length; ++idx) {
        var flag = flagsFound[idx];
        flagOccurrences[flag] = 'flag' + flagCounter;
        if (inline) {
          var anchor = document.createElement("a");
          anchor.setAttribute("name", "flag" + flagCounter);
          commentHead.insertBefore(anchor, commentHead.firstChild);
        } else {
          prefix += '<a name="flag' + flagCounter + '"></a>';
        }
        ++flagCounter;
      }

      var attachmentFlagAnchors = AttachmentFlagHandler.handleItem(user, item);
      if (inline) {
        for (var idx = 0; idx < attachmentFlagAnchors.length; ++idx) {
          var anchor = document.createElement("a");
          anchor.setAttribute("name", attachmentFlagAnchors[idx]);
          commentHead.insertBefore(anchor, commentHead.firstChild);
        }
      } else {
        prefix += attachmentFlagAnchors.map(function(name) {
          return '<a name="' + name + '"></a>';
        }).join("");
      }

    // END OF THE SAME STUFF FOR NON-FIRST ITEMS

      html += prefix +
      transformType(trimContent(item[0]), trimContent(item[1]),
          trimContent(item[2])) + ": " +
          formatTransition(trimContent(item[1]), trimContent(item[2]),
              trimContent(item[0]), iframe.contentDocument);
    }
    html += ccSuffix;
    if (ccOnly) {
      html = '<div class="bztw_cc">' + html + '</div>';
    } else {
      html = '<div>' + html + '</div>';
    }
    // Add the new stuff to the place below the top of the comment div
    currentDiv.innerHTML += html;
    // FIXME here used to be a break;
}

// ===================================================
var TransformValues = {
    linkifyURLs: function (str) {
      return str.replace(/((https?|ftp)\:\/\/[\S]+)/g, '<a href="$1">$1</a>');
    },
    linkifyBugAndCommentNumbers: function (str) {
      return str.replace(/(bug )(\d+) (comment )(\d+)/gi, '<a href="show_bug.cgi?id=$2#c$4">$1\n$2 $3\n$4</a>');
    },
    linkifyCommentNumbers: function (str) {
      return str.replace(/(comment (\d+))/gi, '<a href="#c$2">$1</a>');
    },
    linkifyBugNumbers: function (str) {
      return str.replace(/(bug (\d+))/gi, '<a href="show_bug.cgi?id=$2">$1</a>');
    },
    linkifyDependencies: function (str, type, histDoc) {
      switch (type) {
      case "Blocks":
      case "Depends on":
      case "Duplicate":
        str = str.replace(/\d+/g, function(str) {
          var link = histDoc.querySelector("a[href='show_bug.cgi?id=" + str + "']");
          if (link) {
            var class_ = '';
            if (/bz_closed/i.test(link.className)) {
              class_ += 'bz_closed ';
            } else if (/bztw_unconfirmed/i.test(link.className)) {
              class_ += 'bztw_unconfirmed ';
            }
            var parent = link.parentNode;
            if (parent) {
              if (parent.tagName.toLowerCase() == "i") {
                class_ += 'bztw_unconfirmed ';
              }
              if (/bz_closed/i.test(parent.className)) {
                class_ += 'bz_closed ';
              }
            }
            str = applyClass(class_,
                '<a title="' + htmlEncode(link.title) + '" href="show_bug.cgi?id=' + htmlEncode(str) + '"' +
                (link.hasAttribute("name") ? (' name="' + htmlEncode(link.getAttribute("name")) + '"') : '') +
                '>' + htmlEncode(str) + '</a>');
          }
          return str;
        });
      }
      return str;
    }
};

// ===============================================================================

function transform(str, type, histDoc) {
  for (var funcname in TransformValues) {
    var func = TransformValues[funcname];
    str = func.call(null, str, type, histDoc);
  }
  return str
}

var TransformTypes = {
    linkifyAttachments: function (str) {
      return str.replace(/(Attachment #(\d+))/g, function (str, x, id) {
        var link = document.querySelector("a[href='attachment.cgi?id=" + id + "']");
        if (link) {
          var class_ = '';
          if (/bz_obsolete/i.test(link.className)) {
            class_ += 'bz_obsolete ';
          }
          var parent = link.parentNode;
          if (parent && /bz_obsolete/i.test(parent.className)) {
            class_ += 'bz_obsolete ';
          }
          if (link.querySelector(".bz_obsolete")) {
            class_ += 'bz_obsolete ';
          }
          str = applyClass(class_,
              '<a title="' + htmlEncode(trimContent(link)) + '" href="attachment.cgi?id=' +
              htmlEncode(id) + '&action=edit">' + htmlEncode(str) + '</a>');
        }
        return str;
      });
    },
    changeDependencyLinkTitles: function (str, old, new_) {
      switch (str) {
      case "Blocks":
      case "Depends on":
        if (old.length && !new_.length) { // if the dependency was removed
          str = "No longer " + str[0].toLowerCase() + str.substr(1);
        }
        break;
      }
      return str;
    }
};

// =======================================================================

function transformType(str, old, new_) {
  for (var funcname in TransformTypes) {
    var func = TransformTypes[funcname];
    str = func.call(null, str, old, new_);
  }
  return str;
}

// new is a keyword, which makes this function uglier than I'd like
function formatTransition(old, new_, type, histDoc) {
  if (old.length) {
    old = transform(htmlEncode(old), type, histDoc);
    var setOldStyle = true;
    switch (type) {
    case "Blocks":
    case "Depends on":
      setOldStyle = false;
      break;
    }
    if (setOldStyle) {
      old = '<span class="old">' + old + '</span>';
    }
  }
  if (new_.length) {
    new_ = '<span class="new">' + transform(htmlEncode(new_), type, histDoc) + '</span>';
  }
  var mid = '';
  if (old.length && new_.length) {
    mid = ' <span style="font-size: 150%;">&rArr;</span> ';
  }
  return old + mid + new_;
}

// =========================================================================

function trimContent(el) {
  return el.textContent.trim();
}

function AttachmentFlag(flag) {
  for (var name in flag)
    this[name] = flag[name];
}
AttachmentFlag.prototype = {
    equals: function(flag) {
      if (this.type != flag.type ||
          this.name != flag.name ||
          this.setter != flag.setter ||
          ("requestee" in this && !("requestee" in flag)) ||
          ("requestee" in flag && !("requestee" in this)))
        return false;
      return this.requestee == flag.requestee;
    }
};

var reAttachmentDiff = /attachment\.cgi\?id=(\d+)&action=diff$/i;
var reviewBoardUrlBase = "http://reviews.visophyte.org/";

// ===============================================================================

function AttachmentFlagHandlerCtor() {
  this._db = {};
  this._interestingFlags = {};
}
AttachmentFlagHandlerCtor.prototype = {
    determineInterestingFlags: function () {
      var table = document.getElementById("attachment_table");
      if (!table)
        return;
      var rows = table.querySelectorAll("tr");
      for (var i = 0; i < rows.length; ++i) {
        var item = rows[i].querySelectorAll("td");
        if (item.length != 3 ||
            item[1].className.indexOf("bz_attach_flags") < 0 ||
            trimContent(item[1]) == "no flags")
          continue;
        // get the ID of the attachment
        var link = item[0].querySelector("a");
        if (!link)
          continue;
        var match = this._reAttachmentHref.exec(link.href);
        if (match) {
          var attachmentID = match[1];
          if (!(attachmentID in this._interestingFlags)) {
            this._interestingFlags[attachmentID] = [];
          }
          for (var el = item[1].firstChild; el.nextSibling; el = el.nextSibling) {
            if (el.nodeType != el.TEXT_NODE)
              continue;
            var text = trimContent(el).replace('\u2011', '-', 'g');
            if (!text)
              continue;
            match = this._reParseInterestingFlag.exec(text);
            if (match) {
              var flag = {};
              flag.setter = match[1];
              flag.name = match[2];
              if (match[4] == "+" || match[4] == "-") {
                flag.type = match[4];
              } else {
                flag.type = "?";
                if (match[7]) {
                  flag.requestee = match[7];
                }
              }

              // always show the obsolete attachments with a + flag
              if (flag.type == "+") {
                var parent = link.parentNode;
                while (parent) {
                  if (parent.tagName.toLowerCase() == "tr") {
                    if (/bz_tr_obsolete/i.test(parent.className)) {
                      parent.className += " bztw_plusflag";
                    }
                    break;
                  }
                  parent = parent.parentNode;
                }
              }

              // try to put the flag name and type part in a span
              // which we will
              // use in setupLinks to inject links into.
              match = this._reLinkifyInterestingFlag.exec(text);
              if (match) {
                el.textContent = match[1];
                if (match[3]) {
                  var textNode = document.createTextNode(match[3]);
                  el.parentNode.insertBefore(textNode, el.nextSibling);
                }
                var span = document.createElement("span");
                span.textContent = match[2];
                el.parentNode.insertBefore(span, el.nextSibling);

                flag.placeholder = span;
              }

              this._interestingFlags[attachmentID].push(new AttachmentFlag(flag));
            }
          }
        }
      }
    },
    handleItem: function (name, item) {
      var anchorsCreated = [];
      var base = item[4] ? 2 : 0;
      var what = trimContent(item[base]);
      var match = this._reAttachmentFlagName.exec(what);
      if (match) {
        var id = match[1];
        if (!(id in this._db)) {
          this._db[id] = [];
        }
        name = name.split('@')[0]; // convert the name to the fraction
        // before the @
        var added = this._parseData(name, trimContent(item[base + 2]));
        for (var i = 0; i < added.length; ++i) {
          var flag = added[i];
          if (!(id in this._interestingFlags))
            continue;
          for (var j = 0; j < this._interestingFlags[id].length; ++j) {
            if (flag.equals(this._interestingFlags[id][j])) {
              // found an interesting flag
              this._interestingFlags[id][j].anchor = this.anchorName;
              anchorsCreated.push(this.anchorName);
              this._counter++;
              break;
            }
          }
        }
      }
      return anchorsCreated;
    },
    setupLinks: function () {
      for (var id in this._interestingFlags) {
        for (var i = 0; i < this._interestingFlags[id].length; ++i) {
          var flag = this._interestingFlags[id][i];
          if ("placeholder" in flag &&
              "anchor" in flag) {
            var link = document.createElement("a");
            link.href = "#" + flag.anchor;
            link.textContent = flag.placeholder.textContent;
            flag.placeholder.replaceChild(link, flag.placeholder.firstChild);
          }
        }
      }
    },
    _parseData: function (name, str) {
      var items = str.replace('\u2011', '-', 'g').split(', '), flags = [];
      for (var i = 0; i < items.length; ++i) {
        if (!items[i].length)
          continue;

        var match = this._reParseRequest.exec(items[i]);
        if (match) {
          var flag = {};
          flag.name = match[1];
          flag.setter = name;
          if (match[4]) {
            flag.requestee = match[4];
          }
          flag.type = match[2];
          flags.push(new AttachmentFlag(flag));
        }
      }
      return flags;
    },
    _counter: 1,
    get anchorName() {
      return "attachflag" + this._counter;
    },
    _reParseRequest: /^(.+)([\?\-\+])(\((.+)@.+\))?$/,
    _reParsePartToLinkify: /^\s*:\s+.+[\-\+\?](\s*\()?\s*$/,
    _reParseInterestingFlag: /^(.+):\s+(.+)(([\-\+])|\?(\s+(\((.+)\)))?)$/,
    _reLinkifyInterestingFlag: /^(\s*:\s+)(.+[\-\+\?])(\s*\(\s*)?$/,
    _reAttachmentHref: /attachment\.cgi\?id=(\d+)$/i,
    _reAttachmentFlagName: /^Attachment\s+#(\d+)\s+Flags$/i
};

function CheckinCommentCtor() {
  this.bugNumber = null;
  this.summarySpan = null;
  this.checkinFlags = "";
}
CheckinCommentCtor.prototype = {
    initialize: function(flags) {
      this.bugNumber = getBugNo();
      var summarySpan = document.getElementById("short_desc_nonedit_display");
      if (summarySpan) {
        this.summary = summarySpan.textContent;
      }
      var checkinFlagsMap = {};
      for (var id in flags) {
        for (var i = 0; i < flags[id].length; ++i) {
          var flag = flags[id][i];
          if (flag.type == "+") {
            var name = flag.name;
            if (name == "review") {
              name = "r";
            } else if (name == "superreview") {
              name = "sr";
            } else if (name == "ui-review") {
              name = "ui-r";
            } else if (name == "feedback") {
              name = "f";
            }
            if (!(name in checkinFlagsMap)) {
              checkinFlagsMap[name] = {};
            }
            checkinFlagsMap[name][flag.setter]++;
          }
        }
      }
      var flagsOrdered = [];
      for (var name in checkinFlagsMap) {
        flagsOrdered.push(name);
      }
      flagsOrdered.sort(function (a, b) {
        function convertToNumber(x) {
          switch (x) {
          case "f":
            return -4;
          case "r":
            return -3;
          case "sr":
            return -2;
          case "ui-r":
            return -1;
          default:
            return 0;
          }
        }
        var an = convertToNumber(a);
        var bn = convertToNumber(b);
        if (an == 0 && bn == 0) {
          return a < b ? -1 : (a = b ? 0 : 1);
        } else {
          return an - bn;
        }
      });
      var checkinFlags = [];
      for (var i = 0; i < flagsOrdered.length; ++i) {
        var name = flagsOrdered[i];
        var flag = name + "=";
        var setters = [];
        for (var setter in checkinFlagsMap[name]) {
          setters.push(setter);
        }
        flag += setters.join(",");
        checkinFlags.push(flag);
      }
      this.checkinFlags = checkinFlags.join(" ");
      if (this.isValid()) {
        var div = document.createElement("div");
        div.setAttribute("style", "display: none;");
        div.id = "__bz_tw_checkin_comment";
        div.appendChild(document.createTextNode(this.toString()));
        document.body.appendChild(div);
      }
    },
    isValid: function() {
      return this.bugNumber != null &&
      this.summary != null;
    },
    toString: function() {
      if (!this.isValid()) {
        return "";
      }
      var message = "Bug " + this.bugNumber + " - " + this.summary;
      if (this.checkinFlags.length) {
        message += "; " + this.checkinFlags;
      }
      return message;
    }
};

function DataStoreCtor() {
  this.storage = document.defaultView.localStorage;
  this.data = {};
  this.bugNumber = null;
  function visualizeStoredData() {
    var data = "";
    for (var i = 0; i < window.localStorage.length; ++i) {
      var key = window.localStorage.key(i);
      data += key + ": " + JSON.parse(window.localStorage.getItem(key).toString()).toSource() + "\n";
    }
    open("data:text/html,<pre>" + escape(htmlEncode(data)) + "</pre>");
  }
  function clearStoredData() {
    var count = window.localStorage.length;
    if (count > 0) {
      if (window.confirm("You currently have data stored for " + count + " bugs.\n\n" +
      "Are you sure you want to clear this data?  This action cannot be undone.")) {
        window.localStorage.clear();
      }
    } else {
      alert("You don't have any data stored about your bugs");
    }
  }
  var script = document.createElement("script");
  script.appendChild(document.createTextNode(visualizeStoredData.toSource() +
      clearStoredData.toSource() +
      htmlEncode.toSource()));
  document.body.appendChild(script);
  this.initialize();
}

DataStoreCtor.prototype = {
    initialize: function() {
      this.bugNumber = getBugNo();
      var data = this._ensureEntry(this.bugNumber, this.data);
      // last visited date
      data.visitedTime = (new Date()).getTime();
      // last comment count
      data.commentCount = document.querySelectorAll(".bz_comment").length;
      // last status of bug flags
      var flags = this._ensureEntry("flags", data);
      var flagRows = document.querySelectorAll("#flags tr");
      for (var i = 0; i < flagRows.length; ++i) {
        var flagCols = flagRows[i].querySelectorAll("td");
        if (flagCols.length != 3) {
          continue;
        }
        var flagName = trimContent(flagCols[1]);
        var flagValue = flagCols[2].querySelector("select");
        if (flagValue) {
          flagValue = flagValue.value;
        } else {
          continue;
        }
        flags[flagName] = flagValue;
      }
      flagRows = document.querySelectorAll(".field_label[id^=field_label_cf_]");
      for (var i = 0; i < flagRows.length; ++i) {
        var flagName = trimContent(flagRows[i]).replace(/:$/, "");
        var flagValue = flagRows[i].parentNode.querySelector("select");
        if (flagValue) {
          flagValue = flagValue.value;
        } else {
          continue;
        }
        flags[flagName] = flagValue;
      }
      // last attachments
      var attachmentTable = document.getElementById("attachment_table");
      var attachmentRows = attachmentTable.querySelectorAll("tr");
      for (var i = 0; i < attachmentRows.length; ++i) {
        var attachmentCells = attachmentRows[i].querySelectorAll("td");
        if (attachmentCells.length != 3) {
          continue;
        }
        var link = attachmentCells[0].querySelector("a");
        var match = this._reAttachmentHref.exec(link.href);
        if (match) {
          var attachmentID = match[1];
          var attachment = this._ensureEntry("attachments", data);
          var attachmentFlags = this._ensureArray(attachmentID, attachment);
          for (var el = attachmentCells[1].firstChild; el.nextSibling; el = el.nextSibling) {
            if (el.nodeType != el.TEXT_NODE) {
              continue;
            }
            var text = trimContent(el);
            if (!text) {
              continue;
            }
            match = this._reParseInterestingFlag.exec(text);
            if (match) {
              var flag = {};
              flag.setter = match[1];
              flag.name = match[2];
              if (match[4] == "+" || match[4] == "-") {
                flag.type = match[4];
              } else {
                flag.type = "?";
                if (match[7]) {
                  flag.requestee = match[7];
                }
              }
              attachmentFlags.push(flag);
            }
          }
        }
      }
      // Write data to storage
      for (var key in this.data) {
        this._ensure(key, this.storage, JSON.stringify(this.data[key]));
      }
    },
    _ensure: function(entry, obj, val) {
      if (obj.toString().indexOf("[object Storage") >= 0) {
        obj.setItem(entry, val);
      } else {
        if (typeof obj[entry] == "undefined")
          obj[entry]  = val;
        return obj[entry];
      }
    },
    _ensureEntry: function(entry, obj) {
      return this._ensure(entry, obj, {});
    },
    _ensureArray: function(entry, obj) {
      return this._ensure(entry, obj, []);
    },
    _reParseInterestingFlag: /^(.+):\s+(.+)(([\-\+])|\?(\s+(\((.+)\)))?)$/,
    _reAttachmentHref: /attachment\.cgi\?id=(\d+)$/i
};

function handleEmptyCollapsedBoxes() {
  // first, try to get the display style of a CC field (any would do)
  var historyBoxes = document.querySelectorAll(".bztw_history");
  for (var i = 0; i < historyBoxes.length; ++i) {
    var box = historyBoxes[i];
    for (var j = 0; j < box.childNodes.length; ++j) {
      var child = box.childNodes[j], found = true;
      if (child.nodeType != child.ELEMENT_NODE)
        continue;
      if (child.className == "sep") {
        // separators are insignificant
        continue;
      } else if (!/bztw_cc/.test(child.className)) {
        found = false;
        break;
      }
    }
    if (found) {
      box.className += " bztw_cc";
    }
  }
}

function applyClass(class_, html) {
  return '<span class="' + class_ + '">' + html + '</span>';
}

function htmlEncode(str) {
  return str.replace('&', '&amp;', 'g')
  .replace('<', '&lt;', 'g')
  .replace('>', '&gt;', 'g')
  .replace('"', '&quot;', 'g');
}

function tbplbotSpamCollapser() {
  var collapseExpandBox = document.querySelector(".bz_collapse_expand_comments");
  if (!collapseExpandBox) {
    return;
  }
  var a = document.createElement("a");
  a.href = "#";
  a.addEventListener("click", function(e) {
    e.preventDefault();
    var win = document.defaultView.wrappedJSObject;
    var comments = document.querySelectorAll(".bz_comment");
    for (var i = 0; i < comments.length; ++i) {
      var comment = comments[i];
      try {
        if (comment.querySelector(".bz_comment_user a.email").href.substr(7) ==
        "tbplbot@gmail.com") {
          win.collapse_comment(comment.querySelector(".bz_collapse_comment"),
              comment.querySelector(".bz_comment_text"));
        }
      } catch (e) {
        continue;
      }
    }
    return false;
  }, false);
  a.appendChild(document.createTextNode("Collapse All tbplbot Comments"));
  var li = document.createElement("li");
  li.appendChild(a);
  collapseExpandBox.appendChild(li);
}