Bitbucket: commit links in diff tab of PRs

Adds convenience links in PRs of Bitbucket v7.6.+

目前为 2023-02-17 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Bitbucket: commit links in diff tab of PRs
  3. // @namespace https://github.com/rybak/atlassian-tweaks
  4. // @version 14
  5. // @license MIT
  6. // @description Adds convenience links in PRs of Bitbucket v7.6.+
  7. // @author Andrei Rybak
  8. // @include https://*bitbucket*/*/repos/*/pull-requests/*
  9. // @match https://bitbucket.example.com/*/repos/*/pull-requests/*
  10. // @icon https://bitbucket.org/favicon.ico
  11. // @homepageURL https://github.com/rybak/atlassian-tweaks
  12. // @grant none
  13. // ==/UserScript==
  14.  
  15. /*
  16. * Copyright (c) 2021-2023 Andrei Rybak
  17. *
  18. * Permission is hereby granted, free of charge, to any person obtaining a copy
  19. * of this software and associated documentation files (the "Software"), to deal
  20. * in the Software without restriction, including without limitation the rights
  21. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  22. * copies of the Software, and to permit persons to whom the Software is
  23. * furnished to do so, subject to the following conditions:
  24. *
  25. * The above copyright notice and this permission notice shall be included in all
  26. * copies or substantial portions of the Software.
  27. *
  28. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  29. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  30. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  31. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  32. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  33. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  34. * SOFTWARE.
  35. */
  36.  
  37. (function() {
  38. 'use strict';
  39.  
  40. function log(msg) {
  41. console.log("[PR commit links] " + msg);
  42. }
  43.  
  44. const ABBREV_LEN = 8; // abbreviate commit hashes to this number of characters
  45. const BLOCK_ID = 'RybakCommitLinkDiv';
  46. const URL_ID = 'RybakCommitLinkA';
  47. const TOOLTIP_BLOCK_ID = 'RybakCommitMessageDiv';
  48. const TOOLTIP_MSG_ID = 'RybakCommitMessagePre';
  49. const parsePath = /[/](projects|users)[/]([^/]*)[/]repos[/]([^/]*)[/].*[/]commits[/]([0-9a-f]+)/
  50.  
  51. function createTooltip(message) {
  52. $('#' + BLOCK_ID).hover((e) => {
  53. $('#' + TOOLTIP_BLOCK_ID).remove(); // delete previous tooltip
  54. const tooltipHtml = $('<div id="' + TOOLTIP_BLOCK_ID + '" class="Tooltip sc-jnlKLf ghcsui sc-bZQynM WXFrO sc-EHOje cffcMV"' +
  55. 'style="z-index:800; opacity: 1; position: fixed; top: 0px; left: 0px;' + // tweaked original element.style
  56. 'max-width: 600px; width: auto;' + // override of .ghcsui for better fitting of text
  57. 'background-color: rgb(23, 43, 77); border-radius: 3px; box-sizing: border-box; color: rgb(255, 255, 255); font-size: 12px; ' + // from .WXFrO
  58. 'line-height: 1.3; padding: 2px 6px; overflow-wrap: break-word;' + // from .WXFrO
  59. 'pointer-events: none;' + // from .cffcMV
  60. '">' +
  61. '<pre id="' + TOOLTIP_MSG_ID + '" class="commit-message-tooltip" style="' +
  62. 'white-space: pre-wrap; word-break: break-word;' + // from .commit-message-tooltip
  63. '"></pre>' +
  64. '</div>');
  65. $($('.atlaskit-portal-container')[0]).append(tooltipHtml);
  66. $('#' + TOOLTIP_MSG_ID).text(message); // text added early to calculate height correctly
  67.  
  68. const width = $('#' + TOOLTIP_BLOCK_ID).outerWidth();
  69. const height = $('#' + TOOLTIP_BLOCK_ID).height();
  70. const block = $('#' + BLOCK_ID);
  71. const blockOffset = block.offset();
  72. var x = blockOffset.left;
  73. var y = blockOffset.top + block.height() + 8; // 8 is from CSS rule ".changes-scope-actions > *"
  74. const maxX = $(window).width() + window.pageXOffset;
  75. const maxY = $(window).height() + window.pageYOffset;
  76. if (x + width > maxX) {
  77. x = Math.max(maxX - width, 0);
  78. }
  79. if (y + height > maxY) {
  80. y = Math.max(maxY - height, 0);
  81. }
  82. $('#' + TOOLTIP_BLOCK_ID).css({left: x, top: y}).show();
  83. }, (e) => {
  84. $('#' + TOOLTIP_BLOCK_ID).hide();
  85. });
  86. }
  87.  
  88. function ensureCommitLink() {
  89. const matching = document.location.pathname.match(parsePath);
  90. if (!matching) {
  91. log("No commit in the URL: " + document.location.pathname);
  92. return;
  93. }
  94. const origin = document.location.origin;
  95. const hash = document.location.hash; // add hash in case the user clicked to a different file
  96. const projectOrUser = matching[1];
  97. const project = matching[2];
  98. const repository = matching[3];
  99. const commit = matching[4];
  100. log("Parsed " + project + "/" + repository + "/" + commit);
  101.  
  102. const url = origin + '/' + projectOrUser + '/' + project + '/repos/' + repository + '/commits/' + commit + document.location.hash;
  103. const linkText = commit.substring(0, ABBREV_LEN);
  104. log("Link: " + url);
  105. log("Text: " + linkText);
  106.  
  107. const prevBlock = $('#' + BLOCK_ID);
  108. if (prevBlock.length) {
  109. log("Updating the link...");
  110. } else {
  111. // css-18u3ks8 is for Bitbucket Server ~v7.6 (aui ~ 8.1.*)
  112. // css-7svmop is for Bitbucket Server v7.21+ (aui ~ 9.3.*)
  113. const html = '<div id="' + BLOCK_ID + '"><div class="css-18u3ks8 css-7svmop">' + '<a id="' + URL_ID + '"></a>' + '</div></div>';
  114. $(".changes-scope-actions").append(html);
  115. log("Creating the link...");
  116. }
  117. $('#' + URL_ID)
  118. .attr('href', url)
  119. .text(linkText);
  120. log("Ajax...: " + document.location.origin + "/rest/api/1.0/" + projectOrUser + "/" + project + '/repos/' + repository + '/commits/' + commit);
  121.  
  122. $.ajax({
  123. // https://docs.atlassian.com/bitbucket-server/rest/7.6.0/bitbucket-rest.html#idp224
  124. url: (document.location.origin + "/rest/api/1.0/" + projectOrUser + "/" + project + '/repos/' + repository + '/commits/' + commit)
  125. }).then(data => {
  126. log("Ajax response received");
  127. createTooltip(data.message);
  128. });
  129. log("Done");
  130. }
  131.  
  132. $(document).ready(function() {
  133. ensureCommitLink();
  134. window.onpopstate = function(event) {
  135. ensureCommitLink();
  136. };
  137. });
  138.  
  139. })();