TFS Helper

Adds styles and moves things around so that oft-used functions are easier

目前為 2016-09-14 提交的版本,檢視 最新版本

// ==UserScript==
// @name         TFS Helper
// @namespace    http://jonas.ninja
// @version      1.8.0
// @description  Adds styles and moves things around so that oft-used functions are easier
// @author       @_jnblog
// @match        http://*/tfs/DefaultCollection/*/_backlogs*
// @match        http://*/tfs/DefaultCollection/*/_versionControl*
// @match        http://*/tfs/DefaultCollection/*/_workitems*
// @grant        GM_addStyle
// ==/UserScript==
/* jshint -W097 */
/* global GM_addStyle */
/* jshint asi: true, multistr: true */

var $ = unsafeWindow.jQuery;
var topClass = "makeTfsNotAwful"
$('body').addClass(topClass)

waitForKeyElements("div.tab-page[rawtitle=Links]", doEverything, false)

$(document).on('click',    'input.task-identifier', copyId)
           .on('dblclick', 'input.task-identifier', copyMessage)

function doEverything(linksPane) {
  if ($(linksPane).data('moved')) {
    return
  }
  showLinksPane(linksPane)
  stackAllTabs($(linksPane))
  addBlockedButton(linksPane)
  window.setTimeout(function() {
    addTaskIdCopyUtilities(linksPane)
    changeDialogBorderColor(linksPane)
  }, 250)
  refreshNowAndLater();
}

function showLinksPane(linksPane) {
  $(linksPane).data('moved', true)
    .addClass('linksPanel')
    .prepend($("<h3>").addClass('linksPanelHeader')
      .text($(linksPane).attr('rawtitle')))

  var link = $('a[rawtitle=Links]')
  link.closest('td').parent().closest('td').prev().css({'width': '35%' /*, 'padding-right': '12px'*/})
  link.closest('td').prepend(linksPane)
  link.parent().remove()
}

function changeDialogBorderColor(linksPane) {
  // depending on the type of this work item, color the border differently
  var dialog = $(linksPane).closest('.workitem-dialog')
  var caption = dialog.find('a.caption').text()

  if (caption.indexOf('Product Backlog Item ') !== -1) {
    dialog.css('border-color', '#009CCC') // blue
  } else if (caption.indexOf('Bug ') !== -1) {
    dialog.css('border-color', '#CC293D') // red
  } else if (caption.indexOf('Feature ') !== -1) {
    dialog.css('border-color', '#773B93') // purple
  } else { // Task
    dialog.css('border-color', '#E0C252') // yellow
  }
}

function stackAllTabs($linksPane) {
  var column2 = $linksPane.closest('table.content').parent('.column')
  var column1 = column2.prev()
  var modalRect = $linksPane.closest('.work-item-view')[0].getBoundingClientRect()
  var editorContainer = column1.find('.richeditor-editarea')
  if (Math.max(window.innerWidth, document.documentElement.clientWidth) >= 1600) {
    // not stackable
    var editorRect = editorContainer[0].getBoundingClientRect()
    var newheight = modalRect.bottom - editorRect.bottom + editorRect.height - 10
    var col2height = column2.height() - 52
    editorContainer.css('min-height', Math.max(newheight, col2height))
      .children('iframe').css('height', Math.max(newheight, col2height))
  } else {
    // stackable
    column2.add(column1).css({width: '100%', display: 'block'})
    var contentHeight = $linksPane.closest('.work-item-view').children('.witform-layout')[0].getBoundingClientRect().bottom
    var newHeight = editorContainer.height() + modalRect.bottom - contentHeight
    editorContainer.css('min-height', Math.max(editorContainer.height(), newHeight))
      .children('iframe').css('height', Math.max(editorContainer.height(), newHeight))
  }
}

function addTaskIdCopyUtilities(linksPane) {
  $('.workitem-info-bar').find('.info-text-wrapper').each(function() {
    var $header = $(this)
    if ($header.hasClass('added')) {
      return
    }
    $header.addClass('added')

    var id = $header.find('a.caption').text().match(/\d+/)[0]
    var $container = $('<div>').css('display', 'inline-block')
    $container.append($('<input value="' + id + '"/>').addClass('task-identifier'))
    $container.append($('<span>').addClass('copy-message'))
    $header.find('span.info-text').after($container)
  });
}

function addBlockedButton(linksPane) {
  var blockedInput = linksPane.find('[aria-label=Blocked]');
  blockedInput.closest('.control-cell').css({width: 'calc(100% - 60px)', 'min-width': '50px'})
}



function copyId(e) {
  displayResult(copy(this), $(this).next('span.copy-message'), $(this).parent())
}
function copyMessage(e) {
  var optMessage = makeMessage(this)
  displayResult(copy(this, optMessage), $(this).next('span.copy-message'), $(this).parent(), true)
}

function copy(elToCopy, 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;
  }

  $fakeElem.remove()
  return succeeded;
}

function makeMessage(el) {
  var parent = $(el).closest('.info-text-wrapper')
  if (parent.length === 0) {
    console.error("The userscript _TFS Helper_ could not identify this task's name")
  }

  return 't' + parent.children('a.caption').text().match(/\d{5,}/)[0] + ' ' + parent.children('.info-text').text().replace(/^dev: /i, "")
}

function displayResult(success, $messageContainer, $cursorContainer, greenCheck) {
  if (success) {
    displayBriefMessage($messageContainer, "Copied" + (greenCheck ? '!' : ''))
    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)
}
function refreshNowAndLater() {
  window.dispatchEvent(new Event('resize'));
  window.setTimeout(function() {
    window.dispatchEvent(new Event('resize'));
  }, 500);
}

;(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 innerModalStyle = '.work-item-view {\
    width: calc(100% - 20px);\
    margin: 0 10px;\
  }'
  var uiDialogContentStyle = '.ui-dialog-content:not(.modal-dialog) {height: calc(100% - 59px) !important}'
  var otherStyles = '.' + topClass + ' table.witform-layout {\
    width: calc(100% - 4px);\
  }\
  .linksPanel {\
    display: block !important;\
  }\
  .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;\
  }\
  input.task-identifier {\
    cursor: pointer;\
    text-align: center;\
    width: 80px;\
    margin: 0 16px;\
    border: 1px solid #ccc;\
  }\
  .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;\
  }\
  button.changeset-identifier {\
    vertical-align: top;\
    line-height: 0;\
    padding: 0px 12px;\
    height: 22px;\
    margin-left: 8px;\
  }\
  .copy-message {\
    font-weight: normal;\
  }\
  .agile-content-container div.board-tile.ui-draggable:focus {\
    box-shadow: 0px 0px 10px 2px; \
  }\
  .ui-tabs-panel[rawtitle="Description"] .workitemcontrol > div, \
  .ui-tabs-panel[rawtitle="Description"] .workitemcontrol > div > .richeditor-container, \
  [rawtitle="Steps to Reproduce"] .workitemcontrol > div, \
  [rawtitle="Steps to Reproduce"] .workitemcontrol > div > .richeditor-container {\
    height: auto !important;\
  }\
  .ui-tabs-panel .richeditor-editarea {\
    position: relative;\
  }\
  .ui-tabs-panel .richeditor-editarea iframe {\
    resize: vertical;\
  }\
  .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: 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("." + topClass + " " + modalStyle)
  GM_addStyle("." + topClass + " " + innerModalStyle)
  GM_addStyle("." + topClass + " " + uiDialogContentStyle)
  GM_addStyle(otherStyles)
})()




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或关注我们的公众号极客氢云获取最新地址