您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Changeset reference utilities
当前为
// ==UserScript== // @name TFS Changeset History Helper // @namespace http://jonas.ninja // @version 1.5.0 // @description Changeset reference utilities // @author @_jnblog // @match http://*/tfs/DefaultCollection/*/_versionControl* // @grant GM_addStyle // @grant GM_setClipboard // ==/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('click', 'input.ijg-copy-changeset-id', copy) .on('click', 'input.ijg-copy-changeset-id', copy) .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 { copy(this) displayResult($(this).parent()) } } function copyMessage(e) { var historyResult = $(this).closest('.history-result') copy(this, createCommitMessage(historyResult, this.value)) displayResult($(this).parent()) } 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 { copy(this) displayResult($(this).parent()) } } function copyPageMessage(e) { copy(this, createCommitMessage(".vc-change-summary-comment", this.value)) displayResult($(this).parent()) } function copy(e) { // !!! new function $target = $(this) GM_setClipboard($target.data('ijgCopyText')) displayResult($target) } 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 = $('<div class="ijg-tasks-container">') historyResult.find('.change-link-container').append(container) tasks.each(function() { var tasknum = $(this).data('ijgTaskId') var task = $('<div class=ijg-task-link>').data('ijgTaskId', tasknum) var link = $('<a target="_blank">') .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 '<span class="ijg-task-id" data-ijg-task-id="' + id + '">' + match + '</span>' } return '<span class="ijg-changeset-id" data-ijg-changeset-id="' + id + '">' + match + '</span>' })) }) 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 '<span class="ijg-changeset-id" data-ijg-changeset-id="' + changesetId + '">' + match + '</span>' })) }) } function addCopyUtilities(historyResult) { var $container = $('<td class="ijg-copyButtons"><div class="ijg-copyButtonsWidthHack"></div></div>') var changesetId = historyResult.find('.change-info').prop('title').match(/^\d{3,6}/)[0] var url = historyResult.find('a.change-link').prop('href') var formattedUrl = '[' + historyResult.find('a.change-link').text() + '](' + url + ')' var message = createCommitMessage(historyResult, changesetId) var button = $('<button class="ijg-copyButton">') $container.find('div') .append(button.clone().text('ID') .addClass('ijg-js-copyButton').data('ijgCopyText', changesetId)) .append(button.clone().text('Link') .addClass('ijg-js-copyButton').data('ijgCopyText', formattedUrl)) .append(button.clone().text('Merge Message').addClass('ijg-js-copyButton').data('ijgCopyText', message)) historyResult.find('.result-details').before($container) } function addChangesetIdCopyUtilities(pageTitle) { var $pageTitle = $(pageTitle) if ($pageTitle.hasClass('added')) { return } $pageTitle.addClass('added') var id = $pageTitle.text().replace('Changeset ', '') var $copyLinkInput = $('<input value="' + id + '">').addClass('ijg-copy-changeset-page-link') $pageTitle.after($copyLinkInput) } 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($cursorContainer) { var cursorClass = 'ijg-check' $cursorContainer.addClass(cursorClass) window.setTimeout(function() { $cursorContainer.removeClass(cursorClass) }, 1750) setGreenCheckCursor() } /** 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 } ;(function addStyles () { var styles = '\ img.identity-picture:first-of-type { \ display: none; \ } \ img.identity-picture:only-of-type { \ display: block; \ } \ tr.history-result > * { \ padding-top: 3px;\ }\ .result-details { \ padding-left: 246px; \ } \ span.ijg-changeset-id { \ border-bottom: 1px dotted #ccc; \ } \ div > span.ijg-changeset-id { \ cursor: default; \ } \ td.ijg-copyButtons { \ width: 1px; \ vertical-align: top; \ padding: 8px 7px 0 0 !important; \ } \ .ijg-copyButtonsWidthHack { \ width: 280px; \ } \ 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; \ }\ .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/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;\ }\ button.ijg-copyButton {\ margin-left: 8px;\ }' var verticalCompactionStyles = '\ .vc-history-list .results-container .history-result .picture {\ height: 40px;\ }' GM_addStyle(styles) GM_addStyle(verticalCompactionStyles) })() function waitForKeyElements( // CC BY-NC-SA 4.0. Author: BrockA selectorTxt, /* Required: The jQuery selector string that specifies the desired element(s). */ actionFunction, /* Required: The code to run when elements are found. It is passed a jNode to the matched element. */ bWaitOnce, /* Optional: If false, will continue to scan for new elements even after the first match is found. */ iframeSelector /* Optional: If set, identifies the iframe to search. */ ) { var targetNodes, btargetsFound; if (typeof iframeSelector == "undefined") targetNodes = $(selectorTxt); else targetNodes = $(iframeSelector).contents() .find(selectorTxt); if (targetNodes && targetNodes.length > 0) { btargetsFound = true; /*--- Found target node(s). Go through each and act if they are new. */ targetNodes.each(function() { var jThis = $(this); var alreadyFound = jThis.data('alreadyFound') || false; if (!alreadyFound) { //--- Call the payload function. var cancelFound = actionFunction(jThis); if (cancelFound) btargetsFound = false; else jThis.data('alreadyFound', true); } }); } else { btargetsFound = false; } //--- Get the timer-control variable for this selector. var controlObj = waitForKeyElements.controlObj || {}; var controlKey = selectorTxt.replace(/[^\w]/g, "_"); var timeControl = controlObj[controlKey]; //--- Now set or clear the timer as appropriate. if (btargetsFound && bWaitOnce && timeControl) { //--- The only condition where we need to clear the timer. clearInterval(timeControl); delete controlObj[controlKey] } else { //--- Set a timer, if needed. if (!timeControl) { timeControl = setInterval(function() { waitForKeyElements(selectorTxt, actionFunction, bWaitOnce, iframeSelector ); }, 300 ); controlObj[controlKey] = timeControl; } } waitForKeyElements.controlObj = controlObj; } function setGreenCheckCursor() { /// from https://bugs.chromium.org/p/chromium/issues/detail?id=26723#c87 if (document.body.style.cursor != cursorUrl) { var wkch = document.createElement("div"); wkch.style.overflow = "hidden"; wkch.style.position = "absolute"; wkch.style.left = "0px"; wkch.style.top = "0px"; wkch.style.width = "100%"; wkch.style.height = "100%"; var wkch2 = document.createElement("div"); wkch2.style.width = "200%"; wkch2.style.height = "200%"; wkch.appendChild(wkch2); document.body.appendChild(wkch); document.body.style.cursor = cursorUrl; wkch.scrollLeft = 1; wkch.scrollLeft = 0; document.body.removeChild(wkch); } }
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址