您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Changeset reference utilities
当前为
// ==UserScript== // @name TFS Changeset History Helper // @namespace http://jonas.ninja // @version 1.1.5 // @description Changeset reference utilities // @author @_jnblog // @match http://*/tfs/DefaultCollection/*/_versionControl* // @require https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js // @grant GM_addStyle // ==/UserScript== /* jshint -W097 */ /* global $ */ /* jshint asi: true, multistr: true */ 'use strict'; waitForKeyElements('.history-result', doEverything, false) waitForKeyElements(".vc-page-title[title^=Changeset]", addChangesetIdCopyUtilities, true) $(document).on('mouseenter', '.history-result, .ijg-changeset-id', highlightHistoryResult) .on('mouseleave', '.history-result, .ijg-changeset-id', unhighlightHistoryResult) $(document).on('click', 'input.ijg-copy-changeset-id', function clickToCopy(e) { var historyResult = $(this).closest('.history-result') var mergedChangesetRegex = / \(merge c\d{5,} to QA\)/gi if (e.ctrlKey) { // if CTRL is held down, copy first the task ID, then a commit message. Only useful if you have a clipboard manager like Ditto. var taskId = historyResult.data('ijgTaskId') var that = this copy(this, $(this).next('span.ijg-copy-message'), taskId) var optMessage = historyResult.find('.change-link').text() if (optMessage.match(mergedChangesetRegex)) { // a changeset that's already merged to QA should merge to Release optMessage = optMessage.replace(mergedChangesetRegex, ' (merge c' + that.value + ' to Release)') } else { optMessage += ' (merge c' + that.value + ' to QA)' } window.setTimeout(function() { copy(that, $(that).next('span.ijg-copy-message'), optMessage) }, 250) } else { copy(this, $(this).next('span.ijg-copy-message')) } }) $(document).on('click', 'input.ijg-copy-changeset-page-link', function clickToCopy(e) { // if shift is held down, copy a commit message var optMessage if (e.ctrlKey) { optMessage = $('.vc-change-summary-comment').text() + ' (merge c' + this.value + ' to QA)' } copy(this, $(this).next('span.ijg-copy-message'), optMessage) }) 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) { // 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($('<td class=ijg-copy-changeset-id-container><input type=text class=ijg-copy-changeset-id data-ijg-changeset-id="' + changesetId + '" value="' + changesetId + '"><span class="ijg-copy-message"></td>')) } 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($('<span>').addClass('copy-message')).after($copyLinkInput) } function highlightHistoryResult(e) { var changesetId = $(this).data('ijgChangesetId') changesetId = changesetId || $(this).find('.ijg-copy-changeset-id').data('ijgChangesetId') 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', '') } 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; \ } \ 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 { \ font-size: .75em; \ display: block; \ 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;\ }' GM_addStyle(styles) function copy(elToCopy, $messageContainer, optMessage) { var $fakeElem = $('<textarea>'); var succeeded var message = optMessage || elToCopy.value $fakeElem .css({ position: 'absolute', left: '-9999px', top: (window.pageYOffset || document.documentElement.scrollTop) + 'px' }) .attr('readonly', '') .val(message) .appendTo(document.body) select($fakeElem[0]) try { succeeded = document.execCommand('copy') } catch (err) { succeeded = false; select(elToCopy) } if (succeeded) { $messageContainer.text('Copied!') $(elToCopy).css('cursor', 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAA7klEQVRIS7WUaw0CQQwG5xSAA5CAEyyAAywgAQfgAAeAAyQghXyXLGlgHy132+R+NTvT22470DmGznx6CDbAMxU+p2AJ3AAJzsBekrkEFp6KHyVzCHLwj2SqoAYfJVMETThwtAI1Zws8gHvj+XrgF2CXBILrBeigQi9ATcqFG56u6BueoDlJCJ4EL2BVqNZKwnCPIF3X1QxRqT3jnX8n1QNdkZq6qDRWf7mu5LNw+0w9klDluV30j6RYeWnZRSRNeGmSPRIXvLYqahI3vLWLcpIQvCVQ3kpOwKGxo37Snm2qCdanWQiHRxCG2gPdBW+zDC83C5E8rQAAAABJRU5ErkJggg==), auto') } else { $messageContainer.text('Press Ctrl+C to copy!') $(elToCopy).css('cursor', 'text') } $fakeElem.remove() $messageContainer.show() window.setTimeout(function() { $messageContainer.fadeOut(500) if (succeeded) { $(elToCopy).css('cursor', 'pointer') } }, 1200) } function select(element) { // MIT licensed. Author: @zenorocha var selectedText; if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') { element.focus(); element.setSelectionRange(0, element.value.length); selectedText = element.value; } else { if (element.hasAttribute('contenteditable')) { element.focus(); } var selection = window.getSelection(); var range = document.createRange(); range.selectNodeContents(element); selection.removeAllRanges(); selection.addRange(range); selectedText = selection.toString(); } return selectedText; } 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; }
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址