Greasy Fork

Greasy Fork is available in English.

TFS Helper

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

当前为 2016-05-03 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         TFS Helper
// @namespace    http://jonas.ninja
// @version      1.5.1
// @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*
// @require      https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js
// @grant        GM_addStyle
// ==/UserScript==
/* jshint -W097 */
/* global $, GM_addStyle */
/* jshint asi: true, multistr: true */
'use strict'

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

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

$(document).on('click', 'input.task-identifier', function clickToCopy(e) {
  copy(this, $(this).next('span.copy-message'))
  if (e.ctrlKey) {
    // if CTRL is held down, copy both the commit ID and a commit message (useful if you have a clipboard manager like Ditto)
    var optMessage = 't' + (this.value) + ' ' + $(this).siblings('.info-text').text().replace(/^dev: /i, "")
    var that = this
    window.setTimeout(function() {
      copy(that, $(that).next('span.copy-message'), optMessage)
    }, 250)
  }
})



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', '30%')
  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 { // Task
    dialog.css('border-color', '#E0C252') // yellow
  }
}

function stackAllTabs(linksPane) {
  var column2 = $(linksPane).closest('table.content').parent('.column')
  if (Math.max(window.innerWidth, document.documentElement.clientWidth) >= 1600) return;
  column2.add(column2.prev()).css({width: '100%', display: 'block'})
  window.dispatchEvent(new Event('resize'));
}

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 $input = $('<input value="' + id + '"/>').addClass('task-identifier')
    $header.find('span.info-text').after($('<span>').addClass('copy-message')).after($input)
  });
}

function copy(elToCopy, $messageContainer, optionalMessage) {
  var $fakeElem = $('<textarea>');
  var succeeded
  var message = optionalMessage || 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/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="415.582" height="415.582" viewBox="0 0 415.582 415.582"><path d="M411.47 96.426l-46.32-46.32c-5.48-5.482-14.37-5.482-19.852 0l-192.95 192.952-82.066-82.064c-5.48-5.482-14.37-5.482-19.85 0l-46.32 46.32c-5.482 5.48-5.482 14.37 0 19.852l138.31 138.31a13.99 13.99 0 0 0 9.927 4.112c3.592 0 7.185-1.37 9.925-4.112l249.195-249.2a14.034 14.034 0 0 0 0-19.85z"/></svg>\')')
  } 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)
}

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

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 {height: calc(100% - 59px) !important}'
var otherStyles = '\
.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 {\
    height: auto !important;\
}\
.ui-tabs-panel[rawtitle="Description"] .richeditor-editarea {\
    position: relative;\
}\
.ui-tabs-panel[rawtitle="Description"] .richeditor-editarea iframe {\
    resize: vertical;\
}\
.witform-layout > tbody > tr.group {\
    width: calc(100% - 4px);\
}'

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;
}