// ==UserScript==
// @name TFS Changeset History Helper
// @namespace http://jonas.ninja
// @version 1.4.1
// @description Changeset reference utilities
// @author @_jnblog
// @match http://*/tfs/DefaultCollection/*/_versionControl*
// @grant GM_addStyle
// @downloadURL none
// ==/UserScript==
/* jshint -W097 */
/* global GM_addStyle */
/* jshint asi: true, multistr: true */
var $ = unsafeWindow.jQuery;
var mergedChangesetRegex = /\(merge c\d{5,} to QA\)/gi
waitForKeyElements('.history-result', doEverything, false)
waitForKeyElements(".vc-page-title[title^=Changeset]", addChangesetIdCopyUtilities, true)
$(document).on('mouseenter', '.history-result', highlightHistoryResult)
.on('mouseleave', '.history-result', unhighlightHistoryResult)
$(document).on('dblclick', 'input.ijg-copy-changeset-id', copyMessage)
.on('click', 'input.ijg-copy-changeset-id', copyId)
.on('dblclick', 'input.ijg-copy-changeset-page-link', copyPageMessage)
.on('click', 'input.ijg-copy-changeset-page-link', copyPageId)
function copyId(e) {
if (e.ctrlKey) {
var historyResult = $(this).closest('.history-result')
copy(this, historyResult.data().ijgTaskId)
} else {
displayResult(copy(this), $(this).next('span.ijg-copy-message'), $(this).parent())
}
}
function copyMessage(e) {
var historyResult = $(this).closest('.history-result')
displayResult(copy(this, createCommitMessage(historyResult, this.value)), $(this).next('span.ijg-copy-message'), $(this).parent(), true)
}
function copyPageId(e) {
if (e.ctrlKey) {
var historyResult = $(this).closest('.history-result')
copy(this, $('.vc-change-summary-comment').text().match(/t\d{3,}/gi)[0].replace('t', ''))
} else {
displayResult(copy(this), $(this).next('span.ijg-copy-message'), $(this).parent())
}
}
function copyPageMessage(e) {
displayResult(copy(this, createCommitMessage(".vc-change-summary-comment", this.value)), $(this).next('span.ijg-copy-message'), $(this).parent(), true)
}
function doEverything(historyResult) {
historyResult = $(historyResult)
spanifyText(historyResult)
addCopyUtilities(historyResult)
createTaskContainers(historyResult)
//fetchTaskLinks(historyResult)
}
function createTaskContainers(historyResult) {
// makes a positioned div in the right place to hold Task info
var tasks = historyResult.find('.ijg-task-id')
if (tasks.size()) {
// make a container and append rows
var container = $('
')
historyResult.find('.change-link-container').append(container)
tasks.each(function() {
var tasknum = $(this).data('ijgTaskId')
var task = $('
').data('ijgTaskId', tasknum)
var link = $('
')
.text(tasknum)
.prop('href', 'http://tfs.sqlsentry.com:8080/tfs/DefaultCollection/SQLSentryWebsite/_workitems/edit/' + tasknum)
task.append(link)
container.append(task)
})
}
}
function fetchTaskLinks(historyResult) {
var base = window.location.origin + window.location.pathname.match(/^\/(.*?)\/(.*?)\//)[0]
var urls = {
changesetLinkedWorkItems: '_apis/tfvc/changesets/{}/workItems',
changesetInfo: '_apis/tfvc/changesets/{}',
apiVersion: '?api-version=1.0'
}
}
function spanifyText(historyResult) {
// wraps changeset/task IDs with spans so they can be targeted individually
// adds data to the newly-created spans
historyResult.find('.change-link').each(function() {
// commit messages may have either Tasks or Changesets
$(this).html($(this).text().replace(/[ct]\d{3,}/gi, function(match) {
var id = match.replace(/[ct]/gi, '')
if (match.startsWith('t')) {
historyResult.data('ijgTaskId', id)
return '' + match + ''
}
return '' + match + ''
}))
})
historyResult.find('.change-info').each(function() {
// '.history-result's will only have changesets, and they will not be prefixed with 'c'
$(this).html($(this).text().replace(/\d{3,}/gi, function(match) {
var changesetId = match.replace(/c/i, '')
return '' + match + ''
}))
})
}
function addCopyUtilities(historyResult) {
// adds a text field for each changeset for easy copying of the changeset id
var changesetId = historyResult.find('.change-info').prop('title').match(/^\d{3,6}/)[0]
historyResult.find('.result-details')
.before($(' | '))
}
function addChangesetIdCopyUtilities(pageTitle) {
var $pageTitle = $(pageTitle)
if ($pageTitle.hasClass('added')) {
return
}
$pageTitle.addClass('added')
var id = $pageTitle.text().replace('Changeset ', '')
var $copyLinkInput = $('
').addClass('ijg-copy-changeset-page-link')
var messageSpan = $('
').addClass('ijg-copy-message')
$pageTitle.after(messageSpan).after($copyLinkInput)
messageSpan.hide()
}
function highlightHistoryResult(e) {
var changeset = $(this).data('changeList')
var changesetId = changeset.changesetId
var mainHistoryResult = $('.result-details .change-info .ijg-changeset-id[data-ijg-changeset-id=' + changesetId + ']').closest('.history-result')
var matchingChangesets = $('span.ijg-changeset-id[data-ijg-changeset-id="' + changesetId + '"]')
if (matchingChangesets.size() > 1) {
matchingChangesets.each(function() {
var matchingChangesetId = $(this)
matchingChangesetId.css('color', 'red').closest('.history-result').css('background-color', 'beige')
})
mainHistoryResult.css('background-color', '#D1D1A9')
}
}
function unhighlightHistoryResult(e) {
$('span.ijg-changeset-id').css('color', '').closest('.history-result').css('background-color', '')
}
function displayResult(success, $messageContainer, $cursorContainer, greenCheck) {
if (success) {
displayBriefMessage($messageContainer, "Copied!")
switchCursorBriefly($cursorContainer, greenCheck)
} else {
displayBriefMessage($messageContainer, "FAILED")
return
}
}
function displayBriefMessage($messageContainer, message) {
$messageContainer.show().text(message)
window.setTimeout(function() {
$messageContainer.fadeOut(500)
}, 1250)
}
function switchCursorBriefly($target, greenCheck) {
var $targets = $target.add($target.children())
var cursorClass = 'ijg-check' + (greenCheck ? 'Green' : '')
$targets.addClass(cursorClass)
window.setTimeout(function() {
$targets.removeClass(cursorClass)
}, 1750)
}
/**
If `historyResult` is a jQuery object, expect it to contain changelist data.
If it is a string, expect it to be a selector string that contains the full commit message.
*/
function createCommitMessage(historyResult, changesetId) {
var optMessage
if (typeof historyResult === "string") {
optMessage = $(historyResult).text().split("\n")[0]
} else if (typeof historyResult === "object") {
optMessage = historyResult.data().changeList.comment.split("\n")[0]
} else {
throw "createCommitMessage expects a string or jQuery object, but it received: " + typeof historyResult
}
if (optMessage.match(mergedChangesetRegex)) {
// a changeset that's already merged to QA should merge to Release
optMessage = optMessage.replace(mergedChangesetRegex, '(merge c' + changesetId + ' to Release)')
} else {
optMessage = '(merge c' + changesetId + ' to QA) ' + optMessage
}
return optMessage
}
var styles = '\
img.identity-picture:first-of-type { \
display: none; \
} \
img.identity-picture:only-of-type { \
display: block; \
} \
span.ijg-changeset-id { \
border-bottom: 1px dotted #ccc; \
} \
div > span.ijg-changeset-id { \
cursor: default; \
} \
td.ijg-copy-changeset-id-container { \
width: 52px; \
vertical-align: top; \
padding: 5px 7px 0 0; \
} \
input.ijg-copy-changeset-id { \
cursor: pointer; \
width: 50px; \
text-align: center; \
border: 1px solid #ddd; \
padding: 3px 0; \
margin-bottom: -2px; \
} \
input.ijg-copy-changeset-page-link {\
cursor: pointer;\
text-align: center;\
width: 80px;\
margin: 0 16px;\
border: 1px solid #ccc;\
vertical-align: middle; \
}\
span.ijg-copy-message { \
display: inline-block; \
width: 100%; \
max-width: 60px; \
font-size: .75em; \
text-align: center; \
}\
.change-link-container { \
position: relative;\
display: inline-block; \
}\
.ijg-tasks-container {\
position: absolute; \
top: 0; \
right: 0;\
transform: translateX(100%);\
padding-left: 20px;\
}\
.ijg-check {\
cursor: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAA7klEQVRIS7WUaw0CQQwG5xSAA5CAEyyAAywgAQfgAAeAAyQghXyXLGlgHy132+R+NTvT22470DmGznx6CDbAMxU+p2AJ3AAJzsBekrkEFp6KHyVzCHLwj2SqoAYfJVMETThwtAI1Zws8gHvj+XrgF2CXBILrBeigQi9ATcqFG56u6BueoDlJCJ4EL2BVqNZKwnCPIF3X1QxRqT3jnX8n1QNdkZq6qDRWf7mu5LNw+0w9klDluV30j6RYeWnZRSRNeGmSPRIXvLYqahI3vLWLcpIQvCVQ3kpOwKGxo37Snm2qCdanWQiHRxCG2gPdBW+zDC83C5E8rQAAAABJRU5ErkJggg==), auto !important;\
}\
.ijg-checkGreen {\
cursor: url(data:image/svg+xml;utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTYuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjI0cHgiIGhlaWdodD0iMjRweCIgdmlld0JveD0iMCAwIDQxNS41ODIgNDE1LjU4MiIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNDE1LjU4MiA0MTUuNTgyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxnPgoJPHBhdGggZD0iTTQxMS40Nyw5Ni40MjZsLTQ2LjMxOS00Ni4zMmMtNS40ODItNS40ODItMTQuMzcxLTUuNDgyLTE5Ljg1MywwTDE1Mi4zNDgsMjQzLjA1OGwtODIuMDY2LTgyLjA2NCAgIGMtNS40OC01LjQ4Mi0xNC4zNy01LjQ4Mi0xOS44NTEsMGwtNDYuMzE5LDQ2LjMyYy01LjQ4Miw1LjQ4MS01LjQ4MiwxNC4zNywwLDE5Ljg1MmwxMzguMzExLDEzOC4zMSAgIGMyLjc0MSwyLjc0Miw2LjMzNCw0LjExMiw5LjkyNiw0LjExMmMzLjU5MywwLDcuMTg2LTEuMzcsOS45MjYtNC4xMTJMNDExLjQ3LDExNi4yNzdjMi42MzMtMi42MzIsNC4xMTEtNi4yMDMsNC4xMTEtOS45MjUgICBDNDE1LjU4MiwxMDIuNjI4LDQxNC4xMDMsOTkuMDU5LDQxMS40Nyw5Ni40MjZ6IiBmaWxsPSIjMmQ5ZTFlIi8+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPC9zdmc+Cg==), auto !important;\
}'
GM_addStyle(styles)
function copy(elToCopy, optMessage) {
var $fakeElem = $('