Overview
FogBugz allows you to add “customizations” to change the look and feel and even the behavior of the application.
While many FogBugz users like to make their special changes to the app, we have found that many users have similar needs. Therefore, we maintain this list of customizations in the hopes that one or more of them might be useful for you!
Consider this article as a kind-of a repository of FogBugz Customizations.
Introduction
Read first this article: Enabling, Configuring, and Creating New Customizations for FogBugz
Please also note the disclaimer mentioned in the article.
Description
Quick-add Subcases and Parent Cases
Here’s a script that adds links to quickly create new subcases or parent cases from the case view.
name: Sub and Parent case links on case view description: Adds links to create a sub- or parent-case right from a case view version: 2.0.0.0 js: $(function(){ var isOcelot = function() { return (typeof fb.config != 'undefined'); }; // if this isn't Ocelot, and we're not on the oldbugz case page, don't run if (!isOcelot() && !$('#bugviewContainer').length) { return; } function getSel() { if (window.getSelection) return window.getSelection().toString(); else if (document.getSelection) return document.getSelection().toString(); else if (document.selection) return document.selection.createRange().text; return ''; }; function makeButtonHtml(sLinkUrl, sLinkText, sClass, fIsOcelot) { var sHtml = ""; if (fIsOcelot) { sHtml = '<a class="control ' + sClass + '" name="' + sClass + '" href="' + sLinkUrl + '"><span class="icon icon-' + sClass + '"></span>' + sLinkText + '</a>'; } else { sHtml = '<li><a class="actionButton2 icon-left ' + sClass + '" href="' + sLinkUrl + '">' + sLinkText + '</a></li>'; } return sHtml; } function addButtons(fIsOcelot) { // array of buttons jQuery finds, using a different selector for each UI var buttons; // the bug JS object is goBug in the old UI and fb.cases.current.bug in Ocelot var bug; // tags differ in the two UIs: bug.tags in Ocelot and goBug.ListTagsAsArray() in the old UI var rgTags; // when using url parameters to populate case form fields on page-load, Ocelot and the // old UI differ in one or more. In this instance, the tag list is "tags" in Ocelot // and "sTags" in the old UI var sTagParamName; // based on the UI, remove the existing buttons and set the above vars if (fIsOcelot){ $('a.control.addsubcase,a.control.addparent').remove(); // Remove existing buttons buttons = $('span.controls'); bug = fb.cases.current.bug; rgTags = bug.tags; sTagParamName = "tags"; } else { $('.icon-left.addsubcase,.icon-left.addparent').remove(); // Remove existing buttons buttons = $("ul.buttons"); bug = goBug; rgTags = goBug.ListTagsAsArray(); sTagParamName = "sTags"; } var sLinkStart = '/default.asp?command=new&pg=pgEditBug' + '&ixCategory=' + bug.ixCategory + '&ixProject=' + bug.ixProject + '&ixArea=' + bug.ixArea + '&ixFixFor=' + bug.ixFixFor + '&ixPersonAssignedTo=' + bug.ixPersonAssignedTo + '&sCustomerEmail=' + encodeURIComponent(bug.sCustomerEmail) + '&ixPriority=' + bug.ixPriority + '&' + sTagParamName + '=' + encodeURIComponent(rgTags) + '&sEvent='; // To be updated dynamically. var sButtonsHtml = makeButtonHtml(sLinkStart + '&ixBugParent=' + bug.ixBug + '&b=c', "Subcase", "addsubcase", fIsOcelot); // ixBugChildren in the request doesn't put the value in the subcases box in Ocelot // like it did in the old UI 🙁 if (bug.ixBugParent == 0) { sButtonsHtml += makeButtonHtml(sLinkStart + '&ixBugChildren=' + bug.ixBug + '&b=c', "Parent", "addparent", fIsOcelot); } buttons.prepend(sButtonsHtml); } // this needs to be idempotent. in ocelot, we don't know on a full page // load if it was run on the /nav/end event so we run it again after subscribing var runThisWhenCasePageModeChanges = function(sCommand, fIsOcelot) { if (sCommand == 'load' || sCommand == 'view') { // show the buttons on case view, but not the bulk case view: if ($('#bulkBugItems').length > 0) { return; } addButtons(fIsOcelot); } else { // do something on case edit in one of the various modes: edit, // assign, resolve, close, reactivate, reopen, open, email, reply, // forward, and new } }; // ------ set up to call your code when the case view changes ------------------- // Bug Monkey customizations are run only on full page-load. In ocelot, this only // happens when you go direclty to a URL or when you refresh the whole page. // Therefore, we just want to subscribe to the navigation event that fires whenever // the view is changed, as well as subscribe to any other events we care about // and run our code one initial time. if (isOcelot()) { // /nav/end fires at the end of every single-page-app navigation, e.g. when // the list page is done displaying or when the case page is done changing from // view to edit mode. the event param contains some useful info e.g. // event.route is something like '/cases/234/case-title-here' and event.url // has the entire url of the page // on the case page, fb.cases.current.sAction is the mode of the page just like // in the old UI: view, edit, assign, resolve, close, reactivate, reopen, // open, email, reply, forward and new fb.pubsub.subscribe({ '/nav/end': function(event) { //console.log("navigated to url " + event.url); //console.log("route: " + event.route); // if it's the case page, add the onkeyup for the title field and // run an initial check of the fields if (typeof fb.cases.current.sAction != 'undefined') { runThisWhenCasePageModeChanges(fb.cases.current.sAction, true); } else { //console.log('ocelot page but not the case page'); } } }); // depending on timing, if you go directly to a case page, the pubsub might not // finish before /nav/end is called the first time, so run the function once // after a delay. remember your code should be idempotent setTimeout(function() { runThisWhenCasePageModeChanges(fb.cases.current.sAction, true) },100); } else { // the old UI case page is a full page load in many situations, but does use ajax // transitions for certain things, like clicking edit while viewing a case or // canceling an edit in progress. // this runs on full page load and determines which action is occurring based on // what case-page html elements are present if ($('#bugviewContainerEdit textarea').length > 0) { if ($('div.ixBug a').length > 0) { if ($('#sTo').length > 0) { // warning: not localized: if ($('#bugviewContainerEdit div.bugevent div.summary span.action:contains("Replied by")').length > 0) { runThisWhenCasePageModeChanges('reply', false); } // warning: not localized: else if ($('#bugviewContainerEdit div.bugevent div.summary span.action:contains("Forwarded by")').length > 0) { runThisWhenCasePageModeChanges('forward', false); } else { runThisWhenCasePageModeChanges('email', false); } } // warning: not localized: else if ($('#bugviewContainerEdit div.bugevent div.summary span.action:contains("Assigned by")').length > 0) { runThisWhenCasePageModeChanges('assign', false); } // warning: not localized: else if ($('#bugviewContainerEdit div.bugevent div.summary span.action:contains("Closed by")').length > 0) { runThisWhenCasePageModeChanges('close', false); } // warning: not localized: else if ($('#bugviewContainerEdit div.bugevent div.summary span.action:contains("Reactivated by")').length > 0) { runThisWhenCasePageModeChanges('reactivate'); } else if ($('#Button_Resolve').length > 0) { runThisWhenCasePageModeChanges('resolve', false); } else { runThisWhenCasePageModeChanges('edit', false); } // add to this: direct link could be to edit or reply or... } else if ($('#bulkBugItems').length > 0) { // bulk action runThisWhenCasePageModeChanges('edit', false); // add to this: direct link could be to edit or reply or... } else { runThisWhenCasePageModeChanges('new', false); } } else { // this is the case view page on a full page-load. The sCommand value // on an ajax load is 'view' If you do not need to distinguish // the two, change the string 'load' here to 'view'. // Note that in Ocelot, the sAction is always 'view' and never 'load' runThisWhenCasePageModeChanges('load', false); } // full page loads are handled above. To handle the ajax // transitions, hook into the BugViewChange event: $(window).on('BugViewChange', function(e, data) { runThisWhenCasePageModeChanges(data.sCommand, false); }); } $(document).on('mouseup', '#bugviewContainer', function() { // Update sEvent: $('a.subcase,a.addparent').each(function() { this.setAttribute('href', this.getAttribute('href') .replace(/&sEvent=[^&]*/, '&sEvent=' + encodeURIComponent(getSel()))); }); }); }); css: /* Green plus sign: */ .icon-left.addsubcase:before, .icon-left.addparent:before { background-position: 0px -374px; height: 16px; width: 16px; } /* plus used in the Iteration Planner */ .icon-addparent:before { content: "\e61d"; color: #80a1bd; } .icon-addsubcase:before { content: "\e61d"; color: #80a1bd; } #bugviewContainer .buttonbar ul.toolbar.buttons { white-space: nowrap; }
Removing Alert from FogBugz screen: Permanently
This removes Alerts like expiry notification from the FogBugz page. This piece of code is to be added to the CSS Page.
Warning: This will remove all alerts Permanently. To restore the alerts, please remove this part from the CSS.
.on-site-expiration-notice {
display: none !important;
}
Auto-link a custom field to an external system
This customization looks for a custom text field, grabs the value from it, and formats it as a link to another web app looking up that value. To enable this customization to properly generate the URL, edit the variable linkBase, and update the line in the link. The Field function which formats the URL.
name: Create custom link from text field description: turns a specific custom text field into a link to another site version: 2.0.0.0 js: $(function(){ // note: the bulk of this code (including comments) is based on this case page Customization template: // https://support.fogbugz.com/hc/en-us/articles/360011344013-Custom-JavaScript-and-CSS-Case-Page-Template var isOcelot = function() { return (typeof fb.config != 'undefined'); }; // if this isn't Ocelot, and we're not on the old UI case page, don't run if (!isOcelot() && !$('#bugviewContainer').length) { return; } // configure the field to work with: // note: does not work with fields containing single quotes // 2.0.0.0 of this Customization is only tested with text-type custom fields containing a int w/ no spaces var fieldName = 'Other Ticket #'; // put the link base here var linkBase = "http://some.other.tracker/"; // in the old UI: // in both views, the label containing the field name is inside a div.dialog-item // in view mode, the field value text is in a div.content next to the label inside the div.dialog-item // in edit mode, the input tag is also in a div.content next to the label inside the div.dialog-item // in ocelot: // in view mode, the label containing the field name is inside a div.field.customfield.view // the field value text is in a div.content next to the label inside the div.field.customfield.view // in edit mode, the label containing the field name is inside a div.contains-customfield // the input tag is inside the label var linkTheField = function(fIsOcelot) { var customFieldLabelTag = $('label:contains("' + fieldName + '")'); var customField = customFieldLabelTag.parent().find('div.content'); if (customField.length == 1) { var fieldText = customField.text(); var otherTicketNumber = parseInt(fieldText); if (!isNaN(otherTicketNumber)) { customField.html('' + otherTicketNumber + ''); } } }; // this needs to be idempotent. in ocelot, we don't know on a full page // load if it was run on the /nav/end event so we run it again after subscribing // this was called runLinkConversion in the previous version of the script // but changed when I used the 'case page hook.js' as a starting point for a new // version with ocelot support var runThisWhenCasePageModeChanges = function(sCommand, fIsOcelot) { if(sCommand == 'view' || sCommand == 'load' || sCommand == 'email' || sCommand == 'reply' || sCommand == 'forward') { linkTheField(fIsOcelot); } }; // ------ set up to call your code when the case view changes ------------------- // ------ everything below is straight from the 'case page hook.js' sample code - // ------ https://support.fogbugz.com/hc/en-us/articles/360011344013-Custom-JavaScript-and-CSS-Case-Page-Template ------- // Bug Monkey customizations are run only on full page-load. In ocelot, this only // happens when you go direclty to a URL or when you refresh the whole page. // Therefore, we just want to subscribe to the navigation event that fires whenever // the view is changed, as well as subscribe to any other events we care about // and run our code one initial time. if (isOcelot()) { // /nav/end fires at the end of every single-page-app navigation, e.g. when // the list page is done displaying or when the case page is done changing from // view to edit mode. the event param contains some useful info e.g. // event.route is something like '/cases/234/case-title-here' and event.url // has the entire url of the page // on the case page, fb.cases.current.sAction is the mode of the page just like // in the old UI: view, edit, assign, resolve, close, reactivate, reopen, // open, email, reply, forward and new fb.pubsub.subscribe({ '/nav/end': function(event) { //console.log("navigated to url " + event.url); //console.log("route: " + event.route); // if it's the case page, add the onkeyup for the title field and // run an initial check of the fields if (typeof fb.cases.current.sAction != 'undefined') { runThisWhenCasePageModeChanges(fb.cases.current.sAction, true); } else { //console.log('ocelot page but not the case page'); } } }); // depending on timing, if you go directly to a case page, the pubsub might not // finish before /nav/end is called the first time, so run the function once // after a delay. remember your code should be idempotent setTimeout(function() { runThisWhenCasePageModeChanges(fb.cases.current.sAction, true) },100); } else { // the old UI case page is a full page load in many situations, but does use ajax // transitions for certain things, like clicking edit while viewing a case or // canceling an edit in progress. // this runs on full page load and determines which action is occurring based on // what case-page html elements are present if ($('#bugviewContainerEdit textarea').length > 0) { if ($('div.ixBug a').length > 0) { if ($('#sTo').length > 0) { // warning: not localized: if ($('#bugviewContainerEdit div.bugevent div.summary span.action:contains("Replied by")').length > 0) { runThisWhenCasePageModeChanges('reply', false); } // warning: not localized: else if ($('#bugviewContainerEdit div.bugevent div.summary span.action:contains("Forwarded by")').length > 0) { runThisWhenCasePageModeChanges('forward', false); } else { runThisWhenCasePageModeChanges('email', false); } } // warning: not localized: else if ($('#bugviewContainerEdit div.bugevent div.summary span.action:contains("Assigned by")').length > 0) { runThisWhenCasePageModeChanges('assign', false); } // warning: not localized: else if ($('#bugviewContainerEdit div.bugevent div.summary span.action:contains("Closed by")').length > 0) { runThisWhenCasePageModeChanges('close', false); } // warning: not localized: else if ($('#bugviewContainerEdit div.bugevent div.summary span.action:contains("Reactivated by")').length > 0) { runThisWhenCasePageModeChanges('reactivate'); } else if ($('#Button_Resolve').length > 0) { runThisWhenCasePageModeChanges('resolve', false); } else { runThisWhenCasePageModeChanges('edit', false); } // add to this: direct link could be to edit or reply or... } else if ($('#bulkBugItems').length > 0) { // bulk action runThisWhenCasePageModeChanges('edit', false); // add to this: direct link could be to edit or reply or... } else { runThisWhenCasePageModeChanges('new', false); } } else { // this is the case view page on a full page-load. The sCommand value // on an ajax load is 'view' If you do not need to distinguish // the two, change the string 'load' here to 'view'. // Note that in Ocelot, the sAction is always 'view' and never 'load' runThisWhenCasePageModeChanges('load', false); } // full page loads are handled above. To handle the ajax // transitions, hook into the BugViewChange event: $(window).on('BugViewChange', function(e, data) { runThisWhenCasePageModeChanges(data.sCommand, false); }); } });
Tidy Case Events
Minor edits to a case (those with no comment text) can clutter up the case view. This customization hides them and provides a link in the left sidebar to restore them.
name: Tidy Case Events description: Hides all brief edited-by events; adds a link in the left sidebar to restore them. version: 2.0.1 js: $(function(){ var isOcelot = function() { return (typeof fb.config != 'undefined'); }; var runThisWhenCasePageModeChanges = function(sCommand, fIsOcelot) { if (sCommand == 'load' || sCommand == 'view') { var c = 0; $('section.case .event.brief').not('.has-attachments').each(function(){ var ev = $(this); if(ev.find('.action').text().indexOf('Edited') !== -1) { ev.hide(); c++; } }); if(c > 0) { $('#tceLabel').remove(); $('section.case .left:not(.corner)').append('<label id="tceLabel" class="field">Tidy Case Events<div class="content"><span class="dotted href" id="tceToggle">Show ' + c + ' minor edits.</span></div></label>'); } } }; if (isOcelot()) { fb.pubsub.subscribe({ '/nav/end': function(event) { if (typeof fb.cases.current.sAction != 'undefined') { runThisWhenCasePageModeChanges(fb.cases.current.sAction, true); } } }); $('body').on('click', '.case #tceToggle', function() { $('#tceLabel').remove(); $('section.case .event.brief').show(); }); setTimeout(function() { runThisWhenCasePageModeChanges(fb.cases.current.sAction, true) },100); } }); css:
Reply As Me
This customization will change the From name to yours whenever replying to or forwarding an email.
name: Reply as me by default description: Change the default From address to my personal name version: 2.0.0.1 js: $(function() { var isOcelot = function() { return (typeof fb.config != 'undefined'); }; var runThisWhenCasePageModeChanges = function(sCommand, fIsOcelot) { if (sCommand == 'reply' || sCommand == 'forward') { var fromField = $('#email-from-droplist'); var dropList = fromField.droplist(); var currentFrom = dropList.val(); var newFrom = ""; for (i=0; i < dropList.config.choices.length; i++) { if (dropList.config.choices[i].value == currentFrom && i % 2 == 0) { newFrom = dropList.config.choices[i+1].value; } } dropList.val(newFrom); }; }; if (isOcelot()) { fb.pubsub.subscribe({ '/nav/end': function(event) { if (typeof fb.cases.current.sAction != 'undefined') { runThisWhenCasePageModeChanges(fb.cases.current.sAction, true); } } }); setTimeout(function() { runThisWhenCasePageModeChanges(fb.cases.current.sAction, true) }, 100); }; }); css:
Show Keyboard Shortcuts/Access Keys in Ocelot
This script will label all of the Access Keys in Ocelot.
name: Show Keyboard Shortcuts/Access Keys in Ocelot description: Show Keyboard Shortcuts (access keys) in Ocelot. Access keys are show in pink text in format of '[a]' where 'a' is the access key for the action or button. version: 1.0.1.0 js: function js() { init(); function init() { appendAccessKeyCharacter(); fb.pubsub.subscribe("/nav/end", appendAccessKeyCharacter); } function appendAccessKeyCharacter() { $('[accesskey]').each(function(){ var title = $(this).attr('title') var appendKey = ' [' + $(this).attr('accesskey') + ']'; if (title && title.indexOf(appendKey) == -1) { // doesn't already have it $(this).attr('title', title + appendKey); } }); } } css: /* body { background-color: red !important; } */ a[accesskey]:after, button[accesskey]:after, input[accesskey]:after, label[accesskey]:after, legend[accesskey]:after, textarea[accesskey]:after { margin-left: 0.3em; color: Plum; content: "[" attr(accesskey) "]";}
Printer-friendly Lightboxes
This script places a convenient “Print” button on top of the lightbox and also works with Cmd/Ctrl-p. Makes FogBugz cases more printer-friendly.
name: Print
description: Print lightboxes nicely. This places a convenient "Print" button at the top of the lightbox but also works with Cmd/Ctrl-p in at least Chrome and Firefox.
Does not work when selecting "Print" from the file menu.
version: 1.1.0.0 js: var printLightbox = function() {
const printWindow = window.open(window.location.href);
printWindow.onload = function() {
setTimeout(function() {
const header = printWindow.$('article').find('.top').find('h1');
const title = printWindow.$(header).text();
printWindow.$(header).text(fb.cases.current.bug.ixBug + ': ' + title);
let extraOffset = 0;
if (printWindow.$('.left')[1].style.width) {
extraOffset = parseInt(printWindow.$('.left')[1].style.width-185) ;
}
printWindow.$('.left')[1].style.width = null;
//printWindow.$('.left').hide();
printWindow.$('nav').hide();
printWindow.$('#header').hide();
printWindow.$('header')[0].style.borderColor = '#fff';
printWindow.$('header')[1].style.borderColor = '#fff';
let caseHeaderInfo = printWindow.$('.case-header-info');
for (let i = 0; i < caseHeaderInfo.length; i += 1) {
caseHeaderInfo[i].style.borderColor = '#fff';
}
printWindow.$('.top')[0].style.borderColor = '#fff';
printWindow.$('article')[0].style.borderColor = '#fff';
printWindow.$('header')[0].style.position = 'relative';
printWindow.$('header')[0].style.right = '0px';
printWindow.$('header')[1].style.position = 'relative';
printWindow.$('header')[1].style.right = '0px';
printWindow.$('.events')[0].style.position = 'relative';
const eventOffset = extraOffset;
printWindow.$('.events')[0].style.right = eventOffset + 'px';
printWindow.focus();
printWindow.print();
setTimeout(function() { printWindow.close(); }, 0);
}, 1000);
};
};
$(function(){
var isOcelot = function() {
return (typeof fb.config != 'undefined');
};
// if this isn't Ocelot, and we're not on the oldbugz case page, don't run
if (!isOcelot() && !$('#bugviewContainer').length) {
return;
}
// this needs to be idempotent. in ocelot, we don't know on a full page
// load if it was run on the /nav/end event so we run it again after subscribing
var runThisWhenCasePageModeChanges = function(sCommand, fIsOcelot) {
if (!fIsOcelot || $('.print').length) {
return;
}
const isLightbox = !!$('.case-lightbox').length
if (sCommand == 'load' || sCommand == 'view') {
const buttonHTML = '<a class="control print" name="print" href=""><span class="icon icon-print"></span>Print</a>';
const controls = $('span.controls');
controls.prepend(buttonHTML);
$('.print').click( (e) => {
e.preventDefault();
printLightbox();
});
$(window).keydown( (e) => {
if ((e.ctrlKey || e.metaKey) && e.keyCode == 80 && fb.cases.current.bug /* check whether we're actually looking at a case */) {
e.preventDefault();
//printLightbox();
}
});
}
};
// ------ set up to call your code when the case view changes -------------------
// Customizations are run only on full page-load. In ocelot, this only
// happens when you go direclty to a URL or when you refresh the whole page.
// Therefore, we just want to subscribe to the navigation event that fires whenever
// the view is changed, as well as subscribe to any other events we care about
// and run our code one initial time.
if (isOcelot()) {
// /nav/end fires at the end of every single-page-app navigation, e.g. when
// the list page is done displaying or when the case page is done changing from
// view to edit mode. the event param contains some useful info e.g.
// event.route is something like '/cases/234/case-title-here' and event.url
// has the entire url of the page
// on the case page, fb.cases.current.sAction is the mode of the page just like
// in the old UI: view, edit, assign, resolve, close, reactivate, reopen,
// open, email, reply, forward and new
fb.pubsub.subscribe({
'/nav/end': function(event) {
//console.log("navigated to url " + event.url);
//console.log("route: " + event.route);
// if it's the case page, add the onkeyup for the title field and
// run an initial check of the fields
if (typeof fb.cases.current.sAction != 'undefined') {
runThisWhenCasePageModeChanges(fb.cases.current.sAction, true);
}
else {
//console.log('ocelot page but not the case page');
}
}
});
// depending on timing, if you go directly to a case page, the pubsub might not
// finish before /nav/end is called the first time, so run the function once
// after a delay. remember your code should be idempotent
setTimeout(function() { runThisWhenCasePageModeChanges(fb.cases.current.sAction, true) }, 100);
}
else {
// the old UI case page is a full page load in many situations, but does use ajax
// transitions for certain things, like clicking edit while viewing a case or
// canceling an edit in progress.
// this runs on full page load and determines which action is occurring based on
// what case-page html elements are present
if ($('#bugviewContainerEdit textarea').length > 0) {
if ($('div.ixBug a').length > 0) {
if ($('#sTo').length > 0) {
// warning: not localized:
if ($('#bugviewContainerEdit div.bugevent div.summary span.action:contains("Replied by")').length > 0) {
runThisWhenCasePageModeChanges('reply', false);
}
// warning: not localized:
else if ($('#bugviewContainerEdit div.bugevent div.summary span.action:contains("Forwarded by")').length > 0) {
runThisWhenCasePageModeChanges('forward', false);
}
else {
runThisWhenCasePageModeChanges('email', false);
}
}
// warning: not localized:
else if ($('#bugviewContainerEdit div.bugevent div.summary span.action:contains("Assigned by")').length > 0) {
runThisWhenCasePageModeChanges('assign', false);
}
// warning: not localized:
else if ($('#bugviewContainerEdit div.bugevent div.summary span.action:contains("Closed by")').length > 0) {
runThisWhenCasePageModeChanges('close', false);
}
// warning: not localized:
else if ($('#bugviewContainerEdit div.bugevent div.summary span.action:contains("Reactivated by")').length > 0) {
runThisWhenCasePageModeChanges('reactivate');
}
else if ($('#Button_Resolve').length > 0) {
runThisWhenCasePageModeChanges('resolve', false);
}
else {
runThisWhenCasePageModeChanges('edit', false);
}
// add to this: direct link could be to edit or reply or...
}
else if ($('#bulkBugItems').length > 0) {
// bulk action
runThisWhenCasePageModeChanges('edit', false);
// add to this: direct link could be to edit or reply or...
}
else {
runThisWhenCasePageModeChanges('new', false);
}
}
else {
// this is the case view page on a full page-load. The sCommand value
// on an ajax load is 'view' If you do not need to distinguish
// the two, change the string 'load' here to 'view'.
// Note that in Ocelot, the sAction is always 'view' and never 'load'
runThisWhenCasePageModeChanges('load', false);
}
// full page loads are handled above. To handle the ajax
// transitions, hook into the BugViewChange event:
$(window).on('BugViewChange', function(e, data) {
runThisWhenCasePageModeChanges(data.sCommand, false);
});
}
}); css: .icon-print:before {
content: "\2399";
color: #80a1bd;
}
#bugviewContainer .buttonbar ul.toolbar.buttons {
white-space: nowrap;
}
Additional From: addresses
This customization will add an additional From addresses to your options whenever replying to or forwarding an email.
name: Additional From Addresses for Emails description: Provide additional options for the From: address when sending emails from FogBugz version: 1.0.0.0 js: $(function() { var isOcelot = function() { return (typeof fb.config != 'undefined'); }; var runThisWhenCasePageModeChanges = function(sCommand, fIsOcelot) { if (sCommand == 'reply' || sCommand == 'forward') { var fromField = $('#email-from-droplist'); var choices = fromField.droplist().droplistChooser.choices; var emailToAdd = { value: "\"Another Email\" <someone@somedomain.com>", text: "\"Another Email\" <someone@somedomain.com>" }; choices.push(emailToAdd); }; }; if (isOcelot()) { fb.pubsub.subscribe({ '/nav/end': function(event) { if (typeof fb.cases.current.sAction != 'undefined') { runThisWhenCasePageModeChanges(fb.cases.current.sAction, true); } } }); setTimeout(function() { runThisWhenCasePageModeChanges(fb.cases.current.sAction, true) }, 100); }; }); css:
Purple links for visited cases on the case list page
When developing FogBugz, we try to pay extra attention to accessibility in all forms. This includes making the colors easy to differentiate for users who have trouble visually distinguishing certain colors (including color blindness). If you’d prefer to instead use a color scheme where unvisited links on the case list page are blue and visited ones are purple, this customization will make it so.
Please note: we highly recommend not setting this customization to be required for all your users so that those users who need the more accessible default color scheme can use it.
name: Purple links for visited cases on the case list page description: Changes the color of visited links to purple version: 1.0.0.0 js: // Nothing here css: a.grid-title-link.visited.case { color: #8f09c4 !important; } div.grid-column-contents > a.visited.case { color: #8f09c4 !important; }
This customization adds a new menu to the sidebar that supports any number of custom links, including sorting those links into sections.
name: Links Menu description: Adds a menu to the sidebar with custom links version: 2.0.0 js: $((function($){ if (!fb) { return } fb.customizations = fb.customizations || {}; if (fb.customizations.linksMenu) { return } function addLink(section, text, url, func) { let linkId = section.toLowerCase().replace(/[^a-z0-9_\-.]/g, '-') + '-' + text.toLowerCase().replace(/[^a-z0-9_\-.]/g, '-'); if ($('#' + linkId).length) { // An item in this section with the same name has already been added to the list return; } let menuSection; for (let existingSection of fb.customizations.linksMenu.sections) { if (existingSection.header.text.toLowerCase() == section.toLowerCase()) { menuSection = existingSection break; } } if (!menuSection) { menuSection = {header: {text: section}, items: []}; fb.customizations.linksMenu.sections.push(menuSection); } menuSection.items.push({text: text, linkId: linkId, href: url}); } fb.customizations.linksMenu = { sections: [], addLink: addLink }; function getTemplateLists() { return fb.customizations.linksMenu.sections; } fb.sidebar._addCustomMenu('Links', getTemplateLists, '#icon-forward'); })(jQuery)); // Add additional calls here to add more links // Parameters: Section, Link Title, Link URL fb.customizations.linksMenu.addLink('FogBugz', 'Try FogBugz', 'https://www.fogbugz.com/try-fogbugz' ); fb.customizations.linksMenu.addLink('FogBugz', 'FogBugz API', 'https://support.fogbugz.com/hc/en-us/articles/360011242374-FogBugz-API-Introduction' ); css: