android中cannocan t resolve symboll 'itemlnfo

android(71)
一、自己写一个RatingBar的style
&style name=&roomRatingBar& parent=&@android:style/Widget.RatingBar&&
&item name=&android:progressDrawable&&@drawable/ratingbar_drawable&/item&
&item name=&android:minHeight&&20dp&/item&
&item name=&android:maxHeight&&20dp&/item&
二、style用到的资源也需要写一下
ratingbar_drawable.xml
&?xml version=&1.0& encoding=&utf-8&?&
&layer-list xmlns:android=&/apk/res/android&&
&item android:id=&@android:id/background&
android:drawable=&@drawable/live_pl_zan0&&&/item&
&item android:id=&@android:id/secondaryProgress&
android:drawable=&@drawable/live_pl_zan0&&&/item&
&item android:id=&@+android:id/progress&
android:drawable=&@drawable/live_pl_zan1&&&/item&
&/layer-list&
注意:一开始,item的id我参照网上例子写,id是的这样
item android:id=&@+android:id/background&
结果报错说 Cannot resolve symbol '@+android:id/background' less... (Ctrl+F1)& Validates resource references inside Android XML files.
解释是这样的:/questions//cannot-resolve-symbol-error-when-using-androidid-androidid-my-id
If you are creating your own id:
&@+id/your_new_id&
if you are accessing your already created id
&@id/your_old_id&
if you are trying to access Android's system created id
&@android:id/system_id&
you can see the difference, if you are creating your own id then you have to add+. Since you are accessing system ID so you don't have to
&意思就是自己新建的id带加号,引用的不需要带!
三、然后就是写布局的时候引用上style
android:numStars=&5&
android:rating=&3&
android:stepSize=&0.5&
activity_pingfen.xml
&?xml version=&1.0& encoding=&utf-8&?&
&RelativeLayout xmlns:android=&/apk/res/android&
xmlns:tools=&/tools& android:layout_width=&match_parent&
android:layout_height=&match_parent& android:paddingLeft=&@dimen/activity_horizontal_margin&
android:paddingRight=&@dimen/activity_horizontal_margin&
android:paddingTop=&@dimen/activity_vertical_margin&
android:paddingBottom=&@dimen/activity_vertical_margin&
&ImageView
android:id=&@+id/img&
android:layout_width=&wrap_content&
android:layout_height=&wrap_content&
android:src=&@drawable/ic_keyan_companyintro_background&/&
&RatingBar
android:id=&@+id/ratingbar&
android:layout_width=&wrap_content&
android:layout_height=&wrap_content&
android:numStars=&5&
android:max=&255&
android:progress=&255&
style=&@style/roomRatingBar&
android:layout_below=&@id/img&
android:layout_marginTop=&10dp&
android:stepSize=&0.1&/&
android:id=&@+id/textview&
android:layout_width=&wrap_content&
android:layout_height=&wrap_content&
android:text=&000&
android:layout_below=&@id/ratingbar&/&
&/RelativeLayout&
四、activity中正常使用即可
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pingfen);
RatingBar ratingbar =(RatingBar) findViewById(R.id.ratingbar);
ratingbar.setRating(0);
final ImageView img =(ImageView) findViewById(R.id.img);
img.setAlpha((int)0.0*255/5);
final TextView textview =(TextView) findViewById(R.id.textview);
ratingbar.setOnRatingBarChangeListener(new RatingBar.OnRatingBarChangeListener() {
public void onRatingChanged(RatingBar ratingBar, float rating, boolean fromUser) {
img.setAlpha((int)rating*255/5);
textview.setText(&rating=&+rating);
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:70955次
积分:1187
积分:1187
排名:千里之外
原创:29篇
转载:137篇
(1)(4)(10)(1)(1)(2)(2)(1)(3)(2)(3)(1)(1)(1)(1)(2)(8)(4)(2)(1)(1)(12)(2)(3)(8)(10)(7)(7)(12)(9)(13)(21)(5)(5)
(window.slotbydup = window.slotbydup || []).push({
id: '4740881',
container: s,
size: '200,200',
display: 'inlay-fix'Bugmonkey Script Archive – Fog Creek Knowledge Base
Using FogBugz On Demand? We've recently rolled out a new sidebar as part of . Please see
for details on what's new, what's changed, and where you can find all your favorite things.
allows you to customize your FogBugz site to suite your needs. Read more about the plugin .
Over the years, we’ve accumulated quite a few useful customizations that we’ve worked on, helped customers write, or been given by enterprising users. Here’s an archive of our favorites, and a major thanks to anyone and everyone who has contributed!
Note: these scripts may not run correctly in FogBugz On Demand / On Site. If you are not funning FogBugz For Your Server, use
Originally posted by nonplus.
Once you have lots of (shared) filters, the filter menu can become cumbersome to use, especially when you try to access your own filters. With this customization, you can organize filters into collapsible groups based on common prefixes.
Anything in the filter name before the first colon is considered the filter’s group name (e.g. if filter name is “Testing: Most Recent Build” then “Testing” is the group name). To group two or more filters together, use the same prefix for them, e.g. “Testing: Production”, “Testing: Development”.
If a group contains two or more filters, the Filters menu will show “Group Name: n filters” instead of the actual filters. Clicking on this item will show or hide the group’s original list of filters indented below it.
Here’s the script for FogBugz for Your Server:
Grouped Filters
description:
Changes Filters menu to group filters by group name
Stepan Riha
$(document).ready(function() {
// Change visibility of links in group
function toggleGroup() {
var $this = $(this);
var group = $this.data('group');
if(!group.prepared) {
prepareLinks(group);
processLinks(group, group.isOpen
? function($link) { $link.hide(); }
: function($link) { $link.show(); });
group.isOpen = !group.isO
// Replace filter group name with its list of filters
function prepareLinks(group) {
var groupname = jQuery.trim(group.text+":");
processLinks(group, function($link) {
var html = $link.html();
html = html.replace(groupname, '');
$link.html(html).addClass('filter_group-link');
group.prepared =
// Apply callback to each link in group
function processLinks(group, callback) {
for(var j = 0; j & group.links. j++) {
callback(group.links[j]);
// Collect filter links and group by prefix
var groups = [];
var $prev =
var group =
$('#filterPopup a').each(function() {
var $this = $(this);
// Use everything up to first : as group name
var text = $this.text();
text = text.replace(/:.*/, '');
// Create new group, if necessary
if(!group || group.text != text) {
group = { text: text, links: [] };
groups.push(group);
group.links.push($this);
// Process groups of 2 or more links
for(var i = 0; i & groups. i++) {
var group = groups[i];
var links = group.
if(links.length & 1) {
// Hide links
processLinks(group, function($link) { $link.hide(); });
// Create group link
group.link = $("&a href='#' class='filter_group'&&span&" + group.text + ": " + links.length + " filters&/span&&/a&")
.bind("click", toggleGroup)
.data('group', group)
.insertBefore(links[0]);
a.filter_group {
padding-left: 17px !
font-style:
a.filter_group span {
padding-left: 5
border-left: solid 3px #B1C9DD;
a.filter_group:hover span {
border-left-color: #E0E9F1;
a.filter_group-link {
padding-left: 23px !
Here’s a new version of the script for FogBugz On Demand:
New Grouped Filters
description:
Changes Filters menu to group filters by group name (for new header)
Stepan Riha and Adam Wishneusky
$(document).ready(function() {
// Change visibility of links in group
function toggleGroup() {
var $this = $(this);
var group = $this.data('group');
if(!group.prepared) {
prepareLinks(group);
processLinks(group, group.isOpen ? function($link) { $link.hide(); }
: function($link) { $link.show(); });
group.isOpen = !group.isO
// Replace filter group name with its list of filters
function prepareLinks(group) {
var groupname = jQuery.trim(group.text+":");
processLinks(group, function($link) {
var html = $link.html();
html = html.replace(groupname, '');
$link.html(html).addClass('filter_group-link');
group.prepared =
// Apply callback to each link in group
function processLinks(group, callback) {
for(var j = 0; j & group.links. j++) {
callback(group.links[j]);
// Collect filter links and group by prefix
var groups = [];
var $prev =
var group =
// selector for oldbugz + old header was $('#filterPopup a')
// selector for ocelot + old header was $('.list-choices-popup div.list-choices-header').parent().find('a')
// in the old header code, each filter was just an &a& tag.
// the new header has an &li& for each item with an &a& inside
// to keep the selectors not-insane, let's do shared and personal filters separately
$(li.button.cases-button & span & ul.dropdown-menu & li:contains("Shared Filters") & ul & li').each(function(){ addToGroups(this); });
$(li.button.cases-button & span & ul.dropdown-menu & li:contains("My Filters") & ul & li').each(function() { addToGroups(this); });
function addToGroups($el) {
// $el looks like:
&a href="default.asp?pgx=LF&ixFilter=3149" id="" class="" title=""&
&img src="images/outline.gif" class="header-filter-icon"&
FogBugz PC
var $this = $($el);
// Use everything up to first : as group name
var text = $this.find('a').text().trim();
text = text.replace(/:.*/, '');
// Create new group, if necessary
if(!group || group.text != text) {
group = { text: text, links: [] };
groups.push(group);
group.links.push($this);
// Process groups of 2 or more links
for(var i = 0; i & groups. i++) {
group = groups[i];
var links = group.
if(links.length & 1) {
// Hide links
processLinks(group, function($link) { $link.hide(); });
// Create group link
group.link = $("&li class='filter_group'&&a href='#'&&span&" + group.text + ": " + links.length + " filters&/span&&/a&&/li&")
.bind("click", toggleGroup)
.data('group', group)
.insertBefore(links[0]);
// in the old header version, these were all 'a's not 'li's
li.filter_group {
padding-left: 14px !
font-style:
li.filter_group span {
padding-left: 5
border-left: solid 3px #B1C9DD;
// color for old header: E0E9F1;
li.filter_group:hover span {
border-left-color: #B1C9DD;
li.filter_group-link {
padding-left: 23px !
Originally posted by John Fuex. Update for FogBugz On Demand by Rohland de Charmoy.
Here’s a BugMonkey script that adds an icon next to the search box with a dropdown of search axis values. Mousing over an item in the dropdown will show more info on how to use that axis, and clicking on it will insert it in the search box.
It isn’t all that pretty yet, but it’s functional. Feel free to update the script here if you want to add some cosmetics to it or improve the mouseover text on the search items.
This version is for FogBugz for Your Server:
Search Box Helper
description:
Adds a syntax helper widget to the search box.
var searchAxes = getSearchAxes();
var srchInput = $('#idDropList_searchFor_oText');
var imgSearchHelperButton = $('&span&&/span&');
imgSearchHelperButton.attr('id','searchHelperButton')
.text('?')
.css('position','absolute')
.css('left',srchInput.position().left - 25)
.css('top', srchInput.position().top);
srchInput.after(imgSearchHelperButton);
var divSearchHelper = $('&div&&/div&')
divSearchHelper.attr('id','divSearchHelper')
.css('position','absolute')
.css('width',srchInput.css('width'))
.css('top', srchInput.position().top + srchInput.outerHeight())
.css('left',imgSearchHelperButton.position().left)
.css('z-index','500')
.css('display','none');
for (var axisID=0; axisID&searchAxes. axisID+=2) {
var divHelpItem = $('&div&&/div&')
var itemText = searchAxes[axisID];
var itemDescription = searchAxes[axisID+1]
if(itemText.substr(0,1)=='#') {
itemText = itemText.substr(1);
divHelpItem .addClass('searchHelperItemHeader')
divHelpItem .addClass('searchHelperItem');
var helpItem = $("&a/&").text(itemText).attr('title',itemDescription);
divHelpItem.append(helpItem);
divSearchHelper.append(divHelpItem );
srchInput.after(divSearchHelper);
// Attach event handlers
$('#searchHelperButton').click(function () { $('#divSearchHelper').toggle();});
$(".searchHelperItem").click(function() {
var srchInput = $('#idDropList_searchFor_oText');
var newSearchText = srchInput.val() + (srchInput.val() != '' ? ' ' : '') + $(this).text() + ':';
srchInput.val(newSearchText);
$('#divSearchHelper').toggle(false);
srchInput.focus();
function getSearchAxes() {
return ["#Cases","Axes for Searching Cases",
"AlsoEditedBy","cases edited by the specified user, to be used in combination with EditedBy",
"Area","cases in the specified area",
"AssignedTo","cases assigned to the specified user",
"Attachment","cases with an attachment with the specified name",
"Category","cases with the specified category",
"Closed","(date) cases closed on the date specified",
"ClosedBy","cases last closed by the specified user",
"CommunityUser","cases that were submitted by the specified community user",
"Computer","cases containing specific text in the second custom field. Note that this field may have been renamed in your installation",
"Correspondent","cases with the specified email correspondent",
"CreatedBy","cases created by the specified user",
"Department","cases belonging to the specified department",
"Due","(date) cases due on the date specified",
"Edited","(date) cases modified on the date specified",
"EditedBy","cases with a bug event generated by the specified user",
"ElapsedTime","cases with the specified (range of) elapsed time",
"EstimateCurrent","cases with the specified (range of) current estimate",
"EstimateOriginal","cases with the specified (range of) original estimate",
"From","cases with emails from the specified email address",
"LastEdited","(date) cases that were modified on the date specified and have not been modified since then",
"LastEditedBy","cases last edited by the specified user",
"LastViewed","(date) cases that you last viewed on the date specified",
"Milestone","cases assigned to the specified milestone",
"Occurrences","Number of occurrences for a BugzScout case",
"Opened","(date) cases opened on the date specified",
"OpenedBy","cases last opened or reopened by the specified user",
"OrderBy","This takes another axis as its argument and sorts the search results by that axis",
"Outline","returns cases in the same subcase hierarchy as the specified case",
"Parent","returns all subcases of the specified case",
"Root","all cases in the hierarchy underneath the specified case",
"Priority","cases with the specified priority",
"Project","cases in the specified project",
"ProjectGroup", "Cases in the specified project group (Requires the Project Groups Plugin",
"RelatedTo","cases that are linked to the specified case",
"Release","same as milestone",
"ReleaseNotes","search cases with text in release notes, use * to see all cases with release notes",
"RemainingTime","cases with the specified (range of) original estimate",
"Resolved","(date) cases resolved on the date specified",
"ResolvedBy","cases last resolved by the specified user",
"Show","cases with the specified attribute (Read, Unread, Subscribed or Spam)",
"StarredBy","starredby:me shows cases you have starred",
"Status","cases with the specified status",
"Tag","cases with the specified tag",
"Title","cases containing the specified words in the title",
"To","cases with email to the specified email address",
"Version","cases containing specific text in the first custom field. Note that this field may have been renamed in your installation",
"ViewedBy","viewedby:me shows cases you have previously viewed",
"#Wiki Pages","Axes for Wiki Pages",
"Edited","(date) wiki pages that were modified on the date specified",
"EditedBy","wiki pages edited by the specified user",
"LastEdited","(date) wiki pages that were modified on the date specified and have not been modified since then",
"LastEditedBy","wiki pages last edited by the specified user",
"LastViewed","(date) wiki pages that you last viewed on the date specified",
"Show","wiki pages with the specified attribute (Read, Unread or Subscribed)",
"StarredBy","starredby:me shows wiki pages you have starred",
"Title","Finds wiki pages containing the specified words in the title",
"ViewedBy","viewedby:me shows wiki pages you have previously viewed",
"Wiki","wiki pages in the specified wiki",
"#Discussion Topics","Axes for Discussion Topics",
"CreatedBy","topics created by the specified user",
"DiscussionGroup","topics in the specified discussion group",
"Edited","(date) topics that were modified on the date specified",
"EditedBy","topics edited by the specified user",
"LastEdited","(date) topics modified on the date specified and which have not been modified since then",
"LastEditedBy","topics last edited by the specified user",
"LastViewed","(date) topics that you last viewed on the date specified",
"Opened","(date) topics opened on the date specified",
"Show","topics with the specified attribute (Read or Unread)",
"StarredBy","starredby:me shows topics you have starred",
"Title","topics containing the specified words in the title",
"Type","type:case for cases, type:wiki for wiki pages, type:discuss for discussion topics",
"ViewedBy","viewedby:me shows topics you have previously viewed"
#divSearchHelper {
background-color:
border: 1px solid #000000;
min-height:30
max-height:200
overflow-y:
#searchHelperButton {
font-family: Sand, fantasy
background-color:#E0E9F1;
cursor:cursor:
.searchHelperItemHeader {
cursor:cursor:
margin-left: 1
font-weight:
.searchHelperItem {
cursor:cursor:
margin-left: 2
This version is for FogBugz On Demand:
$(function(){
var initialiseSearchAxes = function(){
var searchAxes = getSearchAxes();
var srchInput = $('#searchDropListContainer').closest('li');
var imgSearchHelperButton = $('&li class="dropdown js-header-dropdown"&&/li&');
imgSearchHelperButton.attr('id','searchHelperButton').html("&a class='section-link' href='javascript:void(0);'&?&/a&");
srchInput.after(imgSearchHelperButton);
var searchHelper = $('&ul class="dropdown-menu js-header-dropdown-menu"&&/ul&');
searchHelper.attr('id','searchHelper');
searchHelper.append('&li&&h3&Search Axis&/h3&&ul&&/ul&&/li&');
var listParent = searchHelper.find('ul');
for (var axisID=0; axisID&searchAxes. axisID+=2) {
var helpItem = $('&li/&');
var itemText = searchAxes[axisID];
var itemDescription = searchAxes[axisID+1]
if(itemText.substr(0,1)=='#') {
itemText = itemText.substr(1);
helpItem.append($('&h3/&').text(itemText));
helpItem.append($('&a class="js-header-dropdown-link" href="javascript:void(0);"&&/a&').text(itemText).attr('title',itemDescription));
listParent.append(helpItem);
imgSearchHelperButton.append(searchHelper);
searchHelper.find('a').click(function(){
var searchInput = srchInput.find('input');
var currentValue = searchInput.val();
var textToAttach = $(this).text();
var newValue = currentValue == '' ? textToAttach : (currentValue + " " + textToAttach);
searchInput.val(newValue + ":");
searchInput.focus();
function getSearchAxes() {
return ["#Cases","Axes for Searching Cases",
"AlsoEditedBy","cases edited by the specified user, to be used in combination with EditedBy",
"Area","cases in the specified area",
"AssignedTo","cases assigned to the specified user",
"Attachment","cases with an attachment with the specified name",
"Category","cases with the specified category",
"Closed","(date) cases closed on the date specified",
"ClosedBy","cases last closed by the specified user",
"CommunityUser","cases that were submitted by the specified community user",
"Computer","cases containing specific text in the second custom field. Note that this field may have been renamed in your installation",
"Correspondent","cases with the specified email correspondent",
"CreatedBy","cases created by the specified user",
"Department","cases belonging to the specified department",
"Due","(date) cases due on the date specified",
"Edited","(date) cases modified on the date specified",
"EditedBy","cases with a bug event generated by the specified user",
"ElapsedTime","cases with the specified (range of) elapsed time",
"EstimateCurrent","cases with the specified (range of) current estimate",
"EstimateOriginal","cases with the specified (range of) original estimate",
"From","cases with emails from the specified email address",
"LastEdited","(date) cases that were modified on the date specified and have not been modified since then",
"LastEditedBy","cases last edited by the specified user",
"LastViewed","(date) cases that you last viewed on the date specified",
"Milestone","cases assigned to the specified milestone",
"Occurrences","Number of occurrences for a BugzScout case",
"Opened","(date) cases opened on the date specified",
"OpenedBy","cases last opened or reopened by the specified user",
"OrderBy","This takes another axis as its argument and sorts the search results by that axis",
"Outline","returns cases in the same subcase hierarchy as the specified case",
"Parent","returns all subcases of the specified case",
"Root","all cases in the hierarchy underneath the specified case",
"Priority","cases with the specified priority",
"Project","cases in the specified project",
"ProjectGroup", "Cases in the specified project group (Requires the Project Groups Plugin",
"RelatedTo","cases that are linked to the specified case",
"Release","same as milestone",
"ReleaseNotes","search cases with text in release notes, use * to see all cases with release notes",
"RemainingTime","cases with the specified (range of) original estimate",
"Resolved","(date) cases resolved on the date specified",
"ResolvedBy","cases last resolved by the specified user",
"Show","cases with the specified attribute (Read, Unread, Subscribed or Spam)",
"StarredBy","starredby:me shows cases you have starred",
"Status","cases with the specified status",
"Tag","cases with the specified tag",
"Title","cases containing the specified words in the title",
"To","cases with email to the specified email address",
"Version","cases containing specific text in the first custom field. Note that this field may have been renamed in your installation",
"ViewedBy","viewedby:me shows cases you have previously viewed",
"#Wiki Pages","Axes for Wiki Pages",
"Edited","(date) wiki pages that were modified on the date specified",
"EditedBy","wiki pages edited by the specified user",
"LastEdited","(date) wiki pages that were modified on the date specified and have not been modified since then",
"LastEditedBy","wiki pages last edited by the specified user",
"LastViewed","(date) wiki pages that you last viewed on the date specified",
"Show","wiki pages with the specified attribute (Read, Unread or Subscribed)",
"StarredBy","starredby:me shows wiki pages you have starred",
"Title","Finds wiki pages containing the specified words in the title",
"ViewedBy","viewedby:me shows wiki pages you have previously viewed",
"Wiki","wiki pages in the specified wiki",
"#Discussion Topics","Axes for Discussion Topics",
"CreatedBy","topics created by the specified user",
"DiscussionGroup","topics in the specified discussion group",
"Edited","(date) topics that were modified on the date specified",
"EditedBy","topics edited by the specified user",
"LastEdited","(date) topics modified on the date specified and which have not been modified since then",
"LastEditedBy","topics last edited by the specified user",
"LastViewed","(date) topics that you last viewed on the date specified",
"Opened","(date) topics opened on the date specified",
"Show","topics with the specified attribute (Read or Unread)",
"StarredBy","starredby:me shows topics you have starred",
"Title","topics containing the specified words in the title",
"Type","type:case for cases, type:wiki for wiki pages, type:discuss for discussion topics",
"ViewedBy","viewedby:me shows topics you have previously viewed"
var searchAxesInitialised =
if (fb.pubsub){
fb.pubsub.subscribe('/nav/end', function(e){
if (!searchAxesInitialised){
initialiseSearchAxes();
searchAxesInitialised =
Originally posted by Rene Cavet.
Here’s a script that adds links to quickly create new subcases or parent cases from the case view.
+Sub/Parent Case links
description:
Add new subcase/parent case quick links to the case view
Adam Wishneusky, Chad McElligott, Michel de Ruiter
if (!window.goBug)
function getSel() {
(window.getSelection)
window.getSelection().toString();
else if (document.getSelection)
return document.getSelection().toString();
else if (document.selection)
return document.selection.createRange().
return '';
function addButtons() {
$('.icon-left.subcase,.icon-left.addparent').remove(); // Existing buttons
if ($("ul.buttons").length == 0)
var sLinkStart = 'default.asp?command=new&pg=pgEditBug'
'&ixCategory='
goBug.ixCategory
'&ixProject='
goBug.ixProject
'&ixArea='
goBug.ixArea
'&ixFixFor='
goBug.ixFixFor
'&ixPersonAssignedTo=' +
goBug.ixPersonAssignedTo +
'&sCustomerEmail='
+ encodeURIComponent(goBug.sCustomerEmail)
'&ixPriority='
goBug.ixPriority
+ encodeURIComponent(goBug.ListTagsAsArray()) +
'&sEvent='; // To be updated dynamically.
var sNewButtons = '&li&&a class="actionButton2 icon-left subcase" href="'
sLinkStart +
'&ixBugParent='
goBug.ixBug
'&b=c"&Subcase&/a&&li&';
if (goBug.ixBugParent == 0)
sNewButtons +=
'&li&&a class="actionButton2 icon-left addparent" href="' +
sLinkStart +
'&ixBugChildren='
goBug.ixBug
'&b=c"&Parent&/a&&/li&';
$("ul.buttons").prepend($(sNewButtons));
addButtons();
$(window).on('BugViewChange', addButtons);
$(document).on('mouseup', '#bugviewContainer', function() { // Update sEvent:
$('a.subcase,a.addparent').each(function() {
this.setAttribute('href', this.getAttribute('href')
.replace(/&sEvent=[^&]*/, '&sEvent=' + encodeURIComponent(getSel())));
/* Green plus sign: */
.icon-left.subcase::before,
.icon-left.addparent::before {
background-position: 0px -163
height: 16
#mainArea ul {
font-size: 12px !
#bugviewContainer .buttonbar ul.toolbar.buttons {
white-space:
Originally posted by Roman.
Here’s some sample javascript which can be used to replace text in bug events. I’m currently using this for creating links and replacing long urls with shorter ones within bug events.
In this sample there are two objects which I use as a poor man’s replacement for hashes:
aRegExps contains regular expressions to match in the text of the bug event
aReplacements contains the replacement texts for the corresponding regexps
What these replacements do:
svnlink – This replaces a string in the form of SVN#revision with a link to our svn viewVC server
shortenlink – Replaces a very long url (we have a lot of those here) with a shorter one (note that this doesn’t modify the href in the anchor tag, just the text that is displayed)
replaceurl – Replaces one url with another
To add more replacements just add a matching pair of aRegExps and aReplacements objects.
A couple of notes about this sample:
The regular expressions in the sample are obviously examples which I replaced before posting here and need to be customized.
Remember to escape special characters in the regexps
It has been mostly tested in Chrome and Firefox. No guarantees on other browsers.
Here’s the sample:
Note that you need to edit the below js code to adit/add your keywords and corresponding replacements.
Replace links
description:
Replaces keywords with links in BugEvents
Roman Hernandez
var aRegExps = new Object();
var aReplacements = new Object();
aRegExps["svnlink"] = RegExp(/SVN#(d+)/g);
aReplacements["svnlink"] = "&a href="http://your.svn.url/viewvc?view=rev&revision=$1"&SVN#$1&/a&";
aRegExps["shortenlink"] = RegExp(/&mon.url/path?link=something/g);
aReplacements["shortenlink"] = "&SVN#";
aRegExps["replaceurl"] = RegExp(/http://url.to.replace/g);
aReplacements["replaceurl"] = "http://replacement.url.here";
// Find all the bugevent body elements
$("div.body").each(
function (){
var content = $(this).html();
var replace =
for (var key in aRegExps){
if (aRegExps[key].test(content)){
content = content.replace(aRegExps[key], aReplacements[key]);
if (replace)
$(this).html(content);
Originally posted by Jon Erickson.
Use this customization to create a custom menu that looks and performs exactly like the native menus in FogBugz (Filters, Schedules, Wiki, etc).
Custom Menu
description:
Custom Menu
Jon Erickson
====================================================================================================
CUSTOMIZE THESE VARIABLES
urlToFogBugz
URL to your fogbugz installation, make sure that there is an ending slash '/'
customMenuTitle
Title of the custom menu you wish to be displayed
customMenuId
unique id for the menu, needs to be unique from all other non-custom fogbugz menus as well
====================================================================================================
var urlToFogBugz
= 'http://url.to.fogbugz/',
customMenuTitle = 'Custom Menu Title',
customMenuId
= 'myCustomMenu';
(function($) {
if ($('#Menu_LogInOut & span').text() === 'Log Off') { // If user is currently logged on.
function CreateMenuLink(text, url) {
return $('&a&').attr({ 'onclick': 'return doPopupClick();', 'href': url }).text(text);
function CreateMenuBugLink(text, urlParams) {
return CreateMenuLink(text, urlToFogBugz + 'default.asp?command=new&pg=pgEditBug' + urlParams);
// Create your custom links that you want to appear in the menu
// fogbugz will automatically prefill the drop downs with the query string parameters
var prefilledBugLink1 = CreateMenuBugLink('Add Prefilled Bug 1', '&ixProject=10&ixArea=46&ixCategory=4'),
prefilledBugLink2 = CreateMenuBugLink('Add Prefilled Bug 2', '&ixProject=10&ixArea=46&ixCategory=4'),
externalLink1 = CreateMenuLink('Google', '/'),
externalLink2 = CreateMenuLink('Bing', '/');
// Put links in the order that you want them to appear in the menu
// Put in horizontal rules in desired positions
var helpDiv = $('&div&')
.append(prefilledBugLink1)
.append(prefilledBugLink2)
.append('&hr /&')
.append(externalLink1)
.append('&hr /&')
.append(externalLink2);
// shouldn't need to customize anything below this comment...
var customMenu = $('&span&')
.attr('id', customMenuId)
'display': 'none',
'position': 'absolute',
'left': '0px',
'top': '0px'
.addClass('popupMenu')
.addClass(customMenuId);
customMenu
.append('&table&')
.children('table')
.append('&tbody&&/tbody&')
.children('tbody')
.append('&tr&&/tr&')
.children('tr')
.append('&td&&/td&')
.children('td')
.append(helpDiv);
var helpMenu = $('&a&')
'id': 'Menu_' + customMenuId,
'title': customMenuTitle,
'href': urlToFogBugz
.addClass('navlink')
.addClass('menu')
.text(customMenuTitle);
$('#mainnav')
.append(helpMenu)
.append(customMenu);
$(function() {
theMgr.add(customMenuId);
$('#Menu_' + customMenuId).click(function(e) {
e.preventDefault();
return theMgr.showPopup(customMenuId, this, 0, this.offsetHeight + 2, null, true) || KeyManager.browseMenus('mainnav') || KeyManager.oMenuBrowser.setElCurrent(this) || KeyManager.browsePopup(customMenuId);
})(jQuery);
Originally posted by Gal Segal.
Great new script for you Chuck Norris lovers – the plugin that will freak you out! Do you want your developers to log into FogBugz more often? Well, this will give them a real reason. Introducing the “Chuck Norris Jokes Rotator”!
Chuck Norris Jokes Rotator
description:
Adds some Chuck Norris jokes on top
(function($){
$.fn.chuckIt = function(options) {
var plugin = function(container) {
this.container =
this.container.css({
color: '#000',
position:'absolute',
left:'10px',
display:'inline'
this.options = $.extend({}, this.defaults, options);
plugin.prototype = {
updateInterval:undefined,
container:undefined,
defaults: {
interval:10000
start: function() {
var self =
url: "/jquery.icndb.min.js",
dataType: "script",
success: function(d) {
self.setInterval();
self.updateJoke();
setInterval: function () {
this.updateInterval = setInterval($.proxy(this.updateJoke, this), this.options.interval);
clearInterval: function() {
clearInterval(this.updateInterval);
updateJoke: function() {
var self =
this.clearInterval();
$.icndb.getRandomJoke({
success:function(joke){
console.log(self);
self.container.fadeOut(500, function(){
self.container.html(joke.joke)
.fadeIn(500);
self.setInterval();
$(this).each(function(i, v) {
var pluginInstance = new plugin($(v));
pluginInstance.start();
})(jQuery);
$(document).ready(function(){
$('&div id="joke"&&/div&').appendTo('#banner').chuckIt();
Originally posted by Quentin Schroeder.
In using FogBugz, I noticed that most of the users didn’t like seeing the full history of everywhere a bug had been in its storied life. For particularly old bugs, there might be a full page of “Assigned to Peter”, “Assigned to Paul”, “Assigned to Mary”, “Milestone changed to 1.2.3” et cetera. Since people managing the bugs usually needed to see these ’empty edits’ and the people fixing bugs only rarely did, I use BugMonkey to hide the ’empty edits’. I use a session cookie to remember the setting.
Also, I very rarely need to see the full header of an email. We use email as a method for our support staff to submit information about cases. As such, their email should look more like an edit and less like something else. I’ve reformatted the email so that the header is togglable (default to off) and the emails look more like edits. If the email is an Out of Office Autoreply, hide it.
This will also provide color coding for incoming vs. outgoing emails, so the email chain can be easily scanned for information.
Hide empty edits and tidy up
description:
Hides edits with no text, optionally hides email headers, colors incoming and outgoing emails
alficles, FogBugz 8 compatibility edits by Quentin Schroeder
// Cookie functions (mostly) stolen from some blog on the net.
function createCookie(name, value, days)
if (days) {
var date = new Date();
date.setTime(date.getTime()+(days*24*60*60*1000));
var expires = "; expires="+date.toGMTString();
else var expires = "";
document.cookie = name+"="+value+expires+"; path=/";
function readCookie(name)
var ca = document.cookie.split(';');
var nameEQ = name + "=";
for(var i=0; i & ca. i++) {
var c = ca[i];
while (c.charAt(0)==' ') c = c.substring(1, c.length); //delete spaces
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
function eraseCookie(name)
createCookie(name, "", -1);
$("div.emailHeader").parent().prepend("&div class="emailHeaderToggle"&Toggle Email Header&/div&");
$("div.emailHeader").each(function(i) {
var whoEle = $(this).find("div.emailHeaderValue:first");
var actEle = whoEle.parents("div.bugevent").find("span.action");
var who = whoEle.text();
var act = actEle.text();
var nameReg = /"([^"]*)"/;
if (!(/^Replied/.exec(act))) {
if (nameReg.test(whoEle.text())) {
actEle.append(" by "+nameReg.exec(who)[1]);
actEle.append(" by "+whoEle.html());
$("div.emailHeaderToggle").click(function() {
var emailHeader = $(this).parent().find("div.emailHeader");
var old_val = emailHeader.css("display");
if (old_val == "block") { emailHeader.css("display","none"); }
else { emailHeader.css("display","block"); }
var emptyCount = 0;
$(".bugevent").each(function() {
var bodyEle = $(this).find(".body");
var bodyTxt = bodyEle.text();
if (/^[ tn]*$/.exec(bodyTxt)) {
$(this).addClass("emptyBody");
emptyCount += 1;
$(".email .emailHeader .emailHeaderValue").each(function() {
if (/^Out of Office AutoReply/i.exec($(this).text())) {
$(this).parents(".bugevent").addClass("emptyBody");
emptyCount += 1;
if (emptyCount & 0) {
$("#BugEvents").prepend("&div id="EmptyBodyToggle"&Show/Hide Empty Edits ("+emptyCount+")&/div&");
$("#EmptyBodyToggle").click(function() {
var old_val = $(".emptyBody").css("display");
if (old_val == "block")
$(".emptyBody").css("display","none");
createCookie("showEmpty","false");
$(".emptyBody").css("display","block");
createCookie("showEmpty","true");
if (readCookie("showEmpty") == "true") {
$(".emptyBody").css("display","block");
$(".email").each(function() {
var actions = this.getElementsByClassName('action');
if (actions.length & 0)
if (/Replied/.exec(actions[0].innerText)) {
$(this).children('.body').addClass("outgoing");
else { // outgoing message
$(this).children('.body').addClass("incoming");
div.emailBody {
padding: 0
background-color:
div.email {
div.incoming {
background-color: #E0F5E0 ! // green
div.outgoing {
background-color: #EBF5FF ! // blue
div.emailHeader {
border: 1px solid #ADABA8;
margin-bottom: 10
margin-top: 5
div.editable div.emailHeader {
div.emailHeaderToggle {
font-size: 70%;
font-style:
color: #888;
padding: 2
div.emailHeaderToggle:hover {
text-decoration:
color: #000;
#EmptyBodyToggle {
font-style:
color: #888;
padding-bottom: 7
#EmptyBodyToggle:hover {
text-decoration:
color: #000;
.emptyBody {
padding-left: 5
margin-left: 5
border-left: 3px solid #d6d6d6;
Originally posted by Dane Bertram.
Here’s a script that adds support for per-project categories. The details are in the linked question, but basically, you just prefix any per-project categories you want to have with their associated project’s name. For example, to have a “Hot Lead” category that only applies to the “Sales” project, you’d create a category called “Sales – Hot Lead”. Any categories that don’t have a “project prefix” are considered global categories and will be visible for all projects.
Filter categories by their project prefix
description:
Allows you to create per-project categories by prefixing the category name with the project. Eg. "Sales - Hot Lead" will only apply to the "Sales" project. Categories without a project prefix (basically, those that don't contain " - " will be displayed for all projects.
Dane Bertram & Daniel LeCheminant
var toggleProjectCategories = function(sProject){
if(!$('#ixCategory').length)
var existingIxCat = parseInt($('#ixCategory :selected').val());
var cats = $('#ixCategory').empty();
$(DB.Category).each(function(ix, cat){
if(cat.fDeleted) // skip deleted categories
var sCategoryPrefix = /^(.+) - (.+)/.exec(cat.sCategory); // capture the project prefix and non-prefixed category name
if(!sCategoryPrefix || sCategoryPrefix[1] === sProject){
// either a global category (no project prefix), or a per-project
// category that matches the currently selected project
var newOpt = $('&option&')
.val(cat.ixCategory)
.text(sCategoryPrefix ? sCategoryPrefix[2] : cat.sCategory)
.appendTo(cats);
// if we're transitioning into edit mode, keep the previously-selected category selected
if(cat.ixCategory === existingIxCat) newOpt.attr('selected', 'selected');
DropListControl.refresh(cats[0]);
var init = function(){
$('#ixProject').change(function() {
toggleProjectCategories($(this).find(":selected").text());
toggleProjectCategories($('#ixProject :selected').text());
$(window).bind("BugViewChange", init);
Originally posted by tghw.
The following BugMonkey scripts provide “visual” versions of the Status and Priority columns in the list view.
Visual Status
Visual Status
description:
Replaces status words with colored symbols.
Tyler Hicks-Wright & Dane Bertram
function getStatusCol() {
var hs = $('th.c-h a:contains("' + FB_STATUS + '")');
if(hs.length == 0)
return hs.eq(0).parent().parent().parent().attr('class').match(/col_d+/)[0];
if((col = getStatusCol())) {
$('.' + col).each(function(i, e) {
var span = $(e).find('span');
if(span.length & 0) {
var status = span.text();
span.attr({title: status}).css({fontWeight: 'bold', textAlign: 'center'});
if(status.match(new RegExp('(Active|' + FB_ACTIVE + ').*'))) span.text('A').css({color: 'green'});
else if(status.match(new RegExp('(Resolved|' + FB_RESOLVE + ').*'))) span.text('R').css({color: 'goldenrod'});
else if(status.match(new RegExp('(Closed|' + FB_CLOSED + ').*'))) span.text('C').css({color: 'darkred'});
else if(status.match(/Verified.*/)) span.text('V').css({color: 'blue'});
else if(status.match(/Approved.*/)) span.html('').css({color: 'green'});
else if(status.match(/Rejected.*/)) span.html('').css({color: 'red'});
var a = $(e).find('a:first');
a.text('?').css({textAlign: 'center'});
Visual Priority
Visual Priority
description:
Shows priority in a smaller, more colorful way.
Tyler Hicks-Wright & Dane Bertram
function getPriorityCol() {
var hs = $('th.c-h a:contains("' + FB_PRIORITY + '")');
if(hs.length == 0)
return hs.eq(0).parent().parent().parent().attr('class').match(/col_d+/)[0];
if((col = getPriorityCol())) {
$('.' + col).each(function(i, e) {
var span = $(e).find('span');
if(span.length & 0) {
var priority = span.text();
var td = span.parent().parent().parent();
td.css({textAlign: 'center'});
span.attr({title: priority}).css({fontWeight: 'bold', textAlign: 'center'});
if(priority.match(/1.*/)) { span.text('1'); td.css({backgroundColor: '#f00'}); }
else if(priority.match(/2.*/)) { span.text('2'); td.css({backgroundColor: '#f60'}); }
else if(priority.match(/3.*/)) { span.text('3'); td.css({backgroundColor: '#fa0'}); }
else if(priority.match(/4.*/)) { span.html('4'); td.css({backgroundColor: '#ff0'}); }
else if(priority.match(/5.*/)) { span.html('5'); td.css({backgroundColor: '#af0'}); }
else if(priority.match(/6.*/)) { span.html('6'); td.css({backgroundColor: '#0f0'}); }
else if(priority.match(/7.*/)) { span.html('7'); td.css({backgroundColor: '#00f'}); }
var a = $(e).find('a:first');
a.html('&img src="images/exclamation.png" /&').css({textAlign: 'center'});
Originally posted by adambox.
This script simply disables the “OK” button when editing a case if the value of the Title field or event body is left blank. For non-logged-in users, it also requires an email address.
Note that this is only for new cases. If you want to require fields during edits, you need to .
Require title, event and email
description:
Cannot submit a case if the title or event is empty, and email is required for non-logged-in users
Quentin Schroeder and Adam Wishneusky
$(document).ready(function(){
// don't do anything if we're not on the case edit page
if (!$('#bugviewContainer').length)
// $(this).attr("title", "Facilita Support");
var okButton = $('#Button_OKEdit')[0];
if (!okButton)
okButton.disabled =
okButton.title = "Case title cannot be blank";
var verifyFields = function(event)
var okButton = $('#Button_OKEdit')[0];
if (($('#idBugTitleEdit')[0].value.length & 0) &&
($('#sEventEdit')[0].value.length & 0) &&
(IsLoggedIn() || ($('#idDropList_sCustomerEmail_oText')[0].value.length & 0) ))
okButton.disabled =
okButton.title = "";
okButton.disabled =
okButton.title = "Case title cannot be blank";
if (this.originalOnKeyUp)
this.originalOnKeyUp(event);
// remove this if you don't want to require titles
var titleText = $('#idBugTitleEdit')[0];
if (titleText.onkeyup)
titleText.originalOnKeyUp = titleText.
titleText.onkeyup = verifyF
// remove this if you don't want to require event text
var eventText = $('#sEventEdit')[0];
if (eventText.onkeyup)
eventText.originalOnKeyUp = eventText.
eventText.onkeyup = verifyF
// remove this if you don't want to require anonymous visitors' email addresses
if (!IsLoggedIn())
var emailText = $('#idDropList_sCustomerEmail_oText')[0];
if (emailText.onkeyup)
emailText.originalOnKeyUp = emailText.
emailText.onkeyup = verifyF
Originally posted by Dane Bertram.
This script adds a “Toggle Hierarchies” button to the upper-right corner of the list cases page. Clicking it will collapse/expand all the case hierarchies on the page.
if( window.location.href.indexOf("pg=pgList") & 0 || window.location.href.indexOf("pgx=LF") & 0)
$('#listNav')
.prepend('&a href="#"&Toggle Hierarchies&/a&&|&')
.click(function() {
var rgTRs = $("#bugGrid tr");
for (ix = 0; ix & rgTRs. ix++)
var oRow = rgTRs[ix];
if ($("a.arrow", oRow).length & 0)
GridControl.toggleNode(oRow.uid);
Originally posted by Glenn Arndt.
Add the following CSS to hide the “Send & Close” and “Resolve & Close” buttons when editing a case.
.dlgButtonWide#Button_SendAndCloseEmail { font-weight: display: }
.dlgButtonWide#Button_ResolveAndClose { font-weight: display: }
Originally posted by tghw.
When you view the edit page for a case, if another user edits it and then you submit your changes, FogBugz warns you that the case has been changed and does’t commit your edit. This is good to prevent some mistakes, but it would be much better if I was notified real-time when someone had made a change, while I’m viewing the case. Do this with the following script!
Case Updated Notification
description:
Shows a notification when the case you are looking has been updated.
Tyler Hicks-Wright et al.
If you update this, please also update /8534/bugmonkey-script-archive#Notification_for_Updates_to_Case_You8217re_Working_On
Change Log:
v1.0.1.0 - Dane: Don't DOS FogBugz (i.e., wait for existing request to finish/fail before starting another)
v1.0.0.9 - Dane: If you turn auto-update off, actually stop polling instead of just ignoring responses
v1.0.0.8 - Dane: Better ajax error handling (works around FogBugz' customized jQuery lib)
v1.0.0.7 - Michel: Play nice with sibling links.
v1.0.0.6 - Quentin & Dane: Handle ajax errors gracefully to avoid loading the full error page
v1.0.0.5 - Michel: Version 8.7 broke Reload link, added css.
v1.0.0.4 - David:
Parse latest event out of form value, don't have to check the API at time 0.
v1.0.0.3 - David:
Add control over the auto-update functionality.
v1.0.0.2 - Rock: The Info object now waits after showing, so we need to check the latest bug event earlier
v1.0.0.2 - Rock: Updated to work with jQuery 1.6 (strict JSON parsing)
v1.0.0.1 - Dane: Don't try to poll on the new bug creation page.
v1.0.0.0 - Tyler: First implementation
// based off of /p/microajax/
function microAjax(url, successCallback, errorCallback) {
this.bindFunction = function (caller, object) {
return function() {
return caller.apply(object, [object]);
this.stateChange = function (object) {
if (this.request.readyState === 4) {
var status = this.request.
if (status &= 200 && status & 300 || status === 304) {
this.successCallback(this.request.responseText);
this.errorCallback(this.request.responseText);
this.getRequest = function () {
if (window.ActiveXObject)
return new ActiveXObject('Microsoft.XMLHTTP');
else if (window.XMLHttpRequest)
return new XMLHttpRequest();
var noop = function () { };
this.postBody = (arguments[3] || "");
this.successCallback = (successCallback || noop);
this.errorCallback = (errorCallback || noop);
this.url =
this.request = this.getRequest();
if (this.request) {
var req = this.
req.onreadystatechange = this.bindFunction(this.stateChange, this);
if (this.postBody !== "") {
req.open("POST", url, true);
req.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
req.setRequestHeader('Connection', 'close');
req.open("GET", url, true);
req.send(this.postBody);
var pollingFrequency = 10000;
var updating =
var updateInterval =
var ixBugEventLatest =
var ourUpdate =
var pendingRequest =
function checkForUpdates(ixBug) {
if (!updating || pendingRequest !== null)
var url = 'bugData.asp?'
+ 'sRequest=bugs'
+ '&sBugs=' + ixBug
+ '&sOutputType=json'
+ '&sActionToken=' + g_ActionTokens.getToken('loadBug')
+ '&_=' + $.now(); // cache buster
pendingRequest = microAjax(url, function (data) {
if (data === "")
data = eval('(' + data + ')');
var bug = data[ixBug];
if(bug.ixBugEventLatest != ixBugEventLatest) {
var link = $('div.ixBug div a').attr('href');
if($('.ghostFontBig:visible').length == 0) {
Info.show('This case has updates.');
$('#loadingBar').html('This case has updates. &a href="'+link+'"&Reload&/a&');
ourUpdate =
if ($('#bugevent_'+bug.ixBugEventLatest).length & 0) {
ixBugEventLatest = bug.ixBugEventL
if (ourUpdate) {
Info.hide();
ourUpdate =
pendingRequest =
}, function () {
Info.show('Lost connection to FogBugz... (will retry automatically)');
ourUpdate =
pendingRequest =
if ($('div.ixBug').length == 1) {
var poller = function (ixBug) {
return function () {
checkForUpdates(ixBug);
var ixBug = $('div.ixBug div a').text();
ixBugEventLatest = $("input[name='ixBugEventLatest']").val();
if (ixBug.length & 0) {
updateInterval = setInterval(poller(ixBug), pollingFrequency);
$('div.subtitle').append('&span class="autoUpdate"& | Auto Update: &a href="#" class="update" title="Enable/disable auto updating for this page"&On&/a&&/span&');
$('div.subtitle a.update').click(function(event) {
event.preventDefault();
updating = !
$(this).text(updating ? 'On' : 'Off');
if (!updating) {
clearInterval(updateInterval);
if (ourUpdate) {
Info.hide();
updateInterval = setInterval(poller(ixBug), pollingFrequency);
#bugviewContainer .top .subtitle .autoUpdate { color: #68615E; }
Originally posted by Dane Bertram.
Here’s a BugMonkey customization that makes the case action bar stick to the top of the screen when you scroll down the page (in view mode), and does the same for the editor (in edit mode):
Floating Bug Controls++
description:
Makes the bug controls stick to the top of the page when scrolling (including the editor)
Kevin Gessner & Dane Bertram
$(function() {
if (!$('#bugviewContainer').length)
$('#bugviewActionButtonsTop').addClass('bugviewWidth');
var inEditMode = function() { return $('#bugviewContainerEdit .editor').length & 0; };
var getDelta = function(sSelector) { return $(window).scrollTop() - $(sSelector).offset(). };
var floatBugControls = function() {
var floatActionBar = !inEditMode() && getDelta('#bugviewContainer') & 0;
$('#bugviewActionButtonsTop').toggleClass('floating', floatActionBar);
$('#bugviewContainerTop').css('margin-top', floatActionBar ? $('#bugviewActionButtonsTop').outerHeight() : 0);
var floatEditor = inEditMode() && getDelta('#bugviewContainerSide') & 0;
var firstBugEvent = $('#BugEvents').find('.pseudobugevent, .bugevent').first();
$('#bugviewContainerEdit').toggleClass('floating', floatEditor);
if (floatEditor) {
$('#bugviewContainerEdit').width($('#BugEvents .bugevent').outerWidth());
firstBugEvent.css('margin-top', $('#bugviewContainerEdit').outerHeight());
firstBugEvent.css('margin-top', 0);
$(window).scroll(floatBugControls);
$(window).bind('BugViewChange', floatBugControls);
floatBugControls();
#bugviewActionButtonsTop, #bugviewContainerEdit {
z-index: 10;
#bugviewActionButtonsTop.floating, #bugviewContainerEdit.floating {
#bugviewActionButtonsTop.floating {
border-bottom: solid 1px #C7C7C7;
box-shadow: #c7c7c7 0 1px 5
/* play nicely with the Floating Top Nav customization: /questions/ */
.floatingTopNav #bugviewActionButtonsTop.floating, .floatingTopNav #bugviewContainerEdit.floating {
Originally posted by andrewmolyneux.
This customization adds a “[Show HTML Source]” link to the top of the email body in any email bug events. Clicking on that link causes the original source email to be fetched and parsed. If it is a multipart/alternative message with a text/html part, the email body in the bug event is replaced with the HTML source code.
This code supports decoding base64-encoded messages, but other transfer encodings (e.g. quoted-printable) are displayed as-is. My requirement was to get the message to the point where it was feasible for a human being to pick out the content.
I didn’t read any specs before writing the email parsing code, so there are probably lots of corner cases that it fails to handle.
I haven’t attempted to optimize the email parsing code at all, so it’s horribly inefficient in terms of space and time. Clicking “Show HTML Source” for large email messages may bring your browser to its knees.
No attempt is made to convert between character encodings. If the HTML source is encoded as anything other than UTF-8, it’ll be a bit of a mess.
The code to decode base64 came from /javascript-base64.html.
HTML email source display
description:
Allows viewing HTML source of multipart/alternative email messages with an HTML part
Andrew Molyneux, Adam Wishneusky
$(function() {
// Base64 from /javascript-base64.html
// Tweaked formatting and removed support for encoding
var Base64 = {
_keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/=",
decode : function (input) {
var output = "";
var chr1, chr2, chr3;
var enc1, enc2, enc3, enc4;
var i = 0;
input = input.replace(/[^A-Za-z0-9+/=]/g, "");
while (i & input.length) {
enc1 = this._keyStr.indexOf(input.charAt(i++));
enc2 = this._keyStr.indexOf(input.charAt(i++));
enc3 = this._keyStr.indexOf(input.charAt(i++));
enc4 = this._keyStr.indexOf(input.charAt(i++));
chr1 = (enc1 && 2) | (enc2 && 4);
chr2 = ((enc2 & 15) && 4) | (enc3 && 2);
chr3 = ((enc3 & 3) && 6) | enc4;
output = output + String.fromCharCode(chr1);
if (enc3 != 64) {
output = output + String.fromCharCode(chr2);
if (enc4 != 64) {
output = output + String.fromCharCode(chr3);
output = Base64._utf8_decode(output);
_utf8_decode : function (utftext) {
var string = "";
var i = 0;
var c = c1 = c2 = 0;
while ( i & utftext.length ) {
c = utftext.charCodeAt(i);
if (c & 128) {
string += String.fromCharCode(c);
} else if((c & 191) && (c & 224)) {
c2 = utftext.charCodeAt(i+1);
string += String.fromCharCode(((c & 31) && 6) | (c2 & 63));
c2 = utftext.charCodeAt(i+1);
c3 = utftext.charCodeAt(i+2);
string += String.fromCharCode(((c & 15) && 12) | ((c2 & 63) && 6) | (c3 & 63));
function htmlEncode(value) {
return $('&div/&').text(value).html();
function parseHeaders(headers) {
var result = {};
var lines = headers.split("rn");
var lastfieldName = '';
$.each(lines, function(i, line) {
if (/^s+/.test(line)) {
result[lastFieldName] +=
var parts = line.split(':');
if (parts.length &= 2) {
var fieldName = $.trim(parts[0]);
var fieldValue = $.trim(parts.slice(1).join(':'));
result[fieldName] = fieldV
lastFieldName = fieldN
function getHeadersAndBody(msg) {
var parts = msg.split("rnrn");
if (parts.length & 2) {
var headersText = parts[0];
var bodyText = parts.slice(1).join("rnrn");
return {headers: parseHeaders(headersText), body: bodyText};
// Given the value of Content-Type, check if it's multipart/alternative.
// If it is, return the boundary string. Otherwise, return false.
function getBoundary(contentType) {
if (!(/^multipart/alternative/.test(contentType))) {
var parts = contentType.split(';');
var reBoundary = /^s*boundary="?([^"]+)"?s*$/;
var result =
$.each(parts, function(i, part) {
var matches = part.match(reBoundary);
if (matches !== null) {
result = matches[1];
// Given the body of a multipart/alternative email message, find
// the part with the given contentType.
function findPart(body, boundary, contentType) {
var reContentType = new RegExp('^' + contentType.replace('/', '/'));
var parts = body.split('--' + boundary);
var result =
$.each(parts, function(i, part) {
var message = getHeadersAndBody(part);
if (message !== false) {
if (reContentType.test(message.headers['Content-Type'])) {
var reBugEventId = /^bugevent_([0-9]+)$/;
$('.bugevent.email').each(function(i,bugEvent) {
var bugEventId = bugEvent.id.match(reBugEventId)[1];
$(bugEvent).find('.emailBody').each(function(i2,emailBody) {
var linkDiv = $('&div&');
var link = $('&a&', {href: '', text: '[Show HTML Source]'});
link.click(function(event) {
'default.asp?pg=pgDownload&pgType=pgSource&ixBugEvent=' + bugEventId,
dataType: 'text',
function(data) {
var message = getHeadersAndBody(data);
var contentType = message.headers['Content-Type'];
var boundary = getBoundary(contentType);
if (boundary === false) {
link.replaceWith('&p&No HTML found.&/p&');
var htmlPart = findPart(message.body, boundary, 'text/html');
if (htmlPart === false) {
link.replaceWith('&p&No HTML found.&/p&');
var htmlBody = htmlPart.
if (htmlPart.headers['Content-Transfer-Encoding'] == 'base64') {
htmlBody = Base64.decode($.trim(htmlBody));
$(emailBody).empty();
$(emailBody).append("&p&" + htmlEncode(htmlBody).replace(/rn/g,'&br&rn') + "&/p&");
linkDiv.append(link);
$(emailBody).prepend(linkDiv);
Originally posted by Rob Sobers.
Here’s a script that auto-links protocols other than http and https
Auto-link more protocols in cases
description:
Makes links out of ftp:// file:// mailto:// etc in cases
Rob Sobers
jQuery.fn.addlink = function ()
var regex = /((ftp|telnet|gopher|file|news|mailto)://w*(.[a-zA-Z0-9/$-_@!*""'(),=;#?:+%~]*)*[a-zA-Z0-9/])/igm
return this.each(function ()
if (this.className.indexOf("editable") & 0 && this.innerHTML.indexOf("emailActions") & 0) {
this.innerHTML = this.innerHTML.replace(regex, "&a href="$1"&$1&/a&");
$(".bugevent .body").addlink();
$(".bugevent .emailBody").addlink();
Originally posted by Daniel LeCheminant.
The following BugMonkey script shows one way to have more control over which/how case events are displayed:
The following script shows one way to have more control over which/how case events are displayed:
// Provide a menu on case view that allows you to do one of the following:
View all events
Show all events as one-line summaries
Show seen events as one-line summaries
Hide seen events
$(document).ready(function() {
// Don't do anything unless we're looking at a single bug
if (!window.goBug ||
!$("#bugviewContainer").length ||
$("#miniBugList").length)
// The text displayed in the display menu
var modes = {
showAll: {
text: "Show All",
label: "Showing all events",
show: ".bugevent"
summarizeAll: {
text: "Summarize All",
label: "Summarizing all events",
show: ".small-summary"
summarizeSeen: {
text: "Summarize Seen",
label: "Summarizing #seen events",
show: ".bugevent:not(.seen),.small-summary.seen"
hideSeen: {
text: "Hide Seen",
label: "Hiding #seen events",
show: ".bugevent:not(.seen)"
// The name of the cookie used to save settings
var sCookie = "sSeenEventMode";
// Read the user's current settings.
Default is to show all
var sSeenMode = getCookie(sCookie) || "showAll";
// Determine the last event the user saw
var ixLastView = goBug.ixBugEventLastV
// A table describing the translation from a standard bug event
// to its summary
var summTrans = [
{ sel: ".action", color: "#000", css: { "font-weight": "bold"} },
{ sel: ".date a", color: "#68615e" },
{ fnHtml: function(ev) {
return (ev.find(".emailBody").html() || ev.find(".body").html() || "").
replace(/&[^&]*&/g, " ");
}, color: "#888"
{ sel: ".changes", color: "#aaa" }
var jAllEvents = $("#BugEvents .bugevent");
// Process bug events we've already seen
var numSeen =
jAllEvents
// Filter out events we haven't seen
.filter(function() {
return /d+/.exec($(this).attr("id")) &= ixLastV
.addClass("seen")
// Process all visible bug events
jAllEvents
.each(function() {
var ev = $(this);
// Create the event summary
var divSummary = $("&div&")
.addClass("small-summary")
"white-space": "nowrap",
overflow: "hidden",
"text-overflow": "ellipsis",
"font-size": "10px",
"margin-bottom": "4px",
"cursor": "pointer"
.insertBefore(this)
.click(function() {
// Clicking anywhere on the summary hides it
// and displays the full bug event
$(this).hide().next(".bugevent").show();
if (ev.hasClass("seen")) divSummary.addClass("seen");
// Convert the bug event into a summary
$.each(summTrans, function(ix, tbl) {
var entry = $("&span&")
.css(tbl.css)
color: tbl.color,
"margin-right": "4px"
.appendTo(divSummary);
if (tbl.fnHtml)
entry.html(tbl.fnHtml(ev));
entry.text(ev.find(tbl.sel).text())
var divMenu = $("&div&")
"margin-bottom": "1em",
border: "1px dotted #888",
padding: "4px",
"background-color": "#f4f4f4"
.insertBefore("#BugEvents");
var spanLabel = $("&span&")
"font-weight": "bold",
"float": "right"
.appendTo(divMenu);
// Set the display mode
var setMode = function(ev, sMode) {
// If this is being used as an event handler,
// get the mode from the link that was clicked
sMode = sMode || $(this).attr("mode");
// Don't underline the link that represents the current display mode
divMenu.find("a")
.css("text-decoration", function() {
return $(this).attr("mode") == sMode ? "none" : "underline";
// Only show the events/summaries allowed by the user's selection
$("#BugEvents").find(".bugevent,.small-summary")
.filter(modes[sMode].show)
// Update the label
spanLabel.html(modes[sMode].label.replace(/#seen/g, numSeen));
// Remember the user's selection
setCookie(sCookie, sMode);
// Add the display modes to the menu
for (var mode in modes) {
.text(modes[mode].text)
.css("margin", ".5em")
.attr({ href: "javascript:void(0)", mode: mode })
.appendTo(divMenu)
.click(setMode);
// Apply the user's saved mode
setMode(null, sSeenMode);
If you’d like to use the code as is, you can use the
compiled version:
$(document).ready(function(){if(!(!window.goBug||!$("#bugviewContainer").length||$("#miniBugList").length)){var e={showAll:{text:"Show All",label:"Showing all events",show:".bugevent"},summarizeAll:{text:"Summarize All",label:"Summarizing all events",show:".small-summary"},summarizeSeen:{text:"Summarize Seen",label:"Summarizing #seen events",show:".bugevent:not(.seen),.small-summary.seen"},hideSeen:{text:"Hide Seen",label:"Hiding #seen events",show:".bugevent:not(.seen)"}},i=getCookie("sSeenEventMode")||
"showAll",j=goBug.ixBugEventLastView,k=[{sel:".action",color:"#000",css:{"font-weight":"bold"}},{sel:".date a",color:"#68615e"},{fnHtml:function(b){return(b.find(".emailBody").html()||b.find(".body").html()||"").replace(/&[^&]*&/g," ")},color:"#888"},{sel:".changes",color:"#aaa"}],c=$("#BugEvents .bugevent"),l=c.filter(function(){return/d+/.exec($(this).attr("id"))&=j}).addClass("seen").c.each(function(){var b=$(this),a=$("&div&").addClass("small-summary").css({"white-space":"nowrap",overflow:"hidden",
"text-overflow":"ellipsis","font-size":"10px","margin-bottom":"4px",cursor:"pointer"}).insertBefore(this).click(function(){$(this).hide().next(".bugevent").show()});b.hasClass("seen")&&a.addClass("seen");$.each(k,function(n,d){var g=$("&span&").css(d.css).css({color:d.color,"margin-right":"4px"}).appendTo(a);d.fnHtml?g.html(d.fnHtml(b)):g.text(b.find(d.sel).text())})});var f=$("&div&").css({"margin-bottom":"1em",border:"1px dotted #888",padding:"4px","background-color":"#f4f4f4"}).insertBefore("#BugEvents"),
m=$("&span&").css({"font-weight":"bold","float":"right"}).appendTo(f);c=function(b,a){a=a||$(this).attr("mode");f.find("a").css("text-decoration",function(){return $(this).attr("mode")==a?"none":"underline"});$("#BugEvents").find(".bugevent,.small-summary").hide().filter(e[a].show).show();m.html(e[a].label.replace(/#seen/g,l));setCookie("sSeenEventMode",a)};for(var h in e)$("&a&").text(e[h].text).css("margin",".5em").attr({href:"javascript:void(0)",mode:h}).appendTo(f).click(c);c(null,i)}});
Originally posted by Daniel LeCheminant.
Here’s a BugMonkey script that one of our engineers, Daniel came up with:
When you install it
You will need to activate it in the More menu.
If you’d like to have it enabled by default, you can simply change the value of fDefaultEnabled from false to true.
You will need to add the 3 CSS classes at the top of the script to your site CSS in BugMonkey.
If you don’t want it to highlight cases due tomorrow or today, you can leave out those classes.
What it does
It adds an item to the More menu on the case list page.
It patches a built-in FogBugz function so that selecting and de-selecting items in the list works properly (instead of losing the highlight when you de-select).
It’s pretty verbose, so if you want, you can strip the comments or shrink it down significantly with
(use the “simple” optimizations to go from 3k to 1k).
Daniel tested it lightly in new versions of Firefox, Chrome, and IE. Let us know if it has any issues.
Highlight Overdue Cases
description:
Highlights overdue cases in the case list
Daniel LeCheminant
// Settings
// Highlight the whole row, or just the Due column
var fWholeRow =
// Text that appears in the "More" menu
var sMenuText = "Toggle Due Date Highlighting";
// Whether the highlighting should be on or off by default
var fDefaultEnabled =
// If we're not looking at a bug list, we don't do anything
if (!$("#bugListContainer").length)
var dueClasses = {
late: "due-late",
today: "due-today",
tomorrow: "due-tomorrow"
var reToday = new RegExp(FB_TODAY, "i");
var reTomorrow = new RegExp(FB_TOMORROW, "i");
// Read out the cookie for the initial state, otherwise use the default
var sCookie = "fHighlightDue";
var sEnabled = getCookie(sCookie);
// Convert the string to a boolean
var fEnabled = sEnabled ? sEnabled === "true" : fDefaultE
var parseDate = (GetLocaleDate() == "dd/mm/yyyy") ?
function(s) {
return Date.parse(s.substr(3,2) + "/" + s.substr(0,2) + "/" + s.substr(6));
function(s) {
return Date.parse(s);
var highlightDue = function(fHighlight) {
// Remove any existing highlighting
for (var dueClass in dueClasses) {
$("#bugListContainer td,tr")
.find("." + dueClasses[dueClass])
.removeClass(dueClasses[dueClass]);
if (fHighlight) {
// Let's find the where the Due column is
var dueClass = /col_[d]+/.exec($("th:has(a[title=" + FB_DUE + "]):first").attr("class"));
// If the column isn't displayed, we can give up
if (!dueClass)
// Only check cells that are in the due column
var dueSel = "#bugListContainer td." + dueClass + ":contains(/)";
// Get the current time
var dtNow = new Date();
// Apply a CSS class that reflects the overdue state of the bug
.each(function() {
var sDue = $(this).text();
var dueClass =
reToday.test(sDue) ? "today" :
reTomorrow.test(sDue) ? "tomorrow" :
parseDate(sDue) & dtNow ? "late" :
if (dueClass)
(fWholeRow ? $(this).parent().find("td") : $(this)).addClass(dueClasses[dueClass]);
// Add a link to the "More" menu
.attr("href", "javascript:void(0)")
.text(sMenuText)
.appendTo("#idFilterOptInnerToolbarActions")
.wrap("&nobr&")
.click(function() {
// Toggle the highlight state, and remem

我要回帖

更多关于 cannotresolvesymbol 的文章

 

随机推荐