<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[271] sites/trunk/wordpress.org/public_html/style/trac/wp-trac.js: Trac JS cleanup: Reorganize this file to make it a bit more maintainable.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://meta.trac.wordpress.org/changeset/271">271</a></dd>
<dt>Author</dt> <dd>nacin</dd>
<dt>Date</dt> <dd>2014-01-11 20:06:28 +0000 (Sat, 11 Jan 2014)</dd>
</dl>

<h3>Log Message</h3>
<pre>Trac JS cleanup: Reorganize this file to make it a bit more maintainable.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#sitestrunkwordpressorgpublic_htmlstyletracwptracjs">sites/trunk/wordpress.org/public_html/style/trac/wp-trac.js</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="sitestrunkwordpressorgpublic_htmlstyletracwptracjs"></a>
<div class="modfile"><h4>Modified: sites/trunk/wordpress.org/public_html/style/trac/wp-trac.js (270 => 271)</h4>
<pre class="diff"><span>
<span class="info">--- sites/trunk/wordpress.org/public_html/style/trac/wp-trac.js      2014-01-11 19:22:00 UTC (rev 270)
+++ sites/trunk/wordpress.org/public_html/style/trac/wp-trac.js 2014-01-11 20:06:28 UTC (rev 271)
</span><span class="lines">@@ -1,4 +1,4 @@
</span><del>-var vnpTrac, coreKeywordList, gardenerKeywordList;
</del><ins>+var wpTrac, coreKeywordList, gardenerKeywordList;
</ins><span class="cx"> 
</span><span class="cx"> (function($){
</span><span class="cx"> 
</span><span class="lines">@@ -30,23 +30,20 @@
</span><span class="cx"> 
</span><span class="cx">  wpTrac = {
</span><span class="cx"> 
</span><del>-               keywords : {},
-               originalKeywords : {},
-               field : {},
</del><span class="cx">           gardener : typeof wpBugGardener !== 'undefined',
</span><span class="cx"> 
</span><span class="cx">          init : function() {
</span><ins>+                       wpTrac.hacks();
+                       wpTrac.workflow.init();
+                       wpTrac.nonGardeners();
+               },
+
+               hacks: function() {
</ins><span class="cx">                   // Change 'Comments' and 'Stars' columns to dashicons glyphs to save space
</span><span class="cx">                  $('th a[href*="sort=Comments"]').html('<div class="dashicons dashicons-admin-comments"></div>');
</span><span class="cx">                  $('th a[href*="sort=Stars"]').html('<div class="dashicons dashicons-star-empty"></div>');
</span><span class="cx"> 
</span><del>-                       // Bring back 'Delete' comment buttons, if any.
-                       $('div.change').children('.trac-ticket-buttons').each( function() {
-                               var el = $(this);
-                               el.children().appendTo( el.prev().children('.trac-ticket-buttons') ).end().end().remove();
-                       });
-
-                       // Automatically preview images
</del><ins>+                        // Automatically preview images.
</ins><span class="cx">                   $('li.trac-field-attachment').each( function() {
</span><span class="cx">                          var href, el, image, li = $(this);
</span><span class="cx">                          el = $(this).find('.trac-rawlink');
</span><span class="lines">@@ -69,26 +66,21 @@
</span><span class="cx">                          };
</span><span class="cx">                  });
</span><span class="cx"> 
</span><del>-                       // 'User Interface' preferences tab => 'Help Links' (and removes icons-only setting)
-                       var uitab = $('#tab_userinterface');
-                       if ( uitab.length ) {
-                               if ( uitab.hasClass('active') ) {
-                                       uitab.text('Help Links');
-                                       $('input[name="ui.use_symbols"]').closest('div.field').remove();
-                               } else {
-                                       uitab.find('a').text('Help Links');
-                               }
-                       }
</del><ins>+                        // Restore the 'Delete' comment buttons, if any. The Trac plugin places them in a location we don't want.
+                       // See https://meta.trac.wordpress.org/changeset/204.
+                       $('div.change').children('.trac-ticket-buttons').each( function() {
+                               var el = $(this);
+                               el.children().appendTo( el.prev().children('.trac-ticket-buttons') ).end().end().remove();
+                       });
</ins><span class="cx"> 
</span><span class="cx">                  // Add After the Deadline (only add it if it loaded)
</span><span class="cx">                  if ( $.isFunction( $.fn.addProofreader ) ) {
</span><span class="cx">                          $('textarea').addProofreader();
</span><ins>+                               $('.AtD_proofread_button').each(function() {
+                                       $(this).parent().appendTo( $(this).parents('fieldset').find('.wikitoolbar') );
+                               });
</ins><span class="cx">                   }
</span><span class="cx"> 
</span><del>-                       $('.AtD_proofread_button').each(function() {
-                               $(this).parent().appendTo( $(this).parents('fieldset').find('.wikitoolbar') );
-                       });
-
</del><span class="cx">                   // Force 'Attachments' and 'Modify Ticket' to be shown
</span><span class="cx">                  $('#attachments').removeClass('collapsed');
</span><span class="cx">                  $("#modify").parent().removeClass('collapsed');
</span><span class="lines">@@ -102,6 +94,7 @@
</span><span class="cx">                          var action, hadClass,
</span><span class="cx">                                  form = $('#propertyform'),
</span><span class="cx">                                  modify = $('#modify').parent();
</span><ins>+
</ins><span class="cx">                           if ( ! form.length ) {
</span><span class="cx">                                  return;
</span><span class="cx">                          }
</span><span class="lines">@@ -144,12 +137,12 @@
</span><span class="cx">                          });
</span><span class="cx"> 
</span><span class="cx">                  // Clear the milestone on wontfix, duplicate, worksforme, invalid
</span><del>-                       wpTrac.field.milestone = $('#field-milestone');
-                       if ( ! wpTrac.field.milestone.prop('disabled') ) {
</del><ins>+                        var milestone = $('#field-milestone');
+                       if ( ! milestone.prop('disabled') ) {
</ins><span class="cx">                           $('#propertyform').submit( function() {
</span><span class="cx">                                  var action = $('input[name=action]:checked').val();
</span><span class="cx">                                  if ( 'duplicate' === action || ( 'resolve' === action && 'fixed' !== $('#action_resolve_resolve_resolution').val() ) ) {
</span><del>-                                               wpTrac.field.milestone.val('');
</del><ins>+                                                milestone.val('');
</ins><span class="cx">                                   }
</span><span class="cx">                          });
</span><span class="cx">                  }
</span><span class="lines">@@ -176,249 +169,252 @@
</span><span class="cx">                                  .show();
</span><span class="cx">                  });
</span><span class="cx"> 
</span><del>-                       // Start of Keywords manipulation.
-                       wpTrac.hiddenEl = $('#field-keywords');
-                       if ( ! wpTrac.hiddenEl.length )
-                               return;
</del><ins>+                        // 'User Interface' preferences tab => 'Help Links' (and removes icons-only setting)
+                       var uitab = $('#tab_userinterface');
+                       if ( uitab.length ) {
+                               if ( uitab.hasClass('active') ) {
+                                       uitab.text('Help Links');
+                                       $('input[name="ui.use_symbols"]').closest('div.field').remove();
+                               } else {
+                                       uitab.find('a').text('Help Links');
+                               }
+                       }
+               },
</ins><span class="cx"> 
</span><del>-                       // Designed so the list could have come from another file.
-                       if ( typeof coreKeywordList === 'undefined' )
</del><ins>+                // If we're not dealing with a trusted bug gardener:
+               nonGardeners: function() {
+                       if ( wpTrac.gardener ) {
</ins><span class="cx">                           return;
</span><ins>+                       }
</ins><span class="cx"> 
</span><del>-                       // If we're not a gardener and we're on /newticket (field-owner check), declutter.
-                       if ( ! wpTrac.gardener && $('#field-owner').length ) {
</del><ins>+                        var version,
+                               elements = {},
+                               remove = true;
+
+                       // If we're on /newticket (based on the field-owner check), declutter.
+                       if ( $('#field-owner').length ) {
</ins><span class="cx">                           $('#field-priority, #field-severity, #field-milestone, #field-cc, #field-keywords').parents('td').hide().prev().hide();
</span><span class="cx">                  }
</span><del>-       
-                       // Generate the workflow template.
-                       wpTrac.template();
</del><span class="cx"> 
</span><del>-                       wpTrac.field.add = $('#keyword-add');
</del><ins>+                        elements.type = $('#field-type');
+                       elements.version = $('#field-version');
+                       version = elements.version.val();
</ins><span class="cx"> 
</span><del>-                       // Load up the initial keywords and the dropdown.
-                       wpTrac.populate();
</del><ins>+                        // Remove task (blessed), or make a task ticket read only.
+                       if ( 'task (blessed)' === elements.type.val() ) {
+                               elements.type.after('<input type="hidden" name="field_type" value="task (blessed)" /> task (blessed)')
+                                       .parent().css('vertical-align', 'middle').end()
+                                       .remove();
+                       } else {
+                               elements.type.find('option[value="task (blessed)"]').remove();
+                       }
</ins><span class="cx"> 
</span><del>-                       // Save these for later.
-                       wpTrac.originalKeywords = $.merge([], wpTrac.keywords);
</del><ins>+                        // Once a Version is set, remove newer versions.
+                       if ( version ) {
+                               elements.version.find('option').each( function() {
+                                       var value = $(this).val();
+                                       if ( version === value )
+                                               remove = false;
+                                       else if ( remove && value )
+                                               $(this).remove();
+                               });
+                       }
+               },
</ins><span class="cx"> 
</span><del>-                       // Catch the submit to see if keywords were simply reordered.
-                       wpTrac.hiddenEl.parents('form').submit( wpTrac.submit );
</del><ins>+                workflow: (function() {
+                       var keywords = {},
+                               originalKeywords = {},
+                               elements = {};
</ins><span class="cx"> 
</span><del>-                       // Keyword removal.
-                       $('#keyword-bin').delegate('a', 'click', function(e) {
-                               e.preventDefault();
-                               wpTrac.removeKeyword( $(this).parent() );
-                       });
</del><ins>+                        return {
+                               init: function() {
+                                       elements.hiddenEl = $('#field-keywords');
+                                       if ( ! elements.hiddenEl.length ) {
+                                               return;
+                                       }
</ins><span class="cx"> 
</span><del>-                       // Keyword adds.
-                       $('#keyword-add').bind('change keypress', function(e) {
-                               if ( e.type === 'keypress' ) {
-                                       if ( e.which === 13 ) {
-                                               e.stopPropagation();
-                                               e.preventDefault();
-                                       } else {
</del><ins>+                                        // Designed so the list could have come from another file.
+                                       if ( typeof coreKeywordList === 'undefined' ) {
</ins><span class="cx">                                           return;
</span><span class="cx">                                  }
</span><del>-                               }
-                               wpTrac.addKeyword( $(this).val() );
-                               $(this).val('');
-                       });
</del><span class="cx"> 
</span><del>-                       // Manual link.
-                       $('#edit-keywords').click( function() {
-                               wpTrac.hiddenEl.show().focus();
-                               $(this).hide();
-                               wpTrac.hiddenEl.change( wpTrac.populate );
-                       });
</del><ins>+                                        // Generate the workflow template.
+                                       wpTrac.workflow.template();
</ins><span class="cx"> 
</span><del>-                       // If we're not dealing with a trusted bug gardener:
-                       if ( ! wpTrac.gardener ) {
-                               var remove = true, version;
-                               wpTrac.field.type = $('#field-type');
-                               wpTrac.field.version = $('#field-version');
-                               version = wpTrac.field.version.val();
</del><ins>+                                        elements.add = $('#keyword-add');
</ins><span class="cx"> 
</span><del>-                               // Remove task (blessed), or make a task ticket read only.
-                               if ( 'task (blessed)' === wpTrac.field.type.val() ) {
-                                       wpTrac.field.type.after('<input type="hidden" name="field_type" value="task (blessed)" /> task (blessed)')
-                                               .parent().css('vertical-align', 'middle').end()
-                                               .remove();
-                               } else {
-                                       wpTrac.field.type.find('option[value="task (blessed)"]').remove();
-                               }
</del><ins>+                                        // Load up the initial keywords and the dropdown.
+                                       wpTrac.workflow.populate();
</ins><span class="cx"> 
</span><del>-                               // Once a Version is set, remove newer versions.
-                               if ( version ) {
-                                       wpTrac.field.version.find('option').each( function() {
-                                               var value = $(this).val();
-                                               if ( version === value )
-                                                       remove = false;
-                                               else if ( remove && value )
-                                                       $(this).remove();
-                                       });
-                               }
-                       }
-               },
</del><ins>+                                        // Save these for later.
+                                       originalKeywords = $.merge([], keywords);
</ins><span class="cx"> 
</span><del>-               // Generates the workflow template.
-               template : function() {
-                       var container = wpTrac.hiddenEl.parent(), html, labelWidth;
</del><ins>+                                        // Catch the submit to see if keywords were simply reordered.
+                                       elements.hiddenEl.parents('form').submit( wpTrac.workflow.submit );
</ins><span class="cx"> 
</span><del>-                       // Necessary to keep everything in line. The + 4 is a careful CSS balance.
-                       labelWidth = container.prev().width() + 4;
</del><ins>+                                        // Keyword removal.
+                                       elements.bin.on( 'click', 'a', function(e) {
+                                               e.preventDefault();
+                                               wpTrac.workflow.removeKeyword( $(this).parent() );
+                                       });
</ins><span class="cx"> 
</span><del>-                       // Rearrange the table to suit our needs.
-                       container.prev().detach().end()
-                               .attr('colspan', '2').addClass('has-js')
-                               .parents('table').css('table-layout', 'fixed');
</del><ins>+                                        // Keyword adds.
+                                       $('#keyword-add').bind('change keypress', function(e) {
+                                               if ( e.type === 'keypress' ) {
+                                                       if ( e.which === 13 ) {
+                                                               e.stopPropagation();
+                                                               e.preventDefault();
+                                                       } else {
+                                                               return;
+                                                       }
+                                               }
+                                               wpTrac.workflow.addKeyword( $(this).val() );
+                                               $(this).val('');
+                                       });
</ins><span class="cx"> 
</span><del>-                       // If the owner field exists, then we're on /newticket. Remove it.
-                       $('#field-owner').parents('tr').remove();
</del><ins>+                                        // Manual link.
+                                       $('#edit-keywords').click( function() {
+                                               elements.hiddenEl.show().focus();
+                                               $(this).hide();
+                                               elements.hiddenEl.change( wpTrac.workflow.populate );
+                                       });
+                               },
</ins><span class="cx"> 
</span><del>-                       html = '<a id="edit-keywords">manual</a>';
-                       html += '<div><label id="keyword-label" for="keyword-add" style="width:' + labelWidth + 'px">Workflow Keywords:</label>';
-                       html += '<select id="keyword-add"><option value=""> - Add - </option></select></div>';
-                       html += '<div id="keyword-bin"></div>';
-                       container.prepend( html );
</del><ins>+                                // Generates the workflow template.
+                               template : function() {
+                                       var container = elements.hiddenEl.parent(), html, labelWidth;
</ins><span class="cx"> 
</span><del>-                       // Walk in the footsteps of Firefox autocomplete's trail of destruction,
-                       // tidying the radio buttons in its wake. See WP#17051.
-                       if ( $.browser.mozilla ) {
-                               $('#action input:radio').each( function() {
-                                       this.checked = this.defaultChecked;
-                               });
-                       }
-               },
</del><ins>+                                        // Necessary to keep everything in line. The + 4 is a careful CSS balance.
+                                       labelWidth = container.prev().width() + 4;
</ins><span class="cx"> 
</span><del>-               // Populates the keywords and dropdown.
-               populate : function() {
-                       var bin = $('#keyword-bin');
</del><ins>+                                        // Rearrange the table to suit our needs.
+                                       container.prev().detach().end()
+                                               .attr('colspan', '2').addClass('has-js')
+                                               .parents('table').css('table-layout', 'fixed');
</ins><span class="cx"> 
</span><del>-                       // For repopulation. Starting over.
-                       if ( bin.find('span').length )
-                               bin.empty();
</del><ins>+                                        // If the owner field exists, then we're on /newticket. Remove it.
+                                       $('#field-owner').parents('tr').remove();
</ins><span class="cx"> 
</span><del>-                       // Replace commas, collapse spaces, trim, then split by space.
-                       wpTrac.keywords = $.trim( wpTrac.hiddenEl.val().replace(',', ' ').replace(/ +/g, ' ') ).split(' ');
</del><ins>+                                        html = '<a id="edit-keywords">manual</a>';
+                                       html += '<div><label id="keyword-label" for="keyword-add" style="width:' + labelWidth + 'px">Workflow Keywords:</label>';
+                                       html += '<select id="keyword-add"><option value=""> - Add - </option></select></div>';
+                                       html += '<div id="keyword-bin"></div>';
+                                       container.prepend( html );
+                                       elements.bin = $('#keyword-bin');
</ins><span class="cx"> 
</span><del>-                       // Put our cleaned up version back into the hidden field.
-                       wpTrac.hiddenEl.val( wpTrac.keywords.join(' ') );
</del><ins>+                                        // Walk in the footsteps of Firefox autocomplete's trail of destruction,
+                                       // tidying the radio buttons in its wake. See #WP17051.
+                                       if ( $.browser.mozilla ) {
+                                               $('#action input:radio').each( function() {
+                                                       this.checked = this.defaultChecked;
+                                               });
+                                       }
+                               },
</ins><span class="cx"> 
</span><del>-                       // If we have a non-empty keyword, let's go through the process of adding the spans.
-                       if ( 1 !== wpTrac.keywords.length || wpTrac.keywords[0] !== '' ) {
-                               $.each( wpTrac.keywords, function( k, v ) {
-                                       var html = $('<span />').text(v).attr('data-keyword', v).prepend('<a href="#" />');
-                                       if ( v in coreKeywordList )
-                                               html.attr('title', coreKeywordList[v]);
-                                       html.appendTo( bin );
-                               });
-                       }
</del><ins>+                                // Populates the keywords and dropdown.
+                               populate : function() {
+                                       // For repopulation. Starting over.
+                                       if ( elements.bin.find('span').length )
+                                               elements.bin.empty();
</ins><span class="cx"> 
</span><del>-                       // Populate the dropdown.
-                       $.each( coreKeywordList, function( k, v ) {
-                               // Don't show special (permission-based) ones.
-                               if ( ! wpTrac.gardener && -1 !== $.inArray( k, gardenerKeywordList ) )
-                                       return;
-                               wpTrac.field.add.append( '<option value="' + k + ( -1 !== $.inArray( k, wpTrac.keywords ) ? '" disabled="disabled">* ' : '">' ) + k + '</option>' );
-                       });
-               },
</del><ins>+                                        // Replace commas, collapse spaces, trim, then split by space.
+                                       keywords = $.trim( elements.hiddenEl.val().replace(',', ' ').replace(/ +/g, ' ') ).split(' ');
</ins><span class="cx"> 
</span><del>-               // Add a keyword. Takes a sanitized string.
-               addKeyword : function( keyword ) {
-                       if ( ! keyword )
-                               return;
-                       var html, title = '';
-                       // Don't add it again.
-                       if ( -1 !== $.inArray( keyword, wpTrac.keywords ) )
-                               return;
-                       wpTrac.keywords.push( keyword );
</del><ins>+                                        // Put our cleaned up version back into the hidden field.
+                                       elements.hiddenEl.val( keywords.join(' ') );
</ins><span class="cx"> 
</span><del>-                       // Update the dropdown. Core keywords also get a title attribute with their description.
-                       if ( keyword in coreKeywordList ) {
-                               wpTrac.field.add.find('option[value=' + keyword + ']').prop('disabled', true).text('* ' + keyword);
-                               title = coreKeywordList[keyword];
-                       }
</del><ins>+                                        // If we have a non-empty keyword, let's go through the process of adding the spans.
+                                       if ( 1 !== keywords.length || keywords[0] !== '' ) {
+                                               $.each( keywords, function( k, v ) {
+                                                       var html = $('<span />').text(v).attr('data-keyword', v).prepend('<a href="#" />');
+                                                       if ( v in coreKeywordList )
+                                                               html.attr('title', coreKeywordList[v]);
+                                                       html.appendTo( elements.bin );
+                                               });
+                                       }
</ins><span class="cx"> 
</span><del>-                       if ( 'has-patch' === keyword )
-                               wpTrac.removeKeyword( 'needs-patch' );
-                       else if ( 'needs-patch' === keyword )
-                               wpTrac.removeKeyword( 'has-patch' );
</del><ins>+                                        // Populate the dropdown.
+                                       $.each( coreKeywordList, function( k, v ) {
+                                               // Don't show special (permission-based) ones.
+                                               if ( ! wpTrac.gardener && -1 !== $.inArray( k, gardenerKeywordList ) )
+                                                       return;
+                                               elements.add.append( '<option value="' + k + ( -1 !== $.inArray( k, keywords ) ? '" disabled="disabled">* ' : '">' ) + k + '</option>' );
+                                       });
+                               },
</ins><span class="cx"> 
</span><del>-                       // Add it to the bin, and refresh the hidden input.
-                       html = $('<span />').text(keyword).attr('data-keyword', keyword).prepend('<a href="#" />');
-                       if ( title )
-                               html.attr('title', title);
-                       html.appendTo( $('#keyword-bin') );
-                       wpTrac.hiddenEl.val( wpTrac.keywords.join(' ') );
-               },
</del><ins>+                                // Add a keyword. Takes a sanitized string.
+                               addKeyword : function( keyword ) {
+                                       if ( ! keyword )
+                                               return;
+                                       var html, title = '';
+                                       // Don't add it again.
+                                       if ( -1 !== $.inArray( keyword, keywords ) )
+                                               return;
+                                       keywords.push( keyword );
</ins><span class="cx"> 
</span><del>-               // Remove a keyword. Takes a jQuery object of a keyword in the bin, or a sanitized keyword as a string.
-               removeKeyword : function( object ) {
-                       var keyword;
-                       if ( typeof object === 'string' ) {
-                               keyword = object;
-                               object = $('#keyword-bin').find('span[data-keyword="' + keyword + '"]');
-                               if ( ! object.length )
-                                       return;
-                       } else {
-                               keyword = object.text();
-                       }
</del><ins>+                                        // Update the dropdown. Core keywords also get a title attribute with their description.
+                                       if ( keyword in coreKeywordList ) {
+                                               elements.add.find('option[value=' + keyword + ']').prop('disabled', true).text('* ' + keyword);
+                                               title = coreKeywordList[keyword];
+                                       }
</ins><span class="cx"> 
</span><del>-                       wpTrac.keywords = $.grep(wpTrac.keywords, function(v) {
-                               return v != keyword;
-                       });
-                       // Update the core keyword dropdown.
-                       if ( keyword in coreKeywordList )
-                               wpTrac.field.add.find('option[value=' + keyword + ']').prop('disabled', false).text( keyword );
-                       wpTrac.hiddenEl.val( wpTrac.keywords.join(' ') );
-                       object.remove();
-               },
</del><ins>+                                        if ( 'has-patch' === keyword ) {
+                                               wpTrac.workflow.removeKeyword( 'needs-patch' );
+                                       } else if ( 'needs-patch' === keyword ) {
+                                               wpTrac.workflow.removeKeyword( 'has-patch' );
+                                       }
</ins><span class="cx"> 
</span><del>-               // Check on submit that we're not just re-ordering keywords.
-               // Otherwise, Trac flips out and adds a useless 'Keywords changed from X to X' marker.
-               submit : function(e) {
-                       if ( wpTrac.keywords.length !== wpTrac.originalKeywords.length )
-                               return;
-                       var testKeywords = $.grep(wpTrac.keywords, function(v) {
-                               return -1 === $.inArray( v, wpTrac.originalKeywords );
-                       });
-                       // If the difference has no length, then restore to the original keyword order.
-                       if ( ! testKeywords.length )
-                               wpTrac.hiddenEl.val( wpTrac.originalKeywords.join(' ') );
-               },
</del><ins>+                                        // Add it to the bin, and refresh the hidden input.
+                                       html = $('<span />').text(keyword).attr('data-keyword', keyword).prepend('<a href="#" />');
+                                       if ( title )
+                                               html.attr('title', title);
+                                       html.appendTo( elements.bin );
+                                       elements.hiddenEl.val( keywords.join(' ') );
+                               },
</ins><span class="cx"> 
</span><del>-               hide_cc_field: function() {
-                       var content = $( '#content' );
-                       if ( content.hasClass( 'query' ) ) {
-                               $( 'table.trac-clause tr.actions option[value="cc"]' ).remove();
-                               $( '#columns' ).find( 'input[type="checkbox"][name="col"][value="cc"]' ).parent().remove();
-                       }
-                       if ( content.hasClass( 'ticket' ) ) {
-                               $( '#changelog div.change' ).has( 'li.trac-field-cc' ).each( function() {
-                                       var change = $(this), changes = change.children( 'ul.changes' );
-                                       /* Three possibilities:
-                                          The comment is just a single CC (hide the whole comment)
-                                          The comment is a CC plus a comment (hide the CC line)
-                                          The comment contains multiple property changes (hide only the CC line)
-                                       */
-                                       if ( changes.children( 'li' ).length === 1 ) {
-                                               if ( change.children( 'div.comment' ).length === 0 ) {
-                                                       change.hide();
-                                               } else {
-                                                       changes.hide();
-                                               }
</del><ins>+                                // Remove a keyword. Takes a jQuery object of a keyword in the bin, or a sanitized keyword as a string.
+                               removeKeyword : function( object ) {
+                                       var keyword;
+                                       if ( typeof object === 'string' ) {
+                                               keyword = object;
+                                               object = elements.bin.find('span[data-keyword="' + keyword + '"]');
+                                               if ( ! object.length )
+                                                       return;
</ins><span class="cx">                                   } else {
</span><del>-                                               changes.children( 'li.trac-field-cc' ).hide();
</del><ins>+                                                keyword = object.text();
</ins><span class="cx">                                   }
</span><del>-                               });
</del><ins>+
+                                       keywords = $.grep( keywords, function(v) {
+                                               return v != keyword;
+                                       });
+
+                                       // Update the core keyword dropdown.
+                                       if ( keyword in coreKeywordList )
+                                               elements.add.find('option[value=' + keyword + ']').prop('disabled', false).text( keyword );
+                                       elements.hiddenEl.val( keywords.join(' ') );
+                                       object.remove();
+                               },
+
+                               // Check on submit that we're not just re-ordering keywords.
+                               // Otherwise, Trac flips out and adds a useless 'Keywords changed from X to X' marker.
+                               submit : function(e) {
+                                       if ( keywords.length !== originalKeywords.length )
+                                               return;
+                                       var testKeywords = $.grep( keywords, function(v) {
+                                               return -1 === $.inArray( v, originalKeywords );
+                                       });
+                                       // If the difference has no length, then restore to the original keyword order.
+                                       if ( ! testKeywords.length )
+                                               elements.hiddenEl.val( originalKeywords.join(' ') );
+                               }
</ins><span class="cx">                   }
</span><del>-               },
</del><ins>+                }()),
</ins><span class="cx"> 
</span><span class="cx">          notifications: (function() {
</span><span class="cx">                  var notifications, endpoint, _ticket;
</span><span class="cx"> 
</span><span class="cx">                  function init( settings ) {
</span><del>-                               $( wpTrac.hide_cc_field );
</del><ins>+                                $( hide_cc_field );
</ins><span class="cx">                           if ( ! settings.authenticated ) {
</span><span class="cx">                                  return;
</span><span class="cx">                          }
</span><span class="lines">@@ -427,9 +423,36 @@
</span><span class="cx">                                  _ticket = settings.ticket;
</span><span class="cx">                                  ticketInit( _ticket );
</span><span class="cx">                          }
</span><del>-                               $( reportInit() );
</del><ins>+                                $( reportInit );
</ins><span class="cx">                   }
</span><span class="cx"> 
</span><ins>+                       function hide_cc_field() {
+                               var content = $( '#content' );
+                               if ( content.hasClass( 'query' ) ) {
+                                       $( 'table.trac-clause tr.actions option[value="cc"]' ).remove();
+                                       $( '#columns' ).find( 'input[type="checkbox"][name="col"][value="cc"]' ).parent().remove();
+                               }
+                               if ( content.hasClass( 'ticket' ) ) {
+                                       $( '#changelog div.change' ).has( 'li.trac-field-cc' ).each( function() {
+                                               var change = $(this), changes = change.children( 'ul.changes' );
+                                               /* Three possibilities:
+                                                  The comment is just a single CC (hide the whole comment)
+                                                  The comment is a CC plus a comment (hide the CC line)
+                                                  The comment contains multiple property changes (hide only the CC line)
+                                               */
+                                               if ( changes.children( 'li' ).length === 1 ) {
+                                                       if ( change.children( 'div.comment' ).length === 0 ) {
+                                                               change.hide();
+                                                       } else {
+                                                               changes.hide();
+                                                       }
+                                               } else {
+                                                       changes.children( 'li.trac-field-cc' ).hide();
+                                               }
+                                       });
+                               }
+                       }
+
</ins><span class="cx">                   function ticketInit( ticket ) {
</span><span class="cx">                          $.ajax({
</span><span class="cx">                                  url: endpoint + '?trac-notifications=' + ticket,
</span></span></pre>
</div>
</div>

</body>
</html>