Greasy Fork

Greasy Fork is available in English.

TFS 2017 Helper

Adds handy functionality to TFS 2017

当前为 2016-11-18 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         TFS 2017 Helper
// @namespace    http://jonas.ninja
// @version      1.10.0
// @description  Adds handy functionality to TFS 2017
// @author       @_jnblog
// @match        http://*/tfs/DefaultCollection/*/_backlogs*
// @match        http://*/tfs/DefaultCollection/*/_versionControl*
// @match        http://*/tfs/DefaultCollection/*/_workitems*
// @grant        GM_addStyle
// @grant        GM_setClipboard
// ==/UserScript==
/* jshint -W097 */
/* global GM_addStyle */
/* jshint asi: true, multistr: true */

var $ = unsafeWindow.jQuery;
var topClass = "makeTfsNotAwful"
var cursorUrl = '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'
var colorMap = {'rgb(0, 156, 204)'  : 'pbi',
                'rgb(204, 41, 61)'  : 'bug',
                'rgb(242, 203, 29)' : 'task',
                'rgb(119, 59, 147)' : 'feature'}
var templates = {
  button: $('<button class="ijg-copyButton">')
}
$('body').addClass(topClass)

waitForKeyElements(".workitem-dialog", changeDialogBorderColor, false)
waitForKeyElements(".workitem-info-bar > .info-text-wrapper", addCopyUtilities, false)


$(document).on('click', '.ijg-js-copyButton', copy)


function changeDialogBorderColor (workitemDialog) {
  // depending on the type of this work item, color the border differently
  var dialog = $(workitemDialog)
  var borderColor = dialog.find('.work-item-form-main-header').css('border-left-color')
  var itemType = colorMap[borderColor]

  // retry if necessary.
  if (borderColor === undefined) {
    window.setTimeout(function() {
      changeDialogBorderColor(workitemDialog)
    }, 100)
  }

  if (itemType === 'pbi') {
    dialog.css({'border-color': borderColor,
                'box-shadow'  : '#91c3d2 0 0 30px 8px'})
  } else if (itemType === 'bug') {
    dialog.css({'border-color': borderColor,
                'box-shadow':   '#a15d5d 0 0 30px 8px'})
  } else if (itemType === 'feature') {
    dialog.css({'border-color': borderColor,
               'box-shadow':    '#ac80ac 0 0 30px 8px'})
  } else if (itemType === 'task') {
    dialog.css({'border-color': borderColor,
                'box-shadow'  : '#ddd3ae 0 0 30px 8px'})
  }
}

function addCopyUtilities () {
  $('.workitem-info-bar').find('.info-text-wrapper').each(function (idx, header) {
    var $header = $(header)
    if ($header.hasClass('ijg-isProcessed')) {
      return
    }
    $header.addClass('ijg-isProcessed')

    var itemLink = $header.find('a.caption')
    var formattedItemLinkText = itemLink.text().replace(/^Product Backlog Item/i, 'PBI')
    var id = itemLink.text().match(/\d+/)[0]
    var url = itemLink.prop('href')
    var text = $header.closest('.ui-dialog-content').find('.work-item-form-title input').val()
    var formattedUrl = '*' + formattedItemLinkText + ': ' + text + '*\n' + url
    var message = text.replace(/^dev: */i, "")
    var $container = $('<div class="ijg-copyButtons">')

    $container
      .append(makeCopyButton('Link',           'ijg-js-copyButton ijg-copyButton--compact', formattedUrl))
      .append(makeCopyButton('ID',             'ijg-js-copyButton ijg-copyButton--compact', id))
      .append(makeCopyButton('Commit Message', 'ijg-js-copyButton ijg-copyButton--compact', message))

    $header.append($container)
  })
}
function makeCopyButton (text, classes, copyData) {
  return templates.button.clone()
    .text(text).addClass(classes).data('ijgCopyText', copyData)
}
function copy (e) {
  $target = $(this)
  GM_setClipboard($target.data('ijgCopyText'))
  displayResult($target)
}
function displayResult ($button) {
  var cursorClass = 'ijg-check'
  var highlightClass = 'isHighlighted'

  $button.addClass(cursorClass).addClass(highlightClass)
  setGreenCheckCursor()

  window.setTimeout(function () {
    $button.removeClass(highlightClass)
  }, 50)
  window.setTimeout(function () {
    $button.removeClass(cursorClass)
  }, 1500)
}


;(function addStyles () {
  var modalStyle = '.workitem-dialog { \
    left: 10px !important;\
    top: 10px !important;\
    width: calc(100% - 20px) !important;\
    height: calc(100% - 20px) !important;\
    border: 4px solid grey;\
    box-shadow: gray 0 0 30px 8px;\
    box-sizing: border-box;\
  }'
  var dialogStyles = '\
  .ijg-linksPane-createTasksContainer {\
    position: absolute;\
    top: 10px;\
    left: 50%;\
    transform: translateX(-50%);\
  }'
  var innerModalStyle = '.work-item-view {\
    width: calc(100% - 20px);\
    margin: 0 10px;\
    top: 68px;\
  }'
  var uiDialogContentStyle = '.ui-dialog-content:not(.modal-dialog) {height: calc(100% - 51px) !important}'
  var otherStyles = '.' + topClass + ' table.witform-layout {\
    width: calc(100% - 4px);\
  }\
  .linksPanel {\
    display: block !important;\
    margin-bottom: 5px;\
    position: relative;\
  }\
  .linksPanelHeader {\
    background-color: #e6e6e6;\
    font-size: 11px;\
    text-transform: uppercase;\
    margin: 0;\
    padding: 0 4px 0;\
    border: 0;\
    white-space: nowrap;\
    height: 25px;\
    line-height: 2.1;\
  }\
  .tbTile {\
    width: 100%;\
    margin: 0px 0px 3px;\
  }\
  .subColumn { \
    width: calc(50% - 5px); \
    margin-right: 5px; \
  }\
  .linksPanel .grid-cell:not(:only-child) {\
    text-indent: 0 !important;\
  }\
  .linksPanel .grid-row.ijg-is-done {\
    background-color: #e9fce8;\
    color: #646464;\
  }\
  button {\
    transition: box-shadow 100ms;\
  }\
  button:focus {\
    background-color: #f8f8f8;\
    box-shadow: 0px 0px 0px 3px rgba(128, 128, 128, 0.4);\
  }\
  button:hover {\
    background-color: #fefefe;\
  }\
  button:active {\
    background-color: #e6e6e6;\
  }\
  button.changeset-identifier {\
    vertical-align: top;\
    line-height: 0;\
    padding: 0px 12px;\
    height: 22px;\
    margin-left: 8px;\
  }\
  .agile-content-container div.board-tile.ui-draggable,\
  #taskboard-table-body .ui-draggable {\
    transition: box-shadow 250ms;\
  }\
  .agile-content-container div.board-tile.ui-draggable:focus,\
  #taskboard-table-body .ui-draggable:focus {\
    box-shadow: 0px 0px 8px 2px rgb(25, 22, 6);\
    transition-delay: 50ms;\
    outline: none;\
  }\
  .witform-layout > tbody > tr.group {\
    width: calc(100% - 4px);\
  }\
  .link-dialog-form .text {\
    text-shadow: 0px 0px 6px black;\
    height: auto;\
  }\
  .workitem-control-maximized-dialog {\
    height: calc(100% - 20px) !important;\
    width: calc(100% - 20px) !important;\
  }\
  .taskboard-parent {\
    min-width: 154px;\
    width: 154px;\
  }\
  .taskboardTableHeaderScrollContainer .taskboard-parent {\
    min-width: 164px;\
  }\
  .ijg-check {\
    cursor: ' + cursorUrl + ';\
  }\
  .workitem-info-bar .info-text-wrapper{\
    overflow: visible !important;\
  }\
  .workitem-header-bar {\
    overflow: visible !important;\
    z-index: 1;\
  }\
  .workitem-dialog .ui-dialog-titlebar-progress-container {\
    margin: 0 !important;\
  }\
  .workitem-dialog .ui-resizable-handle {\
    display: none !important;\
  }\
\
  .ui-dialog .full-screen-button,\
  .ui-dialog .ui-dialog-titlebar-close{\
    transition: background-color 200ms;\
  }\
  .ui-dialog .full-screen-button:hover {\
    background-color: #dcebfc;\
  }\
  .ui-dialog .ui-dialog-titlebar-close {\
    height: 30px !important;\
  }\
  .ui-dialog .ui-dialog-titlebar-close:hover {\
    background-color: rgba(232, 129, 129, 0.5) !important;\
  }\
\
  .ijg-copyButtons {\
    display: inline-block;\
    vertical-align: top;\
    font-size: 14px;\
    margin-top: -2px;\
    margin-bottom: -5px;\
  }\
  button.ijg-copyButton {\
    margin-left: 16px;\
    transition: box-shadow 100ms, background-color 250ms 100ms linear;\
  }\
  button.ijg-copyButton.ijg-copyButton--compact {\
    height: 25px;\
    padding-top: 1px;\
  }\
  .ijg-copyButton.isHighlighted {\
    transition-delay: 0s;\
    transition-duration: 0s;\
    background-color: rgba(160, 232, 151, 0.6);\
  }'

  GM_addStyle("." + topClass + " " + modalStyle)
  GM_addStyle("." + topClass + " " + innerModalStyle)
  GM_addStyle("." + topClass + " " + uiDialogContentStyle)
  GM_addStyle(otherStyles + dialogStyles)
})()




function waitForKeyElements (
  // CC BY-NC-SA 4.0. Author: BrockA
  selectorTxt,
  actionFunction,
  bWaitOnce
) {
  var targetNodes, btargetsFound;

  targetNodes = $(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
          );
        },
        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);
  }
}