Copy Github PR

Adds a "Copy PR" button to copy the title and link as a rich text

As of 2023-11-30. See the latest version.

  1. // ==UserScript==
  2. // @name Copy Github PR
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.1
  5. // @description Adds a "Copy PR" button to copy the title and link as a rich text
  6. // @author Othman Shareef (othmanosx@gmail.com)
  7. // @match https://github.com/*
  8. // @icon https://github.githubassets.com/favicons/favicon.svg
  9. // @grant none
  10. // @license MIT
  11. // ==/UserScript==
  12. (function() {
  13. 'use strict';
  14. const elementQuery = 'h1 .markdown-title'
  15. let button
  16. let debounceTimer;
  17.  
  18. // Wait for the Jira ticket title element to be available
  19. const waitForElement = (selector, callback) => {
  20. const element = document.querySelector(selector);
  21. if (element) {
  22. callback();
  23. } else {
  24. setTimeout(() => {
  25. waitForElement(selector, callback);
  26. }, 200);
  27. }
  28. };
  29.  
  30. // copy ticket title to clipboard
  31. const copyPR = () => {
  32. button.innerText = 'Loading...';
  33. const ticketTitleElement = document.querySelector(elementQuery);
  34. if (ticketTitleElement) {
  35. const PRTitle = ticketTitleElement.innerText;
  36. const PRLink = window.location.href
  37. const html = `<a href="${PRLink}">${PRTitle}</a>`
  38. const clipboardItem = new ClipboardItem({
  39. 'text/html': new Blob([html], {
  40. type: 'text/html'
  41. }),
  42. 'text/plain': new Blob([html], {
  43. type: 'text/plain'
  44. })
  45. });
  46. navigator.clipboard.write([clipboardItem]).then(_ => {
  47. // Change button text to "Copied!" for a moment
  48. button.innerText = 'Copied!';
  49. setTimeout(() => {
  50. // Change button text back to the original text after a delay
  51. button.innerText = 'Copy PR';
  52. }, 1000); // You can adjust the delay (in milliseconds) as needed
  53. }, error => alert(error));
  54. } else {
  55. alert('PR title element not found!');
  56. }
  57. };
  58.  
  59. // Add button next to the ticket title
  60. const addButton = () => {
  61. const existingCopyBtn = document.getElementById("copy-button")
  62. if(existingCopyBtn) return
  63. const copyButton = document.createElement('button');
  64. copyButton.innerText = 'Copy PR';
  65. copyButton.className = 'Button--secondary Button--big Button';
  66. copyButton.id = "copy-button"
  67. copyButton.style.marginLeft = '10px';
  68. copyButton.style.marginTop = '5px';
  69. copyButton.style.verticalAlign = 'text-top';
  70.  
  71. copyButton.addEventListener('click', copyPR);
  72.  
  73. button = copyButton
  74. const element = document.querySelector(elementQuery);
  75. element.parentElement.appendChild(copyButton);
  76. };
  77.  
  78. waitForElement(elementQuery, addButton);
  79.  
  80. const debounce = (func, delay) => {
  81. clearTimeout(debounceTimer);
  82. debounceTimer = setTimeout(func, delay);
  83. };
  84.  
  85. // Use MutationObserver to detect changes in the DOM
  86. const observer = new MutationObserver(() => {
  87. debounce(() => {
  88. waitForElement(elementQuery, addButton);
  89. }, 100);
  90. });
  91.  
  92. // Observe changes in the body and its descendants
  93. observer.observe(document.body, {
  94. childList: true,
  95. subtree: true,
  96. });
  97.  
  98. })();