/* CONCAT of
/2static/script/fecru/ajax.js
/2static/script/fecru/browse.js
/2static/script/fecru/dialog.js
/2static/script/fecru/hover.js
/2static/script/fecru/profile.js
/2static/script/fecru/rss.js
/2static/script/fecru/ui.js
/2static/script/fecru/onReady.js
/2static/script/fecru/star.js
/2static/script/fecru/prefs.js
/script/common/global.js
/2static/script/lib/ajs/contentnamesearch.js
/2static/script/lib/ajs/master.js
/2static/script/fe/fisheye-ui.js
/2static/script/fe/fisheye-changeset.js
/2static/script/fe/fisheye-history.js
/2static/script/fe/fisheye-search.js
/script/activitystream.js
/2static/script/cru/util.js
/2static/script/cru/dialog/dialog.js
/2static/script/cru/dialog/dialog-event.js
/2static/script/cru/review/email-comments.js
*/
/* START /2static/script/fecru/ajax.js */
if (!AJS.FECRU) {
    AJS.FECRU = {};
}
AJS.FECRU.AJAX = {};
(function() {
    /******************************************************************
     ******************** AJAX METHODS ********************************
     ******************************************************************/

    AJS.FECRU.AJAX.ajaxDo = function (url, params, onCompleteFunc, noDialog) {
        AJS.FECRU.AJAX.ajaxUpdate(url, params, null, onCompleteFunc, noDialog);
    };

    /**
     * Makes a Ajax request with the specified parameters and runs the onCompleteFunc
     * function when the call returns. Optionally, a DOM element can be passed in
     * which will be updated with the payload of the Ajax response (the JSON response
     * object must contain a "payload" member.
     * Also, the user can pass a second function (onPayloadEvalFunc) that will be
     * called after the payload content has been put in the specified elementToUpdate
     * element. If elementIdToUpdate is null, the onPayloadEvalFunc will not be called.
     *
     * noDialog is optional: if falsy, the error message dialog is shown on error.
     * If it is a callback, it is invoked on error instead of showing the dialog.
     * Otherwise, if it is truthy, the dialog is not shown on error.
     */
    AJS.FECRU.AJAX.ajaxUpdate = function (url, params, elementIdToUpdate, onCompleteFunc, noDialog) {
        // Implemementation note:
        // jQuery does an eval(req.responseText) for us internally. We have an onSuccess callback to
        // intercept this, otherwise we end up eval'ing the response text twice (which is poor form).
        // The onComplete callback gives us access to the XmlHttpRequest and allows us to do extra
        // error handling if required.

        var data = false;
        var onSuccess = function(response) {
            data = response;
        };

        var onComplete = function(req) {
            var cleanUpOnFail = function () {
                if (onCompleteFunc) {
                    onCompleteFunc({ worked: false });  //allow function to clean up
                }
            };
            try {
                if (req.status == 0 ) {

                    return;
                }
                if (!data || !isECMA(req)) {
                    noDialog || displayUnexpectedResponse(req);
                    cleanUpOnFail();
                    return;
                }

                var $updateMe;
                if (elementIdToUpdate) {
                    $updateMe = AJS.$("#"+elementIdToUpdate);
                }

                if (!data.worked) {
                    AJS.$('#errorDesc').html("<span class='icon-error'>Error</span><p>" + ourHtmlEscape(data.errorMsg));
                    if (!noDialog) {
                        if (data.userError) {
                            showUserErrorBox();
                        } else {
                            showErrorBox();
                        }
                    }
                } else if ($updateMe && $updateMe.length === 1) {
                    $updateMe.html(data.payload);
                }
                if (onCompleteFunc) {
                    onCompleteFunc(data);
                }
            } catch (e) {
                displayErrorInResponse(e);
                cleanUpOnFail()    ;
            }
        };
        AJS.$.ajax( {
            type: "post",
            url: url,
            data: params,
            dataType: "json", // TODO: check that no other callers expect html/xml data
            success: onSuccess,
            complete: onComplete,
            error: noDialog ? (AJS.$.isFunction(noDialog) ? noDialog : NOOP) : ajaxFailure
        } );
        return false;
    };

    var NOOP = function () {};

    var ajaxFailure = function (req) {
        try {
            if (!isECMA(req)) {
                displayUnexpectedResponse(req);
                return;
            }
            var resp = eval('(' + req.responseText + ')');
            AJS.$('#errorDesc').html("<span class='icon-error'>Error</span><p>There was an error in the response from the server.<br>" +
                                     "Refresh the page before posting more comments.<br>" +
                                     "Click 'show details' for more information." );
            AJS.$('#showErrorDetails').show();
            appendErrorResponse(resp.errorMsg);
            showErrorBox();
        } catch (e) {
            AJS.log('ajax failure: ' + e);
            displayErrorInResponse(e);
        }
    };

    /******************************************************************
     ******************** HELPER METHODS ******************************
     ******************************************************************/

    var isECMA = function (req) {
        return isContentType(req, /^application\/ecmascript/);
    };

    var isHTML = function (req) {
        return isContentType(req, /^text\/html/);
    };

    var isContentType = function (req, targetType) {
        var respContentType = req.getResponseHeader("Content-Type");
        return respContentType != null && respContentType.match(targetType);
    };

    var isHTMLFrag = function (req) {
        var responseText = req.responseText;
        return responseText != null && !responseText.match(/<body/i);
    };

    var ourHtmlEscape = function (input) {
        try {
            return input.replace(/&/g, '&amp;').
                    replace(/>/g, '&gt;').
                    replace(/</g, '&lt;').
                    replace(/"/g, '&quot;');
        } catch(e) {
            return input.message || e.message;
        }
    };

    AJS.FECRU.AJAX.startSpin = function (id, replace, tagType, className) {
        className = className ? className + " spinner" : "spinner";

        var $el = typeof(id) == 'object' ? AJS.$(id) : AJS.$("#"+id);
        var tag = tagType || 'div';
        if ($el.length === 1) {
            var $spinner = AJS.$(document.createElement(tag));
            $spinner.addClass(className)
                    .html("<img src='" + fishEyePageContext + "/" + fishEyeSTATICDIR + "/2static/images/blank.gif'>");
            if (replace) {
                $el.replaceWith($spinner);
            } else {
                $spinner.insertAfter($el);
            }
            return true;
        }
        return false;
    };

    AJS.FECRU.AJAX.stopSpin = function (el, tagType) {
        if (el) {
            var emptyDiv = document.createElement(tagType || "div");
            el.parentNode.replaceChild(emptyDiv, el.nextSibling);
        }
    };

    /******************************************************************
     ******************** ERROR HANDLING ******************************
     ******************************************************************/

    AJS.FECRU.AJAX.checkError = function (resp) {
        if (resp.error) {
            AJS.$('#errorDesc').html('<span class="icon-error">Error</span><p>' + resp.errorHtml);
            return true;
        }
        return false;
    };

    var appendErrorResponse = function (text) {
        if (text) {
            AJS.$('#errorResponses').show();
            AJS.$('#errorResponses').append(AJS.$('<div class="errorResponse-count">Error ' + (AJS.$('#errorResponses').find('.errorResponse').length + 1) + '</div>'));
            AJS.$('#errorResponses').append(AJS.$('<div class="errorResponse"></div>').html( ourHtmlEscape(text) ));
        }
    };

    var dialog = 0;

    var showErrorBox = function () {
        showNotificationBox('An error has occurred');
    };

    var showUserErrorBox = function () {
        showNotificationBox('Cannot perform requested action');
        AJS.$('#showErrorDetails').hide();
        AJS.$('#errorDetails').hide();
    };

    var showNotificationBox = function (title) {
        var util = AJS.CRU.UTIL;
        
        if (dialog == 0) {
            dialog = AJS.FECRU.DIALOG.create(440, 330);
            dialog.addHeader(title)
                .addPanel("Information", AJS.$('#errorBox'))
                .addButton("Reload", function() { window.location.reload(); })
                .addButton("Close", function() {
                    AJS.$('#errorResponses .errorResponse').remove();
                    AJS.$('#errorResponses .errorResponse-count').remove();
                    dialog.hide();
                } );
        }

        if (!util.isAnyDialogShowing()) {
            if (util.isAjaxDialogSpinning()) {
                util.stopAjaxDialogSpin();
            }
            dialog.show();
        }
        return false;
    };

    var displayUnexpectedResponse = function (req) {
        if (isHTML(req)) {
            if (isHTMLFrag(req)) {
                AJS.$('#errorDesc').html("<span class='icon-error'>Error</span><p>There was an error in the response from the server.<br>" +
                                         "Refresh the page before posting more comments.<br>" +
                                         "Click 'show details' for more information." );
                appendErrorResponse(req.responseText);
                showErrorBox();
            } else {
                AJS.$('#errorDesc').html(AJS.$(req.responseText).filter('title').html());
                appendErrorResponse(AJS.$(req.responseText).filter('body').html());
                showErrorBox();
            }
        } else if (!req.responseText) {
            //no-op here: no responseText means most likely server is gone offline.
        }
    };

    var displayErrorInResponse = function (err) {
        AJS.$('#errorDesc').html("<span class='icon-error'>Error</span><p>There was an error in the response from the server.<br>" +
                                 "Refresh the page before posting more comments.<br>" +
                                 "Click 'show details' for more information." );
        appendErrorResponse(err);
        showErrorBox();
    };
})();
;
/* END /2static/script/fecru/ajax.js */
/* START /2static/script/fecru/browse.js */
if (!AJS.FECRU) {
    AJS.FECRU = {};
}
AJS.FECRU.BROWSE = {};

(function() {
    /**
     * Open a folder, given the node of its span, and then run a post function. Opening the node may mean loading a
     * subtree via Ajax -- if this happens, the post function will be run after the subtree has loaded.
     *
     * @param $node -- a jQuery wrapper around the span tag containing the folder name (which is a sibling of the ul tag containing its children)
     * @param postFn -- a function of no arguments to run after opening the node.
     */
    var toggleFolder = function($node, postFn, pathLinkFn, fileLinkFn) {
        if ($node.hasClass("unfilled")) {
            $node.removeClass("unfilled");
            var liId = $node.closest("li.tree-li").attr('id');
            var treeData = AJS.$("#tree-root").data("extraAttrs")[liId];
            var ajaxParameters = {
                path: treeData.path,
                repName: treeData.repname,
                baseUrl: treeData.baseurl,
                noFiles: treeData.nofiles
            };
            var selectedPath = AJS.$("#selectedDirTreeNode").children("input[name='selectedPath']").val();
            if (selectedPath) {
                ajaxParameters.selectedPath = selectedPath;
            }
            $node.after(AJS.$("<span class='dirlistSpinner'>&nbsp;</span>").show());
            $node.removeClass("closed").addClass("open");
            var params = AJS.$("#queryStrSuffix").val();
            if (params) {
                ajaxParameters.queryStrSuffix = params;
            }
            AJS.FECRU.AJAX.ajaxDo(fishEyePageContext + "/json/fe/loadSubTree.do", ajaxParameters, function(resp) {
                if (resp.worked) {
                    // use 'clean' to avoid the regex that jQuery uses to distinguish HTML from ids
                    var replacement = AJS.$.clean([resp.payload], document);
                    $node.parent().replaceWith(replacement);
                    postFn();
                }
            }, false);
        } else {
            var oldClass = "closed";
            var newClass = "open";
            if ($node.hasClass("open")) {
                oldClass = "open";
                newClass = "closed";
            }
            $node.removeClass(oldClass).addClass(newClass);
            $node.siblings("ul." + oldClass).removeClass(oldClass).addClass(newClass);
            postFn();
        }
    };

    var moveSelectedDirTreeIds = function($newLink) {
        AJS.$("#selectedDirTreeNode").attr("id", "");
        AJS.$("#selectedDirTreeLink").attr("id", "");
        $newLink.closest("span").attr("id", "selectedDirTreeNode");
        $newLink.attr("id", "selectedDirTreeLink");
    };

    /**
     * Open the parents of this link and set the selected ids
     * @param $linkNode the link in the directory tree which is selected.
     */
    AJS.FECRU.BROWSE.selectLink = function($linkNode, folderToggledFn, pathLinkFn) {
        var $span = $linkNode.parent();
        var done = function() {
            folderToggledFn($linkNode);
        };
        if ($span.hasClass("closed")) {
            AJS.FECRU.AJAX.startSpin(AJS.$("#filebox"), true); // Get's stopped when #fileResults has html() called
            toggleFolder($span, done, pathLinkFn);
        } else {
            done();
        }
        moveSelectedDirTreeIds($linkNode);
    };

    AJS.FECRU.BROWSE.setupDirectoryTree = function(pathLinkFn, fileLinkFn) {
        AJS.$("span.tree").live("click", function(event) {
            if (AJS.$(event.target).is("a")) {
                return true; // let the link do its job
            }
            toggleFolder(AJS.$(this), function() {}, pathLinkFn, fileLinkFn);
            event.stopPropagation();
            return false;
        });
        if (pathLinkFn) {
            AJS.$("#navigation-tree a.pathLink").live("click", function(event) {
                return pathLinkFn(event);
            });
        }
        if (fileLinkFn) {
            AJS.$("#navigation-tree a.fileLink").live("click", function(event) {
                return fileLinkFn(event);
            });
        }
    };

    AJS.FECRU.BROWSE.initDirectoryTree = function(pathLinkFn, fileLinkFn) {
        AJS.FECRU.BROWSE.setupDirectoryTree(pathLinkFn, fileLinkFn);
        if (AJS.FE) {
            AJS.FE.setupPanes();
        }
        AJS.$("div.panel-directory","#content-navigation").scrollTo(AJS.$("#selectedDirTreeNode"));
    };
})();
;
/* END /2static/script/fecru/browse.js */
/* START /2static/script/fecru/dialog.js */
if (!AJS.FECRU) {
    AJS.FECRU = {};
}
AJS.FECRU.DIALOG = {};

(function () {
    var safeDimensions = function (maxWidth, maxHeight, margin) {
        margin = margin || 50;
        return {
            height: Math.min(AJS.$(window).height() - margin, maxHeight),
            width: Math.min(AJS.$(window).width() - margin, maxWidth)
        };
    };

    /**
     * @param optionalClass a string containing a css class that should be applied to the dialog
     */
    AJS.FECRU.DIALOG.create = function (maxWidth, maxHeight, id, optionalClass) {
        var dimensions = safeDimensions(maxWidth, maxHeight);
        var dialog = new AJS.Dialog(dimensions.width, dimensions.height, id);
        // Add the height and width of the dialog as properties of the object
        AJS.$.extend(dialog, {width: dimensions.width, height: dimensions.height});
        if (optionalClass) {
            dialog.addClass(optionalClass)
        }
        return dialog;
    };
})();
;
/* END /2static/script/fecru/dialog.js */
/* START /2static/script/fecru/hover.js */
if (!AJS.FECRU) {
    AJS.FECRU = {};
}
AJS.FECRU.HOVER = (function() {
    var opts = {
        onHover: true,
        showArrow: false,
        fadeTime: 200,
        hideDelay: 500,
        showDelay: 600,
        width: 300,
        offsetX: 10,
        offsetY: 2,
        container: "body",
        cacheContent:false,
        useLiveEvents : true
    };
    var __cache = {};
    var CACHE_FOREVER = -1;
    //todo: doc the create* function params
    var createDisplayHandler = function(createUrl, createCacheKey, createParams, minutesToCache, cachetype) {
        if (!cachetype) {
            cachetype = '__all';
        }
        return function ($contentDiv, mouseOverTrigger, showPopup) {
            var $trigger = AJS.$(mouseOverTrigger);
            var url = createUrl($trigger);
            var key = createCacheKey($trigger);
            var cache = __cache[cachetype] || {};
            var cacheHit = cache[key] || {isHit:false};
            var lastFetchTime = cacheHit.lastFetchTime || 0;
            var cacheTimedOut = (minutesToCache !== CACHE_FOREVER) && (new Date().getTime() - lastFetchTime) > (minutesToCache * 1000 * 60);
            if (key) {
                $contentDiv.data("targetToDisplay", {key:key});
                var error = function(xmlHttpRequest, textStatus, errorThrown) {
                    $contentDiv.html(escape(textStatus) + "<br>" + escape(errorThrown ? errorThrown : ""));
                };
                var done = function(resp) {
                    if (resp.worked) {
                        if ($contentDiv.data("targetToDisplay").key == key) {
                            $contentDiv.html(resp.html);
                            showPopup();
                        }
                        cacheHit.resp = resp;
                        cacheHit.lastFetchTime = new Date().getTime();
                        cacheHit.isHit = true;
                        cache[key] = cacheHit;
                        __cache[cachetype] = cache;
                    } else {
                        $contentDiv.html('<div class="hoverpopup">' + resp.errorMsg + '</div>');
                    }
                };
                if ((!cacheHit.isHit) || cacheTimedOut) {
                    AJS.$.ajax({url: url,
                                data:createParams(key, $trigger),
                                type:"GET",
                                dataType:"json",
                                success: done,
                                error: error
                    });
                } else {
                    $contentDiv.html(cacheHit.resp.html);
                    showPopup();
                }
            }
        };
    };

    var invalidateCache = function(type, key) {
        __cache[type] = undefined;
    };

    var addAllLinkPopups = function() {
        addJiraLinkPopups();
        addCruLinkPopups();
        addCsLinkPopups();
        linkUsers();
    };

    var addJiraLinkPopups = function() {
        var jiraShowDelay = 300;
        addLinkPopups("jiralinkspan", fishEyePageContext + '/json/action/issue-tooltip.do', jiraShowDelay, CACHE_FOREVER);
    };

    var addCruLinkPopups = function() {
        addLinkPopups("crulinkspan", fishEyePageContext + '/json/cru/tooltipdata', opts.showDelay, CACHE_FOREVER);
    };

    var addCsLinkPopups = function() {
        addLinkPopups("cslinkspan", fishEyePageContext + '/json/action/cstooltipdata.do', opts.showDelay, CACHE_FOREVER, function ($elem) {
            return '//' + $elem.find('input.repname').val() + '/' + $elem.text();
        });
    };

    var addLinkPopups = function(linkSpanClass, url, showDelay, minutesToCache, issueKeyParser) {
        issueKeyParser = issueKeyParser || function ($elem) {
            return $elem.text();
        };
        var createUrl = function() {
            return url;
        };
        var createCacheKey = function($trigger) {
            var $issueKeyLink = AJS.$("a", $trigger);
            var issueKey = issueKeyParser($issueKeyLink);
            issueKey = AJS.$.trim(issueKey);
            return issueKey;
        };
        var createParams = function(key) {
            return {key:key};
        };
        var displayHandler = createDisplayHandler(createUrl, createCacheKey, createParams, minutesToCache, linkSpanClass + "cache");
        var options = AJS.$.extend(false, opts, {showDelay: showDelay});
        AJS.InlineDialog("." + linkSpanClass, linkSpanClass + "-popup", displayHandler, options);
    };

    var linkUsers = function() {
        var createUrl = function($trigger) {
            return $trigger.attr('href');
        };
        var createParams = function() {
            return {ajax :"true"};
        };
        var createCacheKey = function($trigger) {
            return createUrl($trigger);
        };

        var displayHandler = createDisplayHandler(createUrl, createCacheKey, createParams, CACHE_FOREVER, 'userlinks');
        AJS.InlineDialog("a.userorcommitter", "user-hover-inline-dialog", displayHandler, opts);
    };

    return {
        addAllLinkPopups: addAllLinkPopups,
        invalidateCache : invalidateCache
    };

})();
;
/* END /2static/script/fecru/hover.js */
/* START /2static/script/fecru/profile.js */
if (!AJS.FECRU) {
    AJS.FECRU = {};
}
if (!AJS.FECRU.PROFILE) {
    AJS.FECRU.PROFILE = (function() {
        var makeDialogFor = function($profileSettingsLink) {
            var windowWidth = AJS.$(window).width();
            var windowHeight = AJS.$(window).height();
            var width = (windowWidth < 1000) ? windowWidth - 120 : 800;
            var height = (windowHeight < 700) ? windowHeight - 100 : 700;

            var HEADER_HEIGHT = 43; //height of the dialog header, in px
            var BUTTON_HEIGHT = 44; //height of the bottons at bottom of dialog, in px.
            var iframeHeight = height - HEADER_HEIGHT - BUTTON_HEIGHT;
            var settingsDialog = AJS.FECRU.DIALOG.create(width, height, 'fecru-profile-settings-dialog');

            var deepProfileSettingsLink = $profileSettingsLink.attr("href") || fishEyePageContext + "/profile";

            // hack: we're adding a random number to the iframe id to work
            // around webkit bug: 24078 (http://lists.macosforge.org/pipermail/webkit-unassigned/2009-February/100941.html)
            var $iframe = AJS.$("<iframe id='fecru-iframe-" + (Math.ceil(Math.random() * 1000)) + "' frameborder='0' src='" + deepProfileSettingsLink + "' style='width:100%;height:" + (iframeHeight) + "px' ></iframe>");

            settingsDialog.addHeader("Settings");
            settingsDialog.addPanel("Display", $iframe);
            settingsDialog.addButton("Close", function (dialog) {
                dialog.hide();

                //todo: fix up reloading?
                if (getDialogURL()) {
                    // Remove "dialog" parameter before reloading
                    var topURL = window.location.href;
                    topURL = topURL.replace(/\?dialog=[^&]*/, "?");
                    topURL = topURL.replace(/&dialog=[^&]*/, "");
                    topURL = topURL.replace(/\?$/, "");
                    window.location.replace(topURL);
                } else {
                    window.location.reload();
                }
            });
            return settingsDialog;
        };

        AJS.$(document).ready(function () {

            var toggleMappingSubmitButton = function() {
                var disable = (AJS.$("#repositoryDropdown").attr("selectedIndex") == 0);
                AJS.$("#addMappingButton").attr("disabled", disable);
            };
            AJS.$("#repositoryDropdown").change(toggleMappingSubmitButton);
            toggleMappingSubmitButton();    // set initial state

            var settingsDialog;
            //todo may be use live events?
            var $profileSettingsLink = AJS.$("a.dialog-settings").click(function(e) {
                e.preventDefault();
                if (!settingsDialog) {
                    settingsDialog = makeDialogFor($profileSettingsLink);
                }
                settingsDialog.show();
            });

            var $form = AJS.$('form.autosubmit');
            $form.find('input,select').change(function () {
                var params = $form.serialize();
                var action = $form.attr('action');
                var $spinner = $form.find('.edit-settings-spinner').show();
                var saved = function () {
                    $spinner.hide();
                };

                AJS.FECRU.AJAX.ajaxDo(action, params, saved);
            });

            // If there's a dialog argument on our URL, open that dialog.
            var dialogURL = getDialogURL();
            if (dialogURL) {
                settingsDialog = makeDialogFor(AJS.$("<a href='" + fishEyePageContext + dialogURL + "' />"));
                settingsDialog.show();
            }

        });

        function getDialogURL() {
            var matches = /[?&]dialog=([^&]*)/.exec(location.search);
            if (matches) {
                return matches[1];
            }
            return null;
        }

        return true; //flag to stop multiple calls which adds multiple dialog boxes.
    })();
}
;
/* END /2static/script/fecru/profile.js */
/* START /2static/script/fecru/rss.js */
if (!AJS.FECRU) {
    AJS.FECRU = {};
}

AJS.FECRU.RSS = (function() {
    var makeDialogFor = function($Link) {
        var deepRSSLink = $Link.attr("href");
        var dlg = AJS.FECRU.DIALOG.create(800, 500, 'rss-settings-dialog');

        var $iframe = AJS.$("<iframe id='fecru-iframe' frameborder='0' src='" + deepRSSLink + "' style='width:100%;height:"+dlg.height+"px'></iframe>");

        dlg.addHeader("Notification Configuration")
           .addPanel("Display", $iframe)
           .addButton("Close", function (dialog) {
                dialog.hide();
                $iframe.attr("src", deepRSSLink);
            });
        return dlg;
    };
    var setupRSSDialog = function () {
        var rssDialog;
        var $rssLink = AJS.$("#dialog-rss").click(function (e) {
            e.preventDefault();
            if (!rssDialog) {
                rssDialog = makeDialogFor($rssLink);
            }
            rssDialog.show();
        });
    };

    AJS.toInit(function() {
        var $context = AJS.$("#rss-form");
        AJS.$("input, select", $context).change(function() {
            $context.submit();
        });
    });

    return {
        setupRSSDialog : setupRSSDialog
    };

})();
;
/* END /2static/script/fecru/rss.js */
/* START /2static/script/fecru/ui.js */
if (!AJS.FECRU) {
    AJS.FECRU = {};
    AJS.FECRU.UI = {};
} else if (!AJS.FECRU.UI) {
    AJS.FECRU.UI = {};
}

(function() {

    var hasSetDropdowns = false;

    AJS.FECRU.UI.setDropdowns = function (useroptions) {
        if (hasSetDropdowns) {
            AJS.log("WARNING: About to set duplicated dropdown live events");
        } else {
            hasSetDropdowns = true;
        }
        var options = {
            selectionHandler: function () {
                // Prevent AUI from reimplementing default browser handling of <a> elements.
                // May need to revisit depending on AUI changes (see CR-FE-1617).
            },
            useLiveEvents : true //using live meant that hovers will no longer need to perform a dropdown bind on load
        };
        if (useroptions) {
            AJS.$.extend(options, useroptions);
        }
        AJS.dropDown.Standard(options);
    };

    var setupFilterInlineDialog = function(trigger) {
        var PADDING = 2;
        var $filterBox = AJS.$("#header-constraint");
        if ($filterBox.length > 0) {
            var options = {
                onHover: false,
                showArrow: true,
                fadeTime: 200,
                hideDelay: 200,
                showDelay: 200,
                width: $filterBox.width() + PADDING,
                offsetX: 0,
                offsetY: PADDING,
                container: "body",
                cacheContent:true,
                ignoredElements:['.ui-datepicker'] //dont want to hide the filter box when selecting dates
            };
            var contentHandler = function ($contentDiv, mouseOverTrigger, showPopup) {
                $filterBox.appendTo($contentDiv).show();
                showPopup();
            };
            AJS.InlineDialog(trigger, "header-constraint-inline-dialog", contentHandler, options);
        }
    };

    AJS.FECRU.UI.constraintToggle = function() {
        setupFilterInlineDialog(".constraint-toggle");
    };

    var linkAugment = function () {
        AJS.$("a[rel='help']").attr("target", "_help");

        // details switch in stream
        AJS.$(".details-switch").unbind().click(function () {
            AJS.$(this).toggleClass("details-expanded");
            AJS.$(this).parents(".details").children(".details-verbose").toggle();
            return false;
        });

        // look for all jira inline activity items, and load the content ajax, chained one by one.
        var $jiraActivities = AJS.$("div.details-body div.jira-issue-key");
        // cache the ajax results to prevent multiple ajax calls for the same key
        var cache = {};
        var ajaxLoadJiraActivity = function($target, next) {
            var issueKey = AJS.$("input.jira-issue-key", $target).attr("value");
            if (issueKey) {
                //                AJS.log("issueKey="+issueKey);
                var cacheHit = cache[issueKey];
                if (cacheHit) {
                    $target.html(cacheHit.resp.html);
                    if (next) {
                        next();
                    }
                } else {
                    // this doesn't use AJS.FECRU.ajaxDo because we don't want the standard error handling
                    AJS.$.ajax({
                        url: fishEyePageContext + '/json/action/issue-inline.do',
                        data: {'key':issueKey},
                        type: "GET",
                        dataType: "json",
                        success: function(resp) {
                            $target.html(resp.html);
                            if (next) {
                                next();
                            }
                            //cache using the issueKey - prevent multiple calls for the same keys
                            cache[issueKey] = {resp:resp};
                        },
                        error: function(resp) {
                            $target.html("Error retrieving issue: " + issueKey);
                        }
                    });
                }
            }
        };
        var nextInChain = function(i, max) {
            return function() {
                if (i < max) {
                    ajaxLoadJiraActivity(AJS.$($jiraActivities[i]), nextInChain(i + 1, max));
                }
            };
        };
        var max = $jiraActivities.length;
        var i = 0;
        if (max > 0) {
            ajaxLoadJiraActivity(AJS.$($jiraActivities[i]), nextInChain(i + 1, max));
        }

        // inline hover in stream
        AJS.$(".hover").mouseover(function (event) {
            AJS.$("#hover").addClass("mouseover");
            inlineHover(AJS.$(this), event);
        });

        AJS.$("#hover").mouseout(function() {
            var which = AJS.$(this);
            which.bind("mouseleave", function() {
                AJS.$(which).removeClass("mouseover");
                AJS.$(which).hide();
            });
        });
        AJS.$("hover-close").click(function() {
            AJS.$(this).hide();
        });
        // filter bar in stream
        //	AJS.$(".stream-sift a").click(function () {
        //		AJS.$(".stream-sift li").removeClass("active");
        //		AJS.$(this).parent().addClass("active");
        //TODO: could be useful at a later date?
        //	});

        contentToggle();
        starDialog();
    };

    var formAugment = function (which) {
        if (AJS.$.browser.safari) {//overides type=search
            var searchBox = AJS.$("#quick-search-input");
            searchBox[0].type = "text";
        }
    };

    var $focusedTableRow;
    AJS.FECRU.UI.tableRowClick = function (prefix, rowClickFn) {
        AJS.$("#" + prefix + "-table > tbody > tr").live("click", function () {
            var clearFocus = function() {
                if ($focusedTableRow) {
                    $focusedTableRow.removeClass(prefix + "-focus");
                }
            };

            var $this = AJS.$(this);
            if ($this.hasClass(prefix + "-focus")) {
                clearFocus();
                $focusedTableRow = null;
            } else {
                clearFocus();
                $this.addClass(prefix + "-focus");
                $focusedTableRow = $this;
                if (rowClickFn) {
                    rowClickFn(this);
                }
            }
        });
    };

    AJS.FECRU.UI.tableSort = function (prefix, extractionFn) {
        var params = {};
        if (extractionFn) {
            params['textExtraction'] = extractionFn;
        }
        AJS.$("#" + prefix + "-table").tablesorter(params);
    };

    AJS.FECRU.UI.accordion = function () {
        AJS.$('.sidebar-collapse').live("click", function() {
            AJS.$('#content').toggleClass('collapsed-sidebar');
            return false;
        });

        AJS.$(".accordion-head").live("click", function() {
            var $next = AJS.$(this).next();
            var $parent = AJS.$(this).parent();
            if ($next.is(":hidden")) {
                $parent.addClass("active");
                if (!AJS.$.browser.msie) {//fork required as IE doesn't play nicely with jQuery slideDown animation
                    $next.slideDown("fast");
                }
                else {
                    $next.show();
                }
            }
            else {
                if (!AJS.$.browser.msie) {
                    $next.slideUp("fast", function () {
                        $parent.removeClass("active");
                    });
                }
                else {
                    $next.hide();
                    $parent.removeClass("active");
                }
            }
            return false;
        });

        AJS.$("#accordion-toggle").live("click", function() {
            var $accordionToggle = AJS.$(this);
            var $accordionContent = AJS.$(".accordion-content");
            var $parent = AJS.$(this).parent();
            var isExpanded = $accordionToggle.html() === 'expand';
            if (isExpanded) {
                $parent.addClass("active");
                if (!AJS.$.browser.msie) {//fork required as IE doesn't play nicely with jQuery slideDown animation
                    $accordionContent.slideDown("fast");
                }
                else {
                    $accordionContent.show();
                }

            }
            else {
                 if (!AJS.$.browser.msie) {
                    $accordionContent.slideUp("fast", function () {
                        $parent.removeClass("active");
                    });
                }
                else {
                    $accordionContent.hide();
                    $parent.removeClass("active");
                }
            }
            $accordionToggle.html(isExpanded ? 'expand' : 'collapse'); // reverse (it's now "wasExpanded")
            return false;
        });
    };

    var contentToggle = function () {
        AJS.$("#content-toggle").live("click", function() {
            if (AJS.$("#content-toggle").html() == 'expand') {
                AJS.$(".details-verbose").show();
                AJS.$("#content-toggle").html("collapse").addClass("expanded");
            }
            else {
                AJS.$(".details-verbose").hide();
                AJS.$("#content-toggle").html("expand").removeClass("expanded");
            }
            return false;
        });

        AJS.$(".alwaysExpandChangesetsY").live("click", function() {
            AJS.$(".details-verbose").show();
            AJS.$("div.details-switch").addClass("details-expanded");
        });
        AJS.$(".alwaysExpandChangesetsN").live("click", function() {
            AJS.$(".details-verbose").hide();
            AJS.$("div.details-switch").removeClass("details-expanded");
        });
    };

    var inlineHover = function (which, event) {
        if (!AJS.$("#hover").hasClass("mouseover")) {
            return false;//don't fire if the link is no longer hovered
        }

        var source = which.attr("name").split("-")[0];
        var subject = which.attr("name").split("-")[1];
        var content;
        var offset = which.offset();
        var height = which.height();

        if (source === "user") {
            content = hovers.users[subject];
        }
        else if (source === "item") {
            content = hovers.items[subject];
        }

        AJS.$("#hover-content").html(content);
        AJS.$("#hover").css({
            display:"block",
            top: offset.top + height,
            left: offset.left
        });
    };

    var starDialog = function () {
        AJS.$(".close-astronomy").click(function() {
            AJS.$("#astronomy").hide();
            return false;
        });
        AJS.$("#star-labels").attr("autocomplete", "off");
        AJS.FECRU.UI.augmentFormFields("#star-labels");
    };

    AJS.FECRU.UI.augmentFormFields = function (which) {
        AJS.$(which).focus(function() {
            if (AJS.$(which).attr("value") == "Describe your favourite" ||
                AJS.$(which).attr("value") == "scroll to changeset" ||
                AJS.$(which).attr("value") == "Search") {
                AJS.$(which).attr("value", "").addClass("generated");
            }
        });

        AJS.$(which).blur(function() {
            if (AJS.$(which).attr("value").match(/^\s*$/)) { //use the default if only have empty string or space
                AJS.$(which).attr("value", AJS.$(which).attr("defaultValue")).removeClass("generated");
            }
        });
    };

    AJS.FECRU.UI.changesetToggle = function (postOpenFn) {
        return function() {
            AJS.$(".stream-delta dt.hasDiff").click(function () {
                var $node = AJS.$(this);
                $node.next().toggle();
                $node.toggleClass("open");
                if (postOpenFn) {
                    postOpenFn(AJS.$(this));
                }
            });
            AJS.$(".stream-delta dt *").click(function (e) {
                e.stopPropagation();
            });
        };
    };
    AJS.FECRU.UI.changesetToggleAll = function () {
        AJS.$("a#expand-all").click(function() {
            AJS.$(".stream-delta dd").show();
            AJS.$(".delta-toggle").html("collapse all").addClass("expanded");
            AJS.$(".stream-delta dt.hasDiff").addClass("open");
        });
        AJS.$("a#collapse-all").click(function() {
            AJS.$(".stream-delta dd").hide();
            AJS.$(".delta-toggle").html("expand all").removeClass("expanded");
            AJS.$(".stream-delta dt.hasDiff").removeClass("open");
        });
    };

    AJS.FECRU.UI.initStream = function () {
        linkAugment();
        formAugment();
    };

    AJS.FECRU.UI.contentPadBottom = function () {
        var contentPadding = AJS.$("#content").css("padding-bottom");
        var messageHeight = AJS.$("#footer-bar .system-message").height();
        var messagePadding = parseInt(contentPadding, 10) + messageHeight;
        AJS.$("#content").css("padding-bottom", messagePadding);
    };

    /**
     * checks if the dates in the elements ".calendar-date-end" and ".calendar-date-start" are in the correct order
     * and swap them around if they are found to be reversed.
     *
     * @param extractDateStringFn a function to extract the date string into the format 'yy-mm-dd' from the format
     * obtained in the value attribute of the input element
     */
    AJS.FECRU.UI.swapDatesIfReversed = function(extractDateStringFn, context) {
        context = context || "body";
        var endDateInput = AJS.$("input.calendar-date-end", context);
        var startDateInput = AJS.$("input.calendar-date-start", context);
        if (endDateInput.length > 0 && startDateInput.length > 0) {
            var startDateStr = startDateInput.val();
            var endDateStr = endDateInput.val();
            var startDate = AJS.$.datepicker.parseDate('yy-mm-dd', extractDateStringFn(startDateStr));
            var endDate = AJS.$.datepicker.parseDate('yy-mm-dd', extractDateStringFn(endDateStr));

            if (startDateStr && endDateStr && (startDate > endDate)) {
                //do a swap of the date values before submitting if the dates are found to be reversed
                endDateInput.val(startDateStr);
                startDateInput.val(endDateStr);
            }
        }
    };

    AJS.FECRU.UI.setupCalendar = function(addTime) {
        addTime = (typeof addTime == 'undefined') ? true : addTime;
        var calDateStart = AJS.$("input.calendar-date-start");
        calDateStart.attr("autocomplete", "off");
        calDateStart.datepicker({
            dateFormat: 'yy-mm-dd',
            onClose: function(dateText) {
                //only do this if the text is the length of a date
                //this will FAIL if anyone changes the date format

                //NOTE: yy-mm-dd in jquery is a 10 character format
                // ie 1987-12-23
                if (addTime && dateText.length === 10) {
                    AJS.$(this).attr("value", dateText + "T00:00:00");
                }
            }
        });
        var calDateEnd = AJS.$("input.calendar-date-end");
        calDateEnd.attr("autocomplete", "off");
        calDateEnd.datepicker({
            dateFormat: 'yy-mm-dd',
            onClose: function(dateText) {
                if (addTime && dateText.length === 10) {
                    AJS.$(this).attr("value", dateText + "T23:59:59");
                }
            }
        });
    };

    AJS.FECRU.UI.warnAboutFirebug = function (onClose) {
        var cookieName = 'hide_fecru_fb_warn';
        var suppressWarning = AJS.$.cookie(cookieName) === 'Y';
        if (!suppressWarning && window.console && window.console.firebug) {
            var product = AJS.$("#product-name").text() || "FishEye + Crucible";
            var $warning = AJS.$("<div id='firebug-warning'><p>Firebug is known to cause performance problems with " +
                                 product + ". Why not disable it?</p><a class='close'>X</a></div>");
            AJS.$("#firebug-warning .close").live("click", function () {
                $warning.slideUp('fast', function() {
                    AJS.$.cookie(cookieName, 'Y', { expires: 365 });
                    if (onClose) {
                        onClose();
                    }
                });
            });
            $warning.prependTo(AJS.$("#cone"));
        }
    };

    AJS.FECRU.UI.toggleSearch = function () {
        AJS.$("h5:[rel='toggle']").live("click", function() {
            if (AJS.$(this).hasClass("show")) {
                AJS.$("#search-more").show();
                AJS.$(this).removeClass("show").addClass("hide");
                AJS.$(this).find("em").text("hide");
            }
            else {
                AJS.$("#search-more").hide();
                AJS.$(this).addClass("show").removeClass("hide");
                AJS.$(this).find("em").text("show");
            }
            return false;
        });
    };

})();
;
/* END /2static/script/fecru/ui.js */
/* START /2static/script/fecru/onReady.js */
//init
AJS.toInit(function(){

    var fecru = AJS.FECRU;
    var ui = fecru.UI;

    ui.initStream();

    ui.setDropdowns();

    ui.augmentFormFields("#jumptoid");

    fecru.HOVER.addAllLinkPopups();

    fecru.RSS.setupRSSDialog();

    ui.accordion();
});;
/* END /2static/script/fecru/onReady.js */
/* START /2static/script/fecru/star.js */
if (!AJS.FECRU) {
    AJS.FECRU = {};
}

(function() {

    /**
     * html for the star edit dialog, to be inserted on demand.
     */
    var DIALOG_HTML = "<div id='astronomy'>" +
                          "<div id='astronomy-scape'>&nbsp;</div>" +
                          "<div id='astronomy-label'>" +
                              "<h4><span>Update favourite</span></h4>" +
                              "<span class='close'><a href='#' class='close-astronomy' id='close-astronomy'>X</a></span>" +
                              "<form action='#'>" +
                                  "<fieldset class='input'>" +
                                      "<label for='star-labels'>Name</label>" +
                                      "<input type='text' id='star-labels' value='Describe your favourite'>" +
                                  "</fieldset>" +
                                  "<fieldset class='button'>" +
                                      "<ul>" +
                                          "<li><button class='remove' id='remove-astronomy'>Remove</button></li>" +
                                          "<li class='odd'><input type='submit' id='save-star-label' value='Save label'></li>" +
                                      "</ul>" +
                                  "</fieldset>" +
                              "</form>" +
                          "</div>" +
                      "</div>";

    ////// private functions

    var ON_ONLY = true;
    var OFF_ONLY = false;

    function starOnOffClassName(on) {
        return on ? "star-on" : "star-off";
    }

    function starAddRemoveText($star, on) {
        return $star.find(on ? "input.star-textRemove" : "input.star-textAdd").val();
    }

    /**
     * todo: see if this can be swapped with the throbber jquery plugin.
     */
    function makeThrobberControl($link, throbberSetting) {
        /**
         * Starts the throbber, which activates after noLatencyThreshold milliseconds have passed.
         * @return a function that will stop the throbber after minThrobberDisplay milliseconds have passed, ensuring no flicker.
         */
        var startThrob = function () {
            $link.data("throbbing", true);
            var timeout = setTimeout(function () {
                $link.addClass("star-throb");
            }, throbberSetting.noLatencyThreshold);

            return function() {
                $link.data("throbbing", false);
                clearTimeout(timeout);
                setTimeout(function () {
                    $link.removeClass("star-throb");
                }, throbberSetting.minThrobberDisplay);
            };
        };

        /**
         * stores a function to stop the throbber for the given $link
         */
        var stopThrob = undefined;
        return {
            start: function() {
                if (throbberSetting.showThrob && !stopThrob) {
                    stopThrob = startThrob();
                }
            },
            stop: function() {
                if (stopThrob) {
                    stopThrob();
                    stopThrob = undefined;
                }
            },
            isThrobbing: function() {
                return $link.data("throbbing");
            }
        };
    }

    /**
     *
     * @param link the jquery element of the link that was clicked
     * @param dialogControl an object to control the dialog. Its members should be:
     * - showDialog: a function with no arguments, which when invoked will display the dialog
     * - hideDialog: a function with no arguments, which when invoked will hide the dialog
     * - getDialog: a function with no argument, which returns a jquery object that represents the dialog
     * @param options the list of options. Available options are:
     * - throbber : options for throbbing
     *
     */
    function starClicked(link, dialogControl, options) {
        var starId = getStarId(link);
        var starKeys = {};
        var $link = AJS.$(link);
        var throbberControl = makeThrobberControl($link, options.throbberSetting);
        //if throbbing already, then don't do anything.
        if (throbberControl.isThrobbing()) {
            return;
        }

        $link.children("span.inputs").children("input.starKey").each(function() {
            var key = AJS.$(this);
            starKeys[key.attr("name")] = key.val();
        });
        if (starId != null) {
            if (isDialogShown($link)) {
                // we are unstarring, pop up the edit box
                editStar(starId, dialogControl, throbberControl);
            } else {
                //...or remove directly without anymore interaction
                doRemoveStar(starId, throbberControl);
            }
        } else {
            // we are adding a star
            addStar(starKeys, dialogControl, throbberControl);
        }

        // todo: fix this hack to make hoverpopups refresh after starring
        var isUserStar = starKeys["itemType"] === 'atlassian-user';
        var isCommitterStar = starKeys["itemType"] === 'atlassian-committer';
        if (isUserStar || isCommitterStar) {
            //todo: problem is that some committer names displays user hovers, and so the trigger for the hover
            //todo: is no longer available for use here, so cannot work out what the cache key is from the star.
            //todo: refactor this into a better caching mechanism, may be use the DOM to cache instead.
           AJS.FECRU.HOVER.invalidateCache('userlinks');
        }

    }

    function updateAccordian() {
        var $starList = AJS.$("#accordionStarList");
        if ($starList.length > 0) {
            AJS.FECRU.AJAX.ajaxDo(fishEyePageContext + "/json/profile/starAccordionAjaxBody.do", {},
                    function(resp) {
                        if (resp.worked) {
                            $starList.replaceWith(resp.html);
                        }
                    },
                    false
                    );
        }
        var $numStars = AJS.$("#accordionNumStars");
        if ($numStars.length > 0) {
            AJS.FECRU.AJAX.ajaxDo(fishEyePageContext + "/json/profile/starAccordionAjaxNumber.do", {},
                    function(resp) {
                        if (resp.worked) {
                            $numStars.replaceWith(resp.html);
                        }
                    },
                    false
                    );
        }
    }

    function getIdElement(link) {
        return AJS.$(link).children("span.inputs").children("input[name='id']");
    }

    function getStarId(link) {
        return getIdElement(link).val();
    }

    function allStars(on) {
        return AJS.$("a." + starOnOffClassName(on));
    }

    function setStarAttributes(on, $node) {
        var newClass = starOnOffClassName(on);
        var oldClass = starOnOffClassName(!on);
        $node.removeClass(oldClass)
             .addClass(newClass)
             .children("span.starText").text(starAddRemoveText($node,on));
    }

    /**
     * Turn on the Star with the given keys
     * @param keys
     */
    function addStar(keys, dialogControl, throbberControl) {
        var paramMap = {};
        for (var key in keys) {
            paramMap["key." + key] = keys[key];
        }
        throbberControl.start();
        doStar(fishEyePageContext + "/json/profile/addStarAjax.do", paramMap, function(newId) {
            allStars(OFF_ONLY).each(function() {
                var $node = AJS.$(this);
                var $inputs = $node.children("span.inputs");
                for (var key in keys) {
                    var input = $inputs.children("input.starKey[name='" + key + "']");
                    if (input.length === 0 || input.val() != keys[key]) {
                        return;
                    }
                }
                setStarAttributes(true, $node);
                $inputs.append("<input type='hidden' name='id' value='" + newId + "'>");
            });
            // AJS.log("id = " + newId);
            if (keys["itemType"] == "atlassian-chart" || keys["itemType"] == "atlassian-search" ||
                keys["itemType"] == "atlassian-quicksearch") {
                editStar(newId, dialogControl, throbberControl);
            }
            throbberControl.stop();
        });
    }

    /**
     * Pop up a dialog box for a Star which is in the on state, allowing the user to set its label or to remove it.
     * @param starId the id of the Star to change.
     * @param dialogControl
     */
    function editStar(starId, dialogControl, throbberControl) {
        // pop up our star edit dialog
        var onSuccess = function(resp) {
            if (resp.worked) {
                var __updateLabel = function(label) {
                    doSaveLabel(starId, label, throbberControl);
                };
                var __removeStar = function() {
                    doRemoveStar(starId, throbberControl);
                };
                showStarDialog(dialogControl, __updateLabel, __removeStar, resp.payload);
            } else {
                //resp.worked is false, reset the star to an off star.
                turnOffStar(starId);
            }
            throbberControl.stop();
        };
        throbberControl.start();
        // get the label for this Star, to supply it to the dialog
        AJS.$.post(fishEyePageContext + "/json/profile/getStarLabelAjax.do", {id: starId}, onSuccess, 'json');
    }

    function turnOffStar(starId) {
        allStars(ON_ONLY).each(function() {
            var id = getStarId(this);
            if (starId == id) {
                // represents the same star
                getIdElement(this).remove();
                var $node = AJS.$(this);
                setStarAttributes(false, $node);
            }
        });
    }

    /**
     * Send a new label value to the server.
     * @param id the id of the Star who's label has been changed
     * @param label a String holding the new label text
     */
    function doSaveLabel(id, label, throbberControl) {
        var onDone = function() {
            refreshDropDown();
            throbberControl.stop();
        };
        throbberControl.start();
        AJS.$.post(fishEyePageContext + "/json/profile/setStarLabelAjax.do", {id:id, label:label}, onDone, 'json');
    }

    /**
     * Unstar the star with the given id.
     *
     * @param id the id of the Star to unstar.
     */
    function doRemoveStar(id, throbberControl) {
        throbberControl.start();
        doStar(fishEyePageContext + "/json/profile/removeStarAjax.do", {id: id}, function() {
            turnOffStar(id);
            throbberControl.stop();
        });
    }

    /**
     * Change the state of a set of stars, and refresh the 'my stars' dropdown.
     * @param url the URL to inform the server about the change
     * @param updateStars the function to call to change the state of the stars in the browser
     */
    function doStar(url, params, updateStars) {
        var done = function(resp) {
            if (resp.worked) {
                updateStars(resp.id);
                refreshDropDown();
                updateAccordian();
                return true;
            }
        };
        AJS.FECRU.AJAX.ajaxDo(url, params, done, false);
    }

    function refreshDropDown() {
        var $dropDown = AJS.$("#starDropDownList");
        if ($dropDown.length > 0) {
            var done = function(resp) {
                if (resp.worked) {
                    $dropDown.replaceWith(resp.html);
                }
            };
            AJS.FECRU.AJAX.ajaxDo(fishEyePageContext + "/json/profile/starDropDownAjax.do", {}, done, false);
        }
    }

    //todo: use AUI inline dialog
    function createStarDialog(dialogControl) {
        var $dialog = AJS.$(DIALOG_HTML);
        //intialize the dialog buttons
        AJS.$("#close-astronomy", $dialog).click(function() {
            dialogControl.hideDialog();
            return false;
        });
        //make the input dialog change style when user start inputting...
        //...and reset to the default if user did not input anything.
        var $starLabel = AJS.$("#star-labels", $dialog);
        $starLabel.focus(function() {
            if ($starLabel.attr("value") === $starLabel.attr("defaultValue")) {
                $starLabel.attr("value", "").addClass("generated");
            }
        }).blur(function() {
            //use the default if input is empty string or space
            if ($starLabel.attr("value").match(/^\s*$/)) {
                $starLabel.attr("value", $starLabel.attr("defaultValue")).removeClass("generated");
            }
        }).keypress(function(evnt) {
            //if user presses enter
            if (evnt.which == 13) {
                AJS.$("#save-star-label", $dialog).trigger('click');
                evnt.preventDefault();
            }
        }).attr("autocomplete", "off");

        return $dialog;
    }

    function showStarDialog(dialogController, updateLabel, removeStar, currentLabel) {
        var labels = currentLabel;
        var $dialog = dialogController.getDialog();
        var $starLabel = AJS.$("#star-labels", $dialog);
        var LABEL_TIP = $starLabel.attr("defaultValue");

        labels = (labels !== "") ? labels : LABEL_TIP;

        $starLabel.attr("value", labels);
        if ($starLabel.attr("value") !== LABEL_TIP) {
            $starLabel.addClass("generated");
        }
        else {
            $starLabel.removeClass("generated");
        }
        AJS.$("#remove-astronomy", $dialog).unbind().click(function(evnt) {
            evnt.preventDefault();
            removeStar();
            dialogController.hideDialog();
        });

        AJS.$("#save-star-label", $dialog).unbind().click(function(evnt) {
            evnt.preventDefault();
            var newLabel = $starLabel.val();
            if (newLabel !== LABEL_TIP) {
                updateLabel(newLabel);
            }
            dialogController.hideDialog();
        });
        dialogController.showDialog();
    }

    function isDialogShown(starLink) {
        return starLink.hasClass("showDialog");
    }

    /**
     * bind a live click event to the stars.
     */
    function bindStars() {
        var $dialog = undefined; //stores a reference to the jquery elmem
        var dialogHider = {};
        var PADDING = 5;
        var hoverOptions = {
            onHover: false,
            showArrow: true,
            fadeTime: 200,
            hideDelay: 200,
            showDelay: 0,
            width: 240 + PADDING,
            offsetX: -8, //to get the arrow centred at the star's vertical asix
            offsetY: 0,
            container: "body",
            useLiveEvents: true,
            cacheContent:false,
            initCallback: function() {
                //implementation note: we need a way to hide the dialog on demand - this callback has a hook into
                //the hide function that inline-dialog uses internally, so this is saving a reference to that function
                //for use later.
                var that = this;
                dialogHider.hideDialog = function() {
                    that.hide();
                };
            }
        };
        var onStarClickedHandler = function ($contentDiv, trigger, showPopup) {
            var $link = AJS.$(trigger);
            var dialogControl = {
                showDialog: function() {
                    if (isDialogShown($link)) {
                        if ($dialog === undefined) {
                            $dialog = createStarDialog(dialogHider);
                        }
                        $dialog.appendTo($contentDiv).show();
                        showPopup();
                    }
                },
                hideDialog: function() {
                    dialogHider.hideDialog();
                },
                getDialog: function () {
                    if ($dialog === undefined) {
                        $dialog = createStarDialog(dialogHider);
                    }
                    return $dialog;
                }
            };
            var opts = {
                throbberSetting: {
                    showThrob: true,         //todo: customizable throbbing needed?
                    noLatencyThreshold: 150, //if the ajax call takes less than this milliseconds, then no throb shown
                    minThrobberDisplay: 200  //otherwise, show for at least this milliseconds.
                }
            };
            starClicked(trigger, dialogControl, opts);
        };
        AJS.InlineDialog(".starrable", "star-inline-dialog", onStarClickedHandler, hoverOptions);
    }

    ////// onload events for stars
    AJS.toInit(function() {
        bindStars();
    });
})();
;
/* END /2static/script/fecru/star.js */
/* START /2static/script/fecru/prefs.js */
if (!AJS.FECRU) {
    AJS.FECRU = {};
    AJS.FECRU.PREFS = {};
} else if (!AJS.FECRU.PREFS) {
    AJS.FECRU.PREFS = {};
}


AJS.FECRU.PREFS.setPreference = function(name, value, callback) {
    var params = {};
    params[name] = value;
    AJS.FECRU.PREFS.setPreferences(params, callback);
};

AJS.FECRU.PREFS.setPreferences = function(prefs, callback) {
    var params = {};
    for (i in prefs) {
        params["@" + i] = prefs[i];
    }
    AJS.FECRU.AJAX.ajaxDo(fishEyePageContext + "/json/fe/setPreference.do", params, callback, false);
};

AJS.FECRU.PREFS.setupBinaryPrefLinks = function(name, cookieName, initialState, fns, onStateString, offStateString, forceInitialCall, reload) {
    var onSelector = "." + name + onStateString;
    var offSelector = "." + name + offStateString;

    function setPref(value, hideSelector, showSelector, callback) {
        AJS.FECRU.PREFS.setPreference(cookieName, value, callback);
        setVisibility(value, hideSelector, showSelector, true);
    }

    function setVisibility(value, hideSelector, showSelector, callfunction) {
        AJS.$(hideSelector).hide();
        AJS.$(showSelector).css("display","block");
        if(callfunction || forceInitialCall) {
           fns[value].call();
        }
    }

    function reloadPage() {
        if(reload) {
           window.location = window.location;
        }
    }

    AJS.$(onSelector).live("click", function() {
        setPref(onStateString, onSelector, offSelector, reloadPage);
        return false;
    });
    AJS.$(offSelector).live("click", function() {
        setPref(offStateString, offSelector, onSelector, reloadPage);
        return false;
    });
    if (initialState) {
        setVisibility(onStateString, onSelector, offSelector, false);
    } else {
        setVisibility(offStateString, offSelector, onSelector, false);
    }
};
;
/* END /2static/script/fecru/prefs.js */
/* START /script/common/global.js */
//crucible global.js
var $dropdown	= 0;
var dropdownTimer = 0;

function openDropDown(id) {
    clearTimeout(dropdownTimer);

    // close old menu
    if( $dropdown && $dropdown.attr("id") != id) {
        $dropdown.fadeOut(100);
    }

    // get new menu and show it
    $dropdown = AJS.$("#"+id).show();
}

function closeDropDown() {
    if ($dropdown && $dropdown.length === 1) {
        dropdownTimer = setTimeout(function(){
            $dropdown.fadeOut(100);
        }, 200);
    }
}

function simpleSwap(toHide, toShow) {
    if (typeof(toHide) == 'object') {
        AJS.$(toHide).hide();
    } else {
        AJS.$("#"+toHide).hide();
    }
    if (typeof(toShow) == 'object') {
        AJS.$(toShow).show();
    } else {
        AJS.$("#"+toShow).show();
    }
}
function expandAll(ids, prefix) {
    toggleAll(ids, true, false, prefix);
}
function collapseAll(ids, prefix) {
    toggleAll(ids, false, true, prefix);
}
function expandSelected(allIds, selectedIds, prefix) {
    //first collapse all
    toggleAll(allIds, false, true, prefix);
    toggleAll(selectedIds, true, false, prefix);
}
function toggleType(ids, prefix) {
    toggleAll(ids, false, false, prefix);
}
function toggleBasic(nodeName) {
    toggleNodeAndImage(nodeName, false, false);
}
function collapseBasic(nodeName) {
    toggleNodeAndImage(nodeName, false, true);
}
function expandBasic(nodeName) {
    toggleNodeAndImage(nodeName, true, false);
}
function toggleAll(ids, forceOpen, forceClose, prefix) {
    prefix = prefix || '';
    for (var i = 0; i < ids.length; i++) {
        var theNode = prefix + ids[i];
        toggleNodeAndImage(theNode, forceOpen, forceClose);
    }
    return false;
}

/**
 * jQuery uses [,] and : as special characters for selectors. These characters are still valid in HTML ids.
 * Use this method to return a sanitized version of the id for use in jQuery selectors.
 * @param id id to sanitize
 */
function sanitizeId(id) {
    return id.replace(/:/g,"\\:").replace(/\./g,"\\.");
}

function toggleNodeAndImage(nodeName, forceOpen, forceClose) {
    if (!nodeName) {
        return;
    }
    nodeName = sanitizeId(nodeName);
    var $node = AJS.$("#"+nodeName);
    if ($node.length === 0) {
        return;
    }

    var img = AJS.$("#"+nodeName+'img');
    if (img.length === 1) {
        var swapImage = true;
    }

    // Don't use is(":hidden") here, because we need to force things closed even if their parents are closed
    var shouldOpen = $node.css("display") === 'none';
    shouldOpen = (!forceClose) && (forceOpen || shouldOpen);
    if (shouldOpen) {
        $node.show();
        if (swapImage) {
            img.attr("src", fishEyePageContext + '/' + fishEyeSTATICDIR + '/images/arrow_open.gif' );
        }
    } else {
        $node.hide();
        if (swapImage) {
            img.attr("src", fishEyePageContext + '/' + fishEyeSTATICDIR + '/images/arrow_closed.gif' );
        }
    }
}

var currentTab;
var currentBucket;
// rewrote so we don't set styles we don't have to.
function selectTab(tab) {
    if (currentTab) {
        currentTab.removeClass();
        currentBucket.hide();
    }
    currentTab = AJS.$("#" + tab + 'Tab')
            .attr('class','active');

    currentBucket = AJS.$("#" + tab + 'Bucket')
            .show();
}

function clearField(obj, defaultValue) {
    if (obj.value == defaultValue) {
        obj.value = '';
    }
}

function rollover(obj) {
    if (obj.tagName == 'IMG') {
        var imgsrc = obj.src.replace(/\.gif$/, '');
        obj.src = imgsrc + '_over.gif';
    }
    return false;
}

function rollout(obj) {
    if (obj.tagName == 'IMG') {
        obj.src = obj.src.replace(/\_over\.gif$/, '.gif');
    }
    return false;
}

function toggleOverflow(handle, element) {
    var $el = AJS.$(element);
    var $hd = AJS.$(handle);
    if ($el.css("overflow") == 'hidden' || $el.css("overflow") == '') {
        $el.css({'overflow':'visible', 'height':'auto'});
        $hd.attr("src", $hd.attr("src").replace(/expand\.gif$/, 'collapse.gif') );
    } else {
        $el.css({'overflow':'hidden', 'height':'1.3em'});
        $hd.attr( "src", $hd.attr("src").replace(/collapse\.gif$/, 'expand.gif') );
    }
}


function submitDefaultForm(command) {
    document.defaultForm.command.value = command;
    document.defaultForm.submit();
}

function showDiffForm(frxid) {

    simpleSwap('updateFrxFromFormToggle' + frxid, 'updateFrxFromFormSpan' + frxid);
    toggleNodeAndImage('frxinner' + frxid, true, false);

    var url = AJS.CRU.UTIL.jsonUrlBase(permaId) + "/changeDiffDropdownAjax/";
    var params = {'frxId' : frxid};
    var spanName = "updateFrxFromFormSpan" + frxid;

    AJS.FECRU.AJAX.startSpin(spanName, false, "span", "");

    AJS.FECRU.AJAX.ajaxDo(url, params, function(resp) {
        var $span = AJS.$("#" + spanName);
        if ($span.length > 0) {
            AJS.FECRU.AJAX.stopSpin($span[0]);
        }
        if (resp.worked) {
            AJS.$("#" + spanName).html(resp.msgHtml);
            if (resp.showDeleteRevisions) {
                AJS.$(".deleteRev-hidden").removeClass("deleteRev-hidden").addClass("deleteRev-shown");
            }
        }
    });
}

function toggleWording(handle) {
    var $handleEl = AJS.$(handle);
    var handleText = $handleEl.html().substring(0, 4);
    switch (handleText) {
        case 'Show':
            $handleEl.html($handleEl.html().replace(/^Show/, 'Hide'));
            return true;
        case 'Hide':
            $handleEl.html($handleEl.html().replace(/^Hide/, 'Show'));
            return true;
        case 'Expa':
            $handleEl.html($handleEl.html().replace(/^Expand/, 'Collapse'));
            return true;
        case 'Coll':
            $handleEl.html($handleEl.html().replace(/^Collapse/, 'Expand'));
            return true;
        case 'More':
            $handleEl.html($handleEl.html().replace(/^More/, 'Less'));
            return true;
        case 'Less':
            $handleEl.html($handleEl.html().replace(/^Less/, 'More'));
            return true;
    }
    return false;
}

function show(toShow, bool) {
    AJS.$(toShow).toggle(bool);
}

function submitCruSearch(inputEl) {
    window.location = fishEyePageContext + "/cru/search?query=" + encodeURIComponent(inputEl.value);
}

function searchReviews() {
    var searchVal = document.forms.searchForm["search.text"].value;
    if (searchVal) {
        window.location = fishEyePageContext + "/cru/search?query=" + encodeURIComponent(searchVal);
    } else {
        window.location = fishEyePageContext + "/cru/search";
    }
}
function searchComments() {
    var searchVal = document.forms.searchForm["query"].value;
    if (searchVal) {
        window.location = fishEyePageContext + "/cru/commentSearch?search.text=" + encodeURIComponent(searchVal);
    } else {
        window.location = fishEyePageContext + "/cru/commentSearch";
    }
}

var ovAnk = false; //tracks whether the mouse is over a "real" anchor
function toggleSensitively(id) {
    if (!ovAnk) {
        toggleBasic(id);
    }
}
;
/* END /script/common/global.js */
/* START /2static/script/lib/ajs/contentnamesearch.js */
if(!(/jwebunit/).test(navigator.userAgent.toLowerCase())){
    AJS.FECRU.UI.setupQuicksearch = function () {
            AJS.$("#quick-search-form").submit(function () {
                return AJS.$.trim(AJS.$("#quick-search-input").val()).length > 0;
            });
            AJS.FECRU.UI.augmentFormFields("#quick-search-input");

            var attr = {
                cache_size: 30,
                max_length: 1,
                effect: "appear" // TODO: add "fade" effect
            };
            var dd,
                cache = {},
                cache_stack = [],
                timer;

            if (typeof path == "function") {
	            var getPath = path;
            } else {
            	var getPath = function () {
	                return path;
	            };
            }

        function regexEscape(text) {
            if (!arguments.callee.sRE) {
                var specials = [
                    '/', '.', '*', '+', '?', '|',
                    '(', ')', '[', ']', '{', '}', '\\'
                    ];
                arguments.callee.sRE = new RegExp(
                    '(\\' + specials.join('|\\') + ')', 'g'
                    );
            }
            return text.replace(arguments.callee.sRE, '\\$1');
        }

        var hider = function (list) {
             AJS.$("a span", list).each(function () {
                var $a = AJS.$(this),
                    elpss = AJS("var", "&#8230;"),
                    elwidth = elpss[0].offsetWidth,
                    width = this.parentNode.parentNode.parentNode.parentNode.offsetWidth,
                    isLong = false,
                    rightPadding = 20; // add some padding so the ellipsis doesn't run over the edge of the box

                    // get the hidden space name property from the span
                    var spaceName = AJS.dropDown.getAdditionalPropertyValue($a, "spaceName");
                    if (spaceName != null) {
                        spaceName = "(" + spaceName + ") ";
                    } else {
                        spaceName = "";
                    }
                    AJS.dropDown.removeAllAdditionalProperties($a);

                this.realhtml = this.realhtml || $a.html();
                $a.html("<em>" + this.realhtml + "</em>");
                $a.append(elpss);
                $a.attr("title", spaceName + this.realhtml.replace(/<\/?strong>/gi, ""));
                this.elpss = elpss;

                AJS.$("em", $a).each(function () {
                    var $label = AJS.$(this);

                    $label.show();
                    if (this.offsetLeft + this.offsetWidth + elwidth > width - rightPadding) {

                        var childNodes = this.childNodes;
                        var success = false;

                        for (var j = childNodes.length - 1; j >= 0; j--) {
                            var childNode = childNodes[j];
                            var truncatedChars = 1;

                            var valueAttr = (childNode.nodeType == 3) ? "nodeValue" : "innerHTML";
                            var nodeText = childNode[valueAttr];

                            do {
                                if (truncatedChars <= nodeText.length) {
                                    childNode[valueAttr] = nodeText.substr(0, nodeText.length - truncatedChars++);
                                } else { // if we cannot fit even one character of the next word, then try truncating the node just previous to this
                                    break;
                                }
                            } while (this.offsetLeft + this.offsetWidth + elwidth > width - rightPadding);

                            if (truncatedChars <= nodeText.length) {
                                // we've managed truncate part of the word and fit it in
                                success = true;
                                break;
                            }
                        }

                        if (success) {
                            isLong = true;
                        } else {
                            $label.hide();
                        }
                    }
                });
                elpss[isLong ? "show" : "hide"]();
            });
        };

        var searchBox = AJS.$("#quick-search-input");
        if (AJS.$.browser.safari) {
        //    searchBox[0].type = "search";
            searchBox[0].setAttribute("results", 10);
            searchBox[0].setAttribute("placeholder", "search");
        }
        var jsonparser = function (json, resultStatus) {
            var hasErrors = json.statusMessage ? true : false; // right now, we are overloading the existance of a status message to imply an error
            var matches = hasErrors ? [[{html: json.statusMessage, className: "error"}]] : json.contentNameMatches;

            if (!hasErrors) {
                var query = json.query;
                if (!cache[query] && resultStatus == "success") {
                    cache[query] = json;
                    cache_stack.push(query);
                    if (cache_stack.length > attr.cache_size) {
                        delete cache[cache_stack.shift()];
                    }
                }
            }

            // do not update drop down for earlier requests. We are only interested in displaying the results for the most current search
            if (json.query != searchBox.val() && json.query.charAt(json.query.length-1) != "*") {
                return;
            }

            var old_dd = dd;
            dd = AJS.dropDown(matches, {selectionHandler: function (e, selected) {
                if (selected) {
                    if (selected.get(0).nodeName.toLowerCase() !== "a") {
                        var enclosingLinkHref = selected.find("a").attr("href");
                        if (enclosingLinkHref) {
                            window.location = enclosingLinkHref;
                        }
                    } else {
                        var selectedHref = selected.attr("href");
                        if (selectedHref) {
                            window.location = selectedHref;
                        }
                    }
                }
            }})[0];
            dd.$.attr("id", "quick-nav-drop-down");
            dd.$.insertAfter(searchBox);

            dd.onhide = function (causer) {
                if (causer == "escape") {
                    searchBox.focus();
                }
            };
            var spans = AJS.$("span", dd.$);
            for (var i = 0, ii = spans.length - 1; i < ii; i++) {
                (function () {
                    var $this = AJS.$(this),
                    html = $this.html();
                    // highlight matching tokens
                    var match = "(";
                    for (var j = 0, jj = json.queryTokens.length - 1; j <= jj; j++) {
                        match += regexEscape(json.queryTokens[j]);
                        if (j < jj) {
                            match += "|";
                        }
                    }
                    match += ")";
                    html = html.replace(new RegExp(match, "gi"), "<strong>$1</strong>");
                    $this.html(html);
                }).call(spans[i]);
            }
            hider(dd.$);
            if(typeof onShow == "function") {
                onShow.apply(dd);
            }
            dd.hider = function () {
                hider(dd.$);
            };
            AJS.onTextResize(dd.hider);
            if (old_dd) {
                dd.show();
                dd.method = attr.effect;
                AJS.unbindTextResize(old_dd.hider);
                old_dd.$.remove();
            } else {
                dd.show(attr.effect);
            }
            AJS.$("#busyQnav").hide();
        };
        searchBox.oldval = searchBox.val();
        searchBox.keyup(function () {
            var val = searchBox.val();

            if (val != searchBox.oldval) {
                searchBox.oldval = val;
                if (!searchBox.hasClass("placeholded")) {
                    clearTimeout(timer);

                    if ((/[\S]{2,}/).test(val)) {
                            if (cache[val]) {
                                jsonparser(cache[val]);
                            } else {
                                timer = setTimeout(function () { // delay sending a request to give the user a chance to finish typing their search term(s)'
                                    AJS.$("#busyQnav").show();
                                    return AJS.$.ajax({
                                        type: "GET",
                                        url: quicksearchURL + "?q=" + AJS.escape(val) + "&type=ajax&searchAllDirs=false",
                                        data: null,
                                        success: jsonparser,
                                        dataType: "json",
                                        timeout: 20000,
                                        error: function ( xml, status, e ) { // ajax error handler
                                            if (status == "timeout") {
                                                jsonparser({statusMessage: "Timeout", query: val}, status);
                                            }
                                        }
                                    });

                                }, 600);
                            }
                    } else {
                        dd && dd.hide();
                    }
                }
        };
    });
};
}
;
/* END /2static/script/lib/ajs/contentnamesearch.js */
/* START /2static/script/lib/ajs/master.js */
// ============================
// = Search field placeholder =
// ============================
AJS.toInit(function ($) {
    var $search = $("#quick-search-query");
    if (!$search.length) {
        return;
    }

    var search = $search.get(0);
    search.placeholder = AJS.params.quickSearchPlaceholder;
    search.placeholded = true;
    search.value = search.placeholder;

    if (!$.browser.safari) {

        $(search).addClass("placeholded");

        $("#quick-search-query").focus(function () {
            if (this.placeholded) {
                this.placeholded = false;
                this.value = "";
                $(this).removeClass("placeholded");
            }
        });

        $("#quick-search-query").blur(function () {
            if (this.placeholder && (/^\s*$/).test(this.value)) {
                this.value = this.placeholder;
                this.placeholded = true;
                $(this).addClass("placeholded");
            }
        });
    } else {
        search.type = "search";
        search.setAttribute("results", 10);
        search.setAttribute("placeholder", AJS.params.quickSearchPlaceholder);
        search.value = "";
    }
});
;
/* END /2static/script/lib/ajs/master.js */
/* START /2static/script/fe/fisheye-ui.js */
if (!AJS.FE) {
    AJS.FE = {};
}

(function() {

    /**
     * Bind a callback that fires when resizing is completed on matching elements.
     *
     * A resize is considered complete after completionDelay ms have passed since
     * the last resize event.
     *
     * This prevents firing the resize event handler multiple times before the resize
     * is actually complete.
     */
    var setCompletedResizeTimeout = function (selector, callback, completionDelay) {
        completionDelay = completionDelay || 100;
        var timeout;

        AJS.$(selector).resize(function () {
            if (completionDelay > 0 && timeout) {
                clearTimeout(timeout);
            }
            timeout = setTimeout(callback, completionDelay);
        });
    };

    var hasSetupPageFillHeight = false;
    AJS.FE.setupPageFillHeight = function () {
        if (hasSetupPageFillHeight) {
            return;
        }
        hasSetupPageFillHeight = true;
        var onResize = function() {
            var windowHeight = AJS.$(window).height();
            var footerHeight = AJS.$("#footer").outerHeight();
            var offset = 34; //factor
            AJS.$("#atlas").css('height', windowHeight - footerHeight - offset);
        };
        onResize();
        setCompletedResizeTimeout(window, onResize);
    };

    var hasSetupSearchPageFillHeight = false;
    AJS.FE.setupSearchPageFillHeight = function () {
        if (hasSetupSearchPageFillHeight) {
            return;
        }
        hasSetupSearchPageFillHeight = true;

        var onResize = function() {
            var view = AJS.$(window).height();
            var above = AJS.$("#content-search").offset().top + AJS.$(".search-control").outerHeight() + AJS.$(".search-console").outerHeight();
            var below = AJS.$("#footer").outerHeight();

            var diff = AJS.$("#search-content").outerHeight() - parseFloat(AJS.$("#search-content").css("height"));//padding, border, etcetera on content
            diff = parseInt(diff + 0.8);//round up

            AJS.$("#search-content").css({
                "height": view - below - above - diff
            });
        };
        onResize();
        setCompletedResizeTimeout(window, onResize);
    };

    /**
     * setup column height maintenance for the page -- does the iniital setup and binds a resize handler to maintain it
     */
    var hasSetupColumnFillHeight = false;
    var setupColumnFillHeight = function () {
        if (hasSetupColumnFillHeight) {
            return;
        }
        hasSetupColumnFillHeight = true;
        var onResize = function() {
            var head = AJS.$("#content").offset().top;
            var foot = AJS.$('#footer').outerHeight();
            var windowHeight = AJS.$(window).height();
            var contentHeight = windowHeight - head - foot - 1.3; //window - masthead - roundingerrorhack
            AJS.$("#content").css({
                paddingBottom: 0,
                height: contentHeight
            });

            AJS.$("#content-resizable").css({
                height: contentHeight
            });

            AJS.$("#content-navigation-panel").css({
                height: contentHeight - 2
            });

            AJS.$("#content-navigation").css({
                width: "100%"
            });
            AJS.$("#content-column").css({
                height: contentHeight,
                marginRight: "auto",
                marginLeft: "auto"
            });

            AJS.$("#content-column-panel").css({
                // TODO This is a temporary fix for IE7 (the #content-column-panel's top shouldn't be so large).
                height: Math.max(0, windowHeight - AJS.$("#content-column-panel").offset().top - foot - 3.3)
            });
        };
        onResize();

        setCompletedResizeTimeout(window, onResize);
    };

    var columnResize = function (min) {
        AJS.$("#content-resizable").resizable({
            start: function(event, ui) {
                collapseReact(event, ui);
            },
            handles: "e",
            maxWidth: 600,
            minWidth: min || 0
        });
        var collapseReact = function (){
            if (AJS.$(".collapsed-sidebar #content-resizable").css("width") !== undefined) {
                AJS.log(AJS.$(".collapsed-sidebar #content-resizable #content-navigation").attr("style"));
            }
        };
    };

    var hasSetupColumnFoot = false;
    var setupColumnFoot = function () {
        if (hasSetupColumnFoot) {
            return;
        }
        hasSetupColumnFoot = true;
        var hasContent = AJS.$("#panel-foot").children("div.holder").children().length > 0;

        if (hasContent) {
            var onResize = function() {
                var height = AJS.$("#content-column .panel").height();
                var panel = Math.floor(height * 0.55);
                var foot = height - panel;

                AJS.$("#content-column .panel").css({
                   height: panel - 6
                });

                AJS.$("#panel-foot").css({
                   height: foot,
                   display: "block"
                });
            };
            onResize();
            setCompletedResizeTimeout(window, onResize);
        }
    };

    var reloadFilePaneAndHeader = function(href, extractionFn) {
        var origUrl = AJS.$("#origUrl").val();
        var $spinner = AJS.$("#file-table-spinner");
        $spinner.show();
        AJS.FECRU.AJAX.ajaxDo(fishEyePageContext + "/json/fe/loadFilePane.do", {href: href, origUrl: origUrl}, function(resp) {
            if (resp.worked) {
                var replacement = AJS.$.clean([resp.fileTable], document);
                AJS.$("#browse-table").replaceWith(replacement);
                AJS.$("#cone").replaceWith(resp.coneDiv);
                AJS.$("#dirlist-taskbar-bar").replaceWith(resp.taskBarBar);
                AJS.$(".content-fixed").replaceWith(resp.contentFixed);
                AJS.FE.resetupTable("browse", null, extractionFn);
                AJS.FECRU.RSS.setupRSSDialog();
                AJS.FECRU.UI.setupQuicksearch();
            }
            $spinner.hide();
        }, false);
    };

    /**
     * Find a link in the directory tree by its href
     */
    var $findLink = function(href) {
        return AJS.$("#navigation-tree").find("a.pathLink[href='" + href + "']");
    };

    AJS.FE.browseDirectoryPathLinkFunction = function(event) {
        var $node = AJS.$(event.target);
        var href = $node.attr("href");
        var toggled = function () { };
        var self = AJS.FE.browseDirectoryPathLinkFunction;
        if ($node.hasClass("browse-directory")) {
            // find the node in the tree with the same href as us
            var $selectedLink = $findLink(href);
            AJS.FECRU.BROWSE.selectLink($selectedLink, toggled, self);
        } else {
            AJS.FECRU.BROWSE.selectLink($node, toggled, self);
        }
        reloadFilePaneAndHeader(href, AJS.FE.extractRevisionDetailsSortKeys);
        return false;
    };

    AJS.FE.setupTable = function(prefix, rowClickFn, extractionFn) {
        AJS.FE.resetupTable(prefix, extractionFn);
        AJS.FECRU.UI.tableRowClick(prefix, rowClickFn);

    };
    AJS.FE.resetupTable = function(prefix, extractionFn) {
        columnResize();
        setupColumnFillHeight();
        setupColumnFoot();
        AJS.FECRU.UI.tableSort(prefix, extractionFn);
    };

    AJS.FE.toggleTabs = function () {
        AJS.$(".tearout-tabs li").live("click", function () {
            var active = AJS.$(this).hasClass("tearout-active") ? true : null;
            var tab = AJS.$(this).attr("class").split("-")[1];
            var panel = "#panel-" + tab;

            AJS.$(".tearout-tabs li").each(function () {
                AJS.$(this).removeClass("tearout-active");
                AJS.$(this).children("a").unbind();
            });

            AJS.$(".panel-tearout","#content-navigation").each(function () {
                AJS.$(this).addClass("hidden");
            });
            // preference will be null if we are ignoring preferences
            var preference = AJS.$(this).children("input").val();
            if (active) {
                AJS.$('#content').addClass('collapsed-sidebar');
                // hiding everything
                if (preference) {
                    AJS.FECRU.PREFS.setPreference("shp", "N");
                }
            }
            else{
                AJS.$(this).addClass("tearout-active");
                AJS.$('#content').removeClass('collapsed-sidebar');
                AJS.$(panel).removeClass("hidden");
                if (preference) {
                    AJS.FECRU.PREFS.setPreferences({slp: preference, shp: "Y"});
                }
            }
            // resize the pane when in changeset view
            if (AJS.$('#section-changeset-view').length > 0) {
                AJS.$(window).resize();
            }
        });
    };

    AJS.FE.toggleWatch = function(val) {

    };

    var hasSetupPanes = false;

    AJS.FE.setupPanes = function() {
        if (!hasSetupPanes) {
            columnResize(100);
            setupColumnFillHeight();
            setupColumnFoot();
            hasSetupPanes = true;
        }
    };

    AJS.$(document).ready(function () {
        // Give a warning if firebug is running
        AJS.FECRU.UI.warnAboutFirebug(function() {
            AJS.$(window).resize();
        });

        //setup the watch links
        var $watchform = AJS.$("form#watchform");
        if ($watchform.length > 0) {
            var $input = $watchform.children("input");
            AJS.$(".watch-on", $watchform).click(function(evnt) {
                evnt.preventDefault();
                $input.val('off');
                $watchform.submit();
            });
            AJS.$(".watch-off", $watchform).click(function(evnt) {
                evnt.preventDefault();
                $input.val('on');
                $watchform.submit();
            });
        }
    });
})();
;
/* END /2static/script/fe/fisheye-ui.js */
/* START /2static/script/fe/fisheye-changeset.js */
if (!AJS.FE.CHANGESET) {
    AJS.FE.CHANGESET = {};
}
(function() {
    /////////////////////
    // private functions
    /////////////////////

    /**
     *from http://www.quirksmode.org/js/cookies.html
     */
    function readCookie(name) {
        var nameEQ = name + "=";
        var ca = document.cookie.split(';');
        for (var i = 0; i < ca.length; i++) {
            var c = ca[i];
            while (c.charAt(0) == ' ') {
                c = c.substring(1, c.length);
            }
            if (c.indexOf(nameEQ) == 0) {
                return c.substring(nameEQ.length, c.length);
            }
        }
        return null;
    }

    /*
     * usefull for appending to urls (esp ajax urls) when the content of the
     * url depends on the user's cookie preferences
     */
    function getCookiePrefToken() {
        try {
            var val = readCookie("crucibleprefs1");
            if (val) {
                val = val.replace(/^D=[0-9]+/, "");
                return encodeURIComponent(val);
            }
        } catch(ex) {
            if (AJS.console) {
                AJS.console.log("error getting cookie preference:" +
                            ex.message +
                            "\nin:" + ex.fileName +
                            "\nat:" + ex.lineNumber);
            }
        }
        return "unknown";
    }

    function csDisplaySpinner(revId) {
        var spinnerHTML = "<img src='" + fishEyePageContext + "/" + fishEyeSTATICDIR +
                          "/images/spinner.gif'><span class='loading'>Loading content</span>";
        var divId = csGetCsDivId(revId);
        var divEle = AJS.$("#" + divId);
        divEle.html(spinnerHTML);
        return divId;
    }

    function csGetCsDivId(revId) {
        return 'showfilesDiv' + revId;
    }

    /////////////////////
    // exported functions
    /////////////////////

    AJS.FE.CHANGESET.loadDiff = function (revId, url, onCompleteFunc) {
        var onDone = function() {
            if (onCompleteFunc) {
                onCompleteFunc();
            }
        };
        var $revDiv = AJS.$("#" + revId);
        if ($revDiv.hasClass("unloaded-diff")) {
            var divId = csDisplaySpinner(revId);
            $revDiv.removeClass("unloaded-diff");
            AJS.FECRU.AJAX.ajaxDo(url, {revid: revId, rndtmp: getCookiePrefToken()}, function(resp) {
                if (resp.worked) {
                    // use 'clean' to avoid the regex that jQuery uses to distinguish HTML from ids
                    var replacement = AJS.$.clean([resp.payload], document);
                    AJS.$("#" + divId).html(replacement);
                    if (onCompleteFunc) {
                        onCompleteFunc();
                    }
                }
            }, false);
        } else {
            if (onCompleteFunc) {
                onCompleteFunc();
            }
        }
    };

    /**
     * loads a diff asynchronously.
     * @param i
     * @param url
     * @param revIds
     */
    AJS.FE.CHANGESET.loadDiffAsync = function (i, url, revIds) {
        var revIdLength = revIds.length;
        var onComp = function(origRequest) {
            // do the chaining.
            AJS.FE.CHANGESET.loadDiffAsync(i + 1, url, revIds);
        };
        var MAX_TO_LOAD = 100; //so that if viewing changeset of a branch operation then the page wont overload, set a hard limit.
        if ((i < revIdLength) && (i < MAX_TO_LOAD)) {
            var revId = revIds[i];
            AJS.FE.CHANGESET.loadDiff(revId, url, onComp);
        }
    };

    AJS.FE.CHANGESET.setupChangesetDiffLoading = function() {
        AJS.$("a.unloaded-diff").live("click",
                function(event) {
                    event.preventDefault();
                    var $node = AJS.$(this);
                    var id = $node.parent().parent().attr("id");
                    var baseClUrl = AJS.$("#baseJSONClUrl").val();
                    AJS.FE.CHANGESET.loadDiff(id, baseClUrl, null);
                }
                );
    };

    AJS.FE.CHANGESET.loadDiffForDt = function($dt) {
        var $revIdDiv = $dt.next().children("div.unloaded-diff");
        // if it is already loaded we won't find that div
        if ($revIdDiv) {
            var baseClUrl = AJS.$("#baseJSONClUrl").val();
            AJS.FE.CHANGESET.loadDiff($revIdDiv.attr("id"), baseClUrl, null);
        }
    };

    /**
     * Just disable the link from doing anything.
     */
    AJS.FE.CHANGESET.changesetPathLinkFn = function() {
        return false;
    };

    AJS.FE.CHANGESET.changesetFileLinkFn = function(event) {
        document.location.hash = AJS.$(event.target).children("input").val();
        return false;
    };

})();
;
/* END /2static/script/fe/fisheye-changeset.js */
/* START /2static/script/fe/fisheye-history.js */
(function() {

var $allCheckboxes = [];

AJS.FE.setupDiffCheckboxes = function() {
    // It can be slow to keep on finding all of the checkboxes, so let's cache them
    $allCheckboxes = AJS.$("#history-table input.history-item");

    AJS.$("#history-table input.history-item").live("click",
            function(event) {
                var $checkboxClicked = AJS.$(this);
                var count = $allCheckboxes.filter(':checked').length;
                $allCheckboxes.each(function() {
                    var $node = AJS.$(this);
                    if (count < 2) {
                        $node.attr("disabled", "");
                    } else if (!$node.attr("checked")) {
                        $node.attr("disabled", "disabled");
                    }
                });
                var $r1 = AJS.$("#r1");
                var $r2 = AJS.$("#r2");
                if (count == 0) {
                    $r1.val("");
                    $r2.val("");
                }
                else if (count == 1) {
                    $r2.val("");
                    $allCheckboxes.each(function() {
                        var $node = AJS.$(this);
                        if ($node.attr("checked")) {
                            $r1.val($node.val());
                            return false; // fast exit
                        }
                    });
                }
                else if (count == 2) {
                    if ($checkboxClicked.attr("checked")) {
                        $r2.val($checkboxClicked.val());
                    }
                }
                event.stopPropagation();
            }
            );
};

AJS.FE.showRevisionDetails = function(row) {
    var $row = AJS.$(row);
    var revisionId = revisionIdForRow($row);
    showRevisionDiv(revisionId);
    setRowFocus(revisionId);
};

    var revisionIdForRow = function($row) {
        return $row.find("input.rev-id").val();
    };

    var revisionForRow = function($row) {
        return $row.find("input.history-item").val();
    };

    var revisionForRevisionId = function(revisionId) {
        return revisionForRow(rowForRevisionId(revisionId));
    };

    var revisionIdForRevision = function(revision) {
        return AJS.$("input.history-item[value='" + revision + "']").attr("id").substr("revision-".length);
    };

    var rowForRevisionId = function(revisionId) {
        return AJS.$("#rev-id-" + revisionId).closest("tr");
    };

    var divForRevisionId = function(revisionId) {
        return AJS.$("#revision-div-" + revisionId).closest("div.holder");
    };

    var $shownHolder = undefined;
    var showRevisionDiv = function(revisionId) {
        if ($shownHolder) {
            $shownHolder.hide();
        }
        $shownHolder = divForRevisionId(revisionId);
        $shownHolder.show();
        window.location.hash = "r" + revisionForRevisionId(revisionId);
    };

    // Cache the focused row as it can be too slow to do AJS.$("tr.history-focus");
    var $focusedRow = undefined;
    var setRowFocus = function(revisionId) {
        if ($focusedRow) {
            $focusedRow.removeClass("history-focus");
        }
        $focusedRow = rowForRevisionId(revisionId);
        $focusedRow.addClass("history-focus");
    };

AJS.FE.fileHistoryPathLinkFn = function(event) {
    event.stopPropagation();
    return true;
};

AJS.FE.setFocusedRevision = function() {
    var location = document.location.hash;
    var revision;
    if (!location) {
        var $row = AJS.$("#history-table > tbody > tr:first");
        revision = revisionForRow($row);
    } else {
        // remove the '#r' from the anchor
        revision = location.substring(2);
    }
    var revisionId = revisionIdForRevision(revision);
    showRevisionDiv(revisionId);
    setRowFocus(revisionId);
    AJS.$("div.panel-content","#content").scrollTo(rowForRevisionId(revisionId));
    
    // setup local-link links
    AJS.$("a.local-link").live('click', function() {
        var href = AJS.$(this).attr("href");
        var revision = href.substring(href.lastIndexOf("#") + 2);
        var revisionId = revisionIdForRevision(revision);
        showRevisionDiv(revisionId);
        setRowFocus(revisionId);
    });
};

AJS.FE.extractRevisionDetailsSortKeys = function(cell) {
    var $node = AJS.$(cell);
    var $input = $node.children("input");
    if ($input.size() > 0) {
        return $input.val();
    } else {
        return $node.text();
    }
};
})();;
/* END /2static/script/fe/fisheye-history.js */
/* START /2static/script/fe/fisheye-search.js */
AJS.FE.setupQuickSearchRepositoryList = function() {
    AJS.$(".search-quick .qs_repobox .rep-select").live("change",
        function (event) {
            var searchString = AJS.$(".search-quick .qs_searchbox .qsInput").val();
            document.location=fishEyePageContext + "/qsearch/" + AJS.$(this).find(":selected").val() + "?q=" + searchString;
        }
    );
};
;
/* END /2static/script/fe/fisheye-search.js */
/* START /script/activitystream.js */
//todo: refactor all these string manips into using real js objects with explicit property names.

function loadActivityStream(viewParam, callbackParams) {
    var pagingMap = constructPagingParamsMap();
    var paramsMap = {
        type:"ajax",
        view:viewParam
    };
    updateActivityStreamImpl(mergeMaps([paramsMap,pagingMap]), "activity", callbackParams);
}

function loadActivityStreamForProject(id, viewParam, callbackParams) {
    var pagingMap = constructPagingParamsMap();
    var paramsMap = {
        type:"ajax",
        view:viewParam,
        project:id
     };
    updateActivityStream(mergeMaps([paramsMap,pagingMap]), callbackParams);
}

function loadFileActivityStream(id, path, repname, viewParam, callbackParams) {
    var pagingMap = constructPagingParamsMap();
    var paramsMap = {
        type:"ajax",
        view:viewParam,
        p:path,
        repname:repname
    };
    updateActivityStreamImpl(mergeMaps([paramsMap,pagingMap]), id, callbackParams);
}

//todo: add max item constraint for changelog view
function loadDirActivityStream(id, path, repname, viewPara, callbackParams) {
    var pagingMap = constructPagingParamsMap();
    var paramsMap = {
        type:"ajax",
        view:viewPara,
        p:path,
        repname:repname
    };
    updateActivityStreamImpl(mergeMaps([paramsMap,pagingMap]), id, callbackParams);
}

function loadDownStreamStarActivity(viewParam, callbackParams) {
    var pagingMap = constructPagingParamsMap();
    var paramsMap = {
        type:"ajax",
        view:viewParam,
        star:"star",
        home:"home"
    };
    updateActivityStream(mergeMaps([paramsMap,pagingMap]), callbackParams);
}

function loadActivityStreamForUser(username, viewParam, callbackParams) {
    var pagingMap = constructPagingParamsMap();
    var paramsMap = {
        type:"ajax",
        view:viewParam,
        username:username
    };
    updateActivityStream(mergeMaps([paramsMap,pagingMap]), callbackParams);
}

function loadActivityStreamForCommitter(committer, repname, viewParam, callbackParams) {
var pagingMap = constructPagingParamsMap();
    var paramsMap = {
        type:"ajax",
        view:viewParam,
        committer:committer,
        repname:repname
    };
    updateActivityStream(mergeMaps([paramsMap,pagingMap]), callbackParams);
}

function updateActivityStream(paramMap, callbackParams) {
    updateActivityStreamImpl(paramMap, 'activity', callbackParams);
}

function pageActivityStream(forwards, id, callbackParams) {
    updateActivityStreamImpl(toParamMap(forwards ? nextPageParams : prevPageParams), id, callbackParams);
    var $elem = AJS.$("#"+id + " #stream");
    var spinnerUrl = fishEyePageContext + "/" + fishEyeSTATICDIR + "/2static/images/spinner_003366.gif";
    $elem.html('<div><img src="' +spinnerUrl+ '" alt="loading" width="13" height="13">loading...</div>');
}


function updateActivityStreamImpl(params, id, callbackParams) {
    var url = fishEyePageContext + '/json/changelog';
    var done = function(resp) {
        var $elem = AJS.$('#' + id);
        try {
            if (resp.worked) {
//                $elem.html(resp.html);
                // the replaceWith call is a hack to get around an IE7 bug, which makes the .html() call fail.
                $elem.replaceWith("<div id='activity'>" + resp.html + "</div>");
                var $toolbar = AJS.$("#activitystream-toolbar");
                $toolbar.replaceWith("<div id='activitystream-toolbar'>" + resp.activityStreamToolbar + "</div>");
                prevPageParams = resp.prevPageParams;
                nextPageParams = resp.nextPageParams;
                thisPageParams = resp.thisPageParams;
                view = resp.view ? resp.view : "all";
                if (resp.onFinishedLoad) {
                    resp.onFinishedLoad(callbackParams);
                }
                AJS.FECRU.UI.initStream();
                var $newElem = AJS.$('#' + id);
                AJS.FECRU.HOVER.addAllLinkPopups($newElem);
            } else {
                handleError(resp, $elem, null);
            }
        } catch (error) {
            handleError(resp, $elem, error);
        }
    };
    AJS.$.getJSON(url, params, done);
}

function handleError(resp, $elem, error) {
    if (error) {
        $elem.html("<div style='cursor:pointer;' onclick=\"AJS.$('#activityStreamErrorBox').toggle();\">" +
                   "Error, cannot complete request. <br>" +
                   "<div id='activityStreamErrorBox' class='ajaxError' style='display:none;cursor:pointer;'>" +
                   "details:" + error.message + "<br>" +
                   "url:" + error.fileName + "<br>" +
                   "line:" + error.lineNumber + "<br>" +
                   "error:<br>" +
                   (function() {
                               var str;
                               for (var k in error) {
                                   str += "error[" + k + "]=" + error[k] + "<br>";
                               }
                               return str;
                   }()) + "<br>" +
                   "</div>" +
                   "</div>");
    } else {
        $elem.html("An activity stream error has occurred.<br>");
    }
}

//implementation of the prototype toQueryParam() method.
//does NOT support queryString of the type key1=val1&key1=val2&key1=val3 -> {key1:[val1, val2, val3]}
function toParamMap(paramString) {
    var KEY = 0;
    var VAL = 1;
    var paramMap = {};
    var cleanedParamString = paramString.charAt(0) == '?' ? paramString.substring(1, paramString.length)
                                                          : paramString;
    var paramPairs = cleanedParamString.split('&');
    for (var i = 0; i < paramPairs.length; i++) {
        if (paramPairs[i].length > 0) {
            var subpair = paramPairs[i].split('=', 2);
            paramMap[subpair[KEY]] = subpair[VAL];
        }
    }
    return paramMap;
}

function constructPagingParamsMap() {
    var originalParams = toParamMap(thisPageParams);
    var keys = ["maxDate","minDate","direction","prevAnchor","nextAnchor"];
    var cleanedParams = {};
    for (var i = 0; i < keys.length; i++) {
        if (originalParams[keys[i]]) {
            cleanedParams[keys[i]] = originalParams[keys[i]];
        }
    }
    return cleanedParams;
}

/**
 *
 * @param maps an array of javascript map to merge together.
 * Be wary that if two maps have the same key, only one of the value is preserved,
 * and it is not specified which one will be preserved.
 */
function mergeMaps(maps) {
    var merged = {};
    for (var i = 0 ; i < maps.length ; i++) {
        var m = maps[i];
        for (var key in m) {
            if (m.hasOwnProperty(key)) {
                merged[key] = m[key];
            }
        }
    }
    return merged;
}

var prevPageParams = "";
var nextPageParams = "";
var thisPageParams = "";
var view = "";
;
/* END /script/activitystream.js */
/* START /2static/script/cru/util.js */
/**
 * Crucible utility functions that can be used from any crucible page.
 */

if (AJS.CRU === undefined) {
    AJS.CRU = {};
}
AJS.CRU.UTIL = {};

(function () {

    /**
     * Crucible base url (without trailing '/').
     *
     * @param permaId optional review id
     */
    AJS.CRU.UTIL.urlBase = function (permaId) {
        if (permaId) {
            return fishEyePageContext + '/cru/' + permaId;
        } else {
            return fishEyePageContext + '/cru';
        }
    };

    /**
     * Crucible JSON base url (without trailing '/').
     *
     * @param permaId optional review id
     */
    AJS.CRU.UTIL.jsonUrlBase = function (permaId) {
        if (permaId) {
            return fishEyePageContext + '/json/cru/' + permaId;
        } else {
            return fishEyePageContext + '/json/cru';
        }
    };
    
    AJS.CRU.UTIL.isReviewPage = function () {
        return typeof review !== 'undefined';
    };


    AJS.CRU.UTIL.startAjaxDialogSpin = function () {
        AJS.$('body').addClass('ajax-dialog');
        AJS.dim();
    };

    AJS.CRU.UTIL.stopAjaxDialogSpin = function () {
        AJS.dim();
        AJS.$('body').removeClass('ajax-dialog');
    };

    AJS.CRU.UTIL.isAjaxDialogSpinning = function () {
        return AJS.CRU.UTIL.isDimmed() && AJS.$('body').hasClass('ajax-dialog');  
    };

    AJS.CRU.UTIL.ajaxDialog = function (url, params) {
        AJS.CRU.UTIL.startAjaxDialogSpin();
        AJS.FECRU.AJAX.ajaxDo(url, params || {}, function (resp) {
            if (resp.worked) {
                if (resp.showDialog) {
                    AJS.CRU.UTIL.stopAjaxDialogSpin();
                    AJS.CRU.DIALOG.$CONTAINER.html(resp.payload);
                } else if (resp.redirect) {
                    // no need to undim
                    window.location = resp.payload;
                }
            }
            // !resp.worked handled by ajaxDo
        });
        return false;
    };

    AJS.CRU.UTIL.stateTransition = function (transition, permaId, params) {
        var util = AJS.CRU.UTIL;
        var url = util.jsonUrlBase(permaId) + '/changeStateAjax';

        params = params || {};

        AJS.$.extend(params, {
            command: transition
        });

        if (util.isReviewPage() && AJS.$.inArray(transition, ['action:completeReview', 'action:summarizeReview']) >= 0) {
            // If completing or summarizing we need to warn if the review has been updated.

            util.startAjaxDialogSpin();
            AJS.CRU.REVIEW.UTIL.reviewUpdatedAjax({
                done: function () {
                    var reviewUpdated = AJS.$('body').hasClass('review-updated');
                    if (reviewUpdated) {
                        AJS.CRU.REVIEW.UTIL.warnAboutReviewUpdates({ reshowWarning: true });
                    }
                    util.stopAjaxDialogSpin();
                    return util.ajaxDialog(url, AJS.$.extend(params, { reviewUpdated: reviewUpdated }));
                }
            });
            return false;

        } else {
            return util.ajaxDialog(url, params);
        }
    };

    AJS.CRU.UTIL.editDetailsFormChange = false;
    AJS.CRU.UTIL.checkEditForm = function (done) {
        if (AJS.CRU.UTIL.editDetailsFormChange) {
            AJS.CRU.REVIEW.UTIL.postEditDetailsForm(done);
        } else {
            if (done) {
                done();
            }
        }
        return false;//do a link action if called from <a>
    };

    AJS.CRU.UTIL.command = function (cmd, pid, button) {
        var perma = pid || permaId;
        if (button) {
            button.disabled = true;
        }
        var donext = function() {
            var url = AJS.CRU.UTIL.urlBase(perma);
            window.location = url + '/' + cmd;
        };
        //check and post the editDetailsForm if it has changed
        AJS.CRU.UTIL.checkEditForm(donext);
    };


    AJS.CRU.UTIL.createBlankReview = function (params) {
        var url = AJS.CRU.UTIL.jsonUrlBase() + '/createReviewDialog';
        return AJS.CRU.UTIL.ajaxDialog(url, params);
    };

    AJS.CRU.UTIL.addToReview = function (params) {
        var url = AJS.CRU.UTIL.jsonUrlBase() + '/createDialog';
        return AJS.CRU.UTIL.ajaxDialog(url, params);
    };

    AJS.CRU.UTIL.isAnyDialogShowing = function () {
        return AJS.$('body').children('div.dialog:visible').length > 0;
    };

    AJS.CRU.UTIL.isDimmed = function () {
        return !!AJS.dim.dim;
    };

})();
;
/* END /2static/script/cru/util.js */
/* START /2static/script/cru/dialog/dialog.js */
if (!AJS.CRU) {
    AJS.CRU = {};
}
AJS.CRU.DIALOG = {};

(function () {

    AJS.CRU.DIALOG.$CONTAINER = AJS.$('<div id="ajax-dialog-container"></div>');
    AJS.$(document).ready(function () {
        AJS.$('body').append(AJS.CRU.DIALOG.$CONTAINER);
    });

    AJS.CRU.DIALOG.ajaxDialog = function (maxWidth, maxHeight, data, id, optionalClass) {
        var dialog = AJS.FECRU.DIALOG.create(maxWidth, maxHeight, id, optionalClass);
        AJS.$.each(
            AJS.$.extend({ dialog: dialog }, data),
            function (k, v) {
                AJS.CRU.DIALOG.$CONTAINER.data(k, v);
            }
        );
        return dialog;
    };

})();
;
/* END /2static/script/cru/dialog/dialog.js */
/* START /2static/script/cru/dialog/dialog-event.js */
(function () {

    var $container = AJS.CRU.DIALOG.$CONTAINER;

    var dialogHandler = function (callbacks) {
        return function (event) {
            var dialog = $container.data('dialog');
            var permaId = $container.data('permaId');

            if (AJS.CRU.UTIL.isReviewPage() && callbacks.review) {
                return callbacks.review(dialog, permaId, event);
            } else if (!AJS.CRU.UTIL.isReviewPage() && callbacks.external) {
                return callbacks.external(dialog, permaId, event);
            }
        };
    };

    var viewFiltersHandler = function (filters) {
        return dialogHandler({
            review: function (dialog) {
                if (AJS.$('body').hasClass('review-updated')) {
                    window.location.hash = 'f-' + filters.join(',');
                    window.location.reload(true);
                } else {
                    AJS.CRU.REVIEW.UTIL.filterAndExpandFrxs(filters);
                    dialog.remove();
                }
            },
            external: function (dialog, permaId) {
                window.location = AJS.CRU.UTIL.urlBase(permaId) + '#f-' + filters.join(',');
            }
        });
    };

    var batchProcessDraftComments = function (action, permaId, onComplete) {
        var url = AJS.CRU.UTIL.jsonUrlBase(permaId) + '/draftCommentsAjax';
        var params = {
            action: action
        };
        var done = function (resp) {
            if (onComplete) {
                onComplete(resp);
            }
        };
        AJS.FECRU.AJAX.ajaxDo(url, params, done, true);
    };

    var draftCommentsHandler = function (action) {
        return dialogHandler({
            review:  function (dialog) {
                AJS.$.each(review.draftComments(), function (i, comment) {
                    commentator[action + 'Comment'](comment.id(), review.id());
                });
                AJS.$('#dialog-drafts-links').hide();
            },
            external: function (dialog, permaId) {
                batchProcessDraftComments(action, permaId, function (resp) {
                    if (resp.worked) {
                        AJS.$('#dialog-drafts-links').hide();
                    } else {
                        // TODO Show error message within dialog panel.
                    }
                });
            }
        });
    };

    AJS.$(document).ready(function () {
        AJS.$('#dialog-view-drafts').live('click', viewFiltersHandler(['draftcomments']));
        AJS.$('#dialog-view-unread-comments').live('click', viewFiltersHandler(['unreadcomments']));
        AJS.$('#dialog-view-incomplete-frxs').live('click', viewFiltersHandler(['incomplete']));
        AJS.$('#dialog-view-unresolved-jiras').live('click', viewFiltersHandler(['unresolvedsubtasks']));

        AJS.$('#dialog-delete-drafts').live('click', draftCommentsHandler('delete'));
        AJS.$('#dialog-post-drafts').live('click', draftCommentsHandler('publish'));

        AJS.$('.dialog-add-to-review').live('click', function () {
            var permaId = AJS.$(this).attr('id').replace(/^dialog-add-to-review-/, '');
            var $container = AJS.CRU.DIALOG.$CONTAINER;
            $container.data('dialog').remove();
            AJS.CRU.UTIL.addToReview(AJS.$.extend($container.data('params'), {
                task: permaId
            }));
        });
    });

})();
;
/* END /2static/script/cru/dialog/dialog-event.js */
/* START /2static/script/cru/review/email-comments.js */
if (!AJS.CRU) {
    AJS.CRU = {};
}
if (!AJS.CRU.REVIEW) {
    AJS.CRU.REVIEW = {};
}
if (!AJS.CRU.REVIEW.EMAILCOMMENTS) {
    AJS.CRU.REVIEW.EMAILCOMMENTS = {};
}

(function() {
    var dialog;
    var self = AJS.CRU.REVIEW.EMAILCOMMENTS;

    var nextButton = function () {};
    var previousButton = function () {};

    self.showPage = function (pageId) {

        AJS.$(".email-wizard", "#emailWizardDialog").each(function() {
            if (AJS.$(this).is('#' + pageId)) {
                AJS.$(this).show();
            } else {
                AJS.$(this).hide();
            }
        });
        AJS.$("#emailWizardDialog").find(".previousButton, .nextButton, .cancelButton").each(function() {
            AJS.$(this)
                .attr('disabled', false)
                .show();
        });

        if (pageId === 'recipients') {
            AJS.$(".nextButton", "#emailWizardDialog").html("Next");
            nextButton = function () {
                self.showPage('message');
            };
            AJS.$(".previousButton", "#emailWizardDialog")
                    .hide();
            previousButton = function () {};

        } else if (pageId === 'message') {
            AJS.$(".nextButton", "#emailWizardDialog").html("Send");
            nextButton = function () {
                var $form = AJS.$("#emailWizardDialog form#emailCommentsForm");
                var url = $form.attr('action');
                AJS.$(".nextButton", "#emailWizardDialog").attr("disabled", true);
                AJS.FECRU.AJAX.ajaxUpdate(url, $form.serialize(), "dialogPanel");
            };
            previousButton = function () {
                self.showPage('recipients');
            };

        } else if (pageId === 'sent') {
            AJS.$(".nextButton", "#emailWizardDialog").html("Close");
            nextButton = function () {
                dialog.remove();
            };
            previousButton = function () {
                self.showPage('message');
            };
        }
    };

    self.start = function (permaId) {

        dialog = AJS.CRU.DIALOG.ajaxDialog(900, 600, {}, "emailWizardDialog", "hover-over")
            .addHeader("Email Review Comments")
            .addPanel("Recipients", "<div id='dialogPanel'>Loading...</div>")
            .addButton("Cancel", function(dialog) {
                dialog.remove();
            }, "cancelButton")
            .addButton("Previous", function() {
                previousButton();
            }, "previousButton")
            .addButton("Next", function() {
                nextButton();
            }, "nextButton")
            .show();

        var url = AJS.CRU.UTIL.jsonUrlBase(permaId) + "/allcommentsemail";

        AJS.FECRU.AJAX.startSpin("dialogPanel", false, "span", "email-spinner");
        AJS.FECRU.AJAX.ajaxUpdate(url, {}, "dialogPanel", function () {
            AJS.FECRU.AJAX.stopSpin(AJS.$('#dialogPanel').get(0), "span");
            AJS.$("form#emailCommentsForm").find("#to").focus();
        });
    };
} )();
;
/* END /2static/script/cru/review/email-comments.js */
