Last active
January 15, 2019 20:46
-
-
Save curtisj44/9491644 to your computer and use it in GitHub Desktop.
User Script: Trello
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @description Make Trello more awesome | |
// @downloadURL https://gist.github.com/curtisj44/9491644 | |
// @grant none | |
// @include https://trello.com/* | |
// @name Trello | |
// @namespace trello | |
// @require https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js | |
// @version 2.3.0 | |
// ==/UserScript== | |
(function () { | |
'use strict'; | |
var | |
$lists, | |
$listHeaders, | |
addBoardTotal = function () { | |
var total = 0; | |
$.each($listHeaders, function (index, value) { | |
var $text = $(value).clone().children().remove().end().text(); | |
if ($text !== '') { | |
total += parseInt($text.replace(' cards', '').replace(' card', ''), 10); | |
} | |
}); | |
$('.board-header') | |
.find('b').remove().end() | |
.append('<b class="board-header-btn" style="padding: 0 10px; background: rgba(0, 0, 0, .12);">' + total + ' cards</b>'); | |
}, | |
addCardTotal = function () { | |
$listHeaders.removeClass('hide').css('display', 'block'); | |
}, | |
addCustomStyles = function () { | |
// Hide options I never use | |
const attachments = `.js-google-drive-attachment, | |
.js-dropbox-attachment, | |
.js-box-attachment, | |
.js-one-drive-attachment, | |
.js-add-attachment-url + hr, | |
.js-add-attachment-url + hr + .quiet`; | |
const shareButton = 'a.button-link[title="Share"]'; | |
const sidebarHorizontalRules = '.window-module hr'; | |
const watchButton = 'a.button-link[title^="Watch"]'; | |
$('body').append( | |
`<style> | |
${ attachments }, | |
${ shareButton }, | |
${ sidebarHorizontalRules }, | |
${ watchButton } { | |
display: none !important; | |
} | |
</style>` | |
); | |
}, | |
// Calculates total sprint capacity based on one of the follow options based on | |
// card title and/or a custom field | |
// | |
// Options | |
// A: "Title of card [n]", where `n` is any number | |
// B: "Title of card [XS]", where `XS` is one of the `sizes` values defined below | |
// C: "Estimate" custom field using the `sizes` values defined below | |
// D: "Estimate (in days)" custom field using any number | |
addEstimateTotal = function ($list, $cards) { | |
var | |
capacity = 0, | |
estimateDetails, | |
missingEstimates = 0, | |
re = /\[(.*?)\]/g, | |
sizes = { | |
'XXS': '.25', | |
'XS': '.5', | |
'S': '1', | |
'M': '3', | |
'L': '5', | |
'XL': '7', | |
'XXL': '10' | |
}, | |
total = 0, | |
unit = 'hours', | |
flagCard = function (listName, $card) { | |
var | |
listsToInclude = listName.indexOf('Backlog') > -1 || | |
listName.indexOf('Benched') > -1 || | |
listName.indexOf('BLOCKED') > -1 || | |
listName.indexOf('Done') > -1 || | |
listName.indexOf('On Deck') > -1 || | |
listName.indexOf('On Hold') > -1 || | |
listName.indexOf('Pull Requested') > -1 || | |
listName.indexOf('Release') > -1 || | |
listName.indexOf('Sprint') > -1 || | |
listName.indexOf('To Design') > -1 || | |
listName.indexOf('To Develop') > -1 || | |
listName.indexOf('Working On') > -1; | |
if (listsToInclude) { | |
missingEstimates++; | |
$card.css('background', 'rgba(230, 200, 200, 1)'); | |
} | |
}, | |
unFlagCard = function ($card) { | |
$card.removeAttr('style'); | |
}, | |
isSizeEstimate = function (estimate) { | |
return (sizes[estimate] > 0); | |
}; | |
$cards.each(function (index, value) { | |
var | |
$card = $(value), | |
bracketIndex = 0, | |
estimate = 0, | |
listName = getListName($list), | |
cardTitle = getCardTitle($card); | |
unFlagCard($card); | |
if (cardTitle.indexOf('[') > -1) { | |
if (cardTitle.indexOf('[') !== cardTitle.lastIndexOf('[')) { | |
bracketIndex = 1; | |
} | |
estimate = cardTitle.match(re)[bracketIndex].replace('[', '').replace(']', ''); | |
if ($.isNumeric(estimate)) { | |
if (cardTitle.indexOf('Sprint Planning') > -1) { | |
// Capture the capacity size from the "Sprint Planning" card | |
// Requires this card title format to determine the capacity: `Sprint Planning [x]`, where `x` is the number of days | |
capacity = estimate; | |
$card.css('background', 'rgba(200, 200, 200, 1)'); | |
} else { | |
// capture the hourly estimates from all of the other cards | |
total = ((total * 100000) + (estimate * 100000)) / 100000; | |
} | |
// "[?]" cards | |
} else if (estimate.indexOf('?') > -1) { | |
flagCard(listName, $card); | |
// "[RELEASE]" cards | |
} else if (estimate.indexOf('RELEASE') > -1) { | |
$card.css('background', 'rgba(200, 208, 200, 1)'); | |
// t-shirt size estimates | |
} else if (isSizeEstimate(estimate)) { | |
unit = 'days'; | |
total = ((total * 100000) + (sizes[estimate] * 100000)) / 100000; | |
// missing estimates | |
} else { | |
flagCard(listName, $card); | |
} | |
} else { | |
// Check for "Estimate" custom field | |
var | |
$badgeText = $card.find('.badge-text'), | |
hasEstimateField = false; | |
$badgeText.each(function (index, value) { | |
var | |
badgeText = $(value).text(), | |
isEstimateField = badgeText.indexOf('Estimate:') > -1, | |
isEstimateInDaysField = badgeText.indexOf('Estimate (in days):') > -1; | |
if (isEstimateField) { | |
hasEstimateField = true; | |
estimate = badgeText.replace('Estimate: ', ''); | |
unit = 'days'; | |
total = ((total * 100000) + (sizes[estimate] * 100000)) / 100000; | |
} else if (isEstimateInDaysField) { | |
hasEstimateField = true; | |
estimate = badgeText.replace('Estimate (in days): ', ''); | |
unit = 'days'; | |
total = total + parseInt(estimate, 10); | |
} | |
}); | |
// missing estimate | |
if (!hasEstimateField) { | |
flagCard(listName, $card); | |
} | |
} | |
}); | |
$list.find('.list-header-num-cards span').remove(); | |
// add missing estimate count | |
if (missingEstimates > 0) { | |
missingEstimates = '<span style="float: right; margin: 0 -26px 0 36px; padding: 0 .6em; border-radius: 3px; background: rgba(230, 200, 200, 1); color: #4d4d4d; font-weight: bold; font-size: 10px;">' + | |
missingEstimates + | |
'</span>'; | |
$list.find('.list-header-num-cards').append(missingEstimates); | |
} | |
// add estimate count | |
if (total > 0) { | |
estimateDetails = total; | |
if (capacity) { | |
estimateDetails += ' / ' + capacity; | |
} | |
estimateDetails = '<span style="float: right; margin-right: -26px;">' + | |
estimateDetails + ' ' + unit + | |
'</span>'; | |
$list.find('.list-header-num-cards').append(estimateDetails); | |
} | |
}, | |
getCardTitle = function ($card) { | |
return $card.find('.list-card-title').text(); | |
}, | |
getListName = function ($list) { | |
return $list.find('.list-header-name').text(); | |
}, | |
highlightLists = function ($list, $cards) { | |
var listName = getListName($list); | |
// release lists | |
if (listName.indexOf('2015-') > -1 || listName.indexOf('2016-') > -1) { | |
$list.css('background', 'rgba(200, 230, 200, 1)'); | |
} | |
// "Working On" list | |
if (listName.indexOf('Working On') > -1) { | |
$list.css('background', 'rgba(240, 230, 200, 1)'); | |
} | |
}, | |
init = function () { | |
if ($('.list').length > 0) { | |
watchLists(); | |
} else { | |
window.setTimeout(init, 600); | |
} | |
addCustomStyles(); | |
}, | |
modifyCards = function ($lists) { | |
$lists | |
.css('flex', '0 0 380px') | |
.find('.list-card').css('max-width', 'none'); | |
}, | |
modifyLists = function () { | |
// console.log('modifyLists'); | |
$lists = $('.list'); | |
$listHeaders = $('.list-header-num-cards'); | |
addCardTotal(); | |
$lists.each(function (index, value) { | |
var | |
$list = $(value), | |
$cards = $list.find('.list-card'); | |
addEstimateTotal($list, $cards); | |
highlightLists($list, $cards); | |
$list.find('.list-header-name').each(function (index1, value1) { | |
if ($(value1).text() === 'To Do') { | |
modifyCards($lists); | |
} | |
}); | |
}); | |
}, | |
watchLists = function () { | |
// console.log('watchLists'); | |
// TODO: this doesn't work anymore :( | |
// $(document).ajaxComplete(function (event, request, settings) { | |
// // console.log(event); | |
// // console.log(request); | |
// // console.log(settings); | |
// modifyLists(); | |
// addBoardTotal(); | |
// }); | |
var observer = new MutationObserver(function (mutationsList) { | |
for (var mutation of mutationsList) { | |
if (mutation.type == 'childList') { | |
console.log('A child node has been added or removed.'); | |
modifyLists(); | |
addBoardTotal(); | |
} else if (mutation.type == 'attributes') { | |
console.log('The ' + mutation.attributeName + ' attribute was modified.'); | |
} | |
} | |
}); | |
observer.observe( | |
document.getElementById('board'), | |
{ | |
// attributes: true, | |
childList: true, | |
subtree: true | |
} | |
); | |
}; | |
$(init); | |
}()); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment