Jira Copy Ticket

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

当前为 2024-10-30 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Jira Copy Ticket
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.3
  5. // @description Adds a "Copy Ticket" button to copy the title and ticket link as rich text
  6. // @author Othman Shareef (othmanosx@gmail.com)
  7. // @match https://eventmobi.atlassian.net/*
  8. // @icon https://www.google.com/s2/favicons?domain=atlassian.net
  9. // @grant none
  10. // @license MIT
  11. // ==/UserScript==
  12. (function () {
  13. 'use strict';
  14. let copyTitleButton
  15. let copyIdButton
  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. }, 500);
  27. }
  28. };
  29.  
  30. // copy ticket title to clipboard
  31. const copyTicketTitle = () => {
  32. copyTitleButton.innerText = 'Loading...';
  33. const ticketTitleElement = document.querySelector('[data-testid="issue.views.issue-base.foundation.summary.heading"]');
  34. const ticketLinkElement = document.querySelector('[data-testid="issue.views.issue-base.foundation.breadcrumbs.current-issue.item"]');
  35. if (ticketTitleElement && ticketLinkElement) {
  36. const ticketTitle = ticketTitleElement.innerText;
  37. const ticketLink = ticketLinkElement.href
  38. const ticketID = ticketLinkElement.firstChild.innerText
  39. const html = `<a href="${ticketLink}">${ticketID}: ${ticketTitle}</a>`
  40. const clipboardItem = new ClipboardItem({
  41. 'text/html': new Blob([html], {
  42. type: 'text/html'
  43. }),
  44. 'text/plain': new Blob([html], {
  45. type: 'text/plain'
  46. })
  47. });
  48. navigator.clipboard.write([clipboardItem]).then(_ => {
  49. // Change button text to "Copied!" for a moment
  50. copyTitleButton.innerText = 'Copied!';
  51. setTimeout(() => {
  52. // Change button text back to the original text after a delay
  53. copyTitleButton.innerText = 'Copy Ticket';
  54. }, 1000); // You can adjust the delay (in milliseconds) as needed
  55. }, error => alert(error));
  56. } else {
  57. alert('Ticket title element not found!');
  58. }
  59. };
  60.  
  61. // copy ticket ID to clipboard
  62. const copyTicketId = () => {
  63. copyIdButton.innerText = 'Loading...';
  64. const idElement = document.querySelector('[data-testid="issue.views.issue-base.foundation.breadcrumbs.current-issue.item"]');
  65.  
  66. if (idElement) {
  67. const ticketID = idElement.firstChild.innerText
  68. const html = `${ticketID}`
  69. const clipboardItem = new ClipboardItem({
  70. 'text/html': new Blob([html], {
  71. type: 'text/html'
  72. }),
  73. 'text/plain': new Blob([html], {
  74. type: 'text/plain'
  75. })
  76. });
  77. navigator.clipboard.write([clipboardItem]).then(_ => {
  78. // Change button text to "Copied!" for a moment
  79. copyIdButton.innerText = 'Copied!';
  80. setTimeout(() => {
  81. // Change button text back to the original text after a delay
  82. copyIdButton.innerText = 'Copy ID';
  83. }, 1000); // You can adjust the delay (in milliseconds) as needed
  84. }, error => alert(error));
  85. } else {
  86. alert('Ticket title element not found!');
  87. }
  88. };
  89.  
  90. // Add button next to the ticket title
  91. const addCopyTitleButton = () => {
  92. const existingCopyBtn = document.getElementById("copy-title-button")
  93. if (existingCopyBtn) return
  94. const copyButton = document.createElement('button');
  95. copyButton.innerText = 'Copy Ticket Title';
  96. copyButton.className = 'css-1l34k60';
  97. copyButton.id = "copy-title-button"
  98. copyButton.addEventListener('click', copyTicketTitle);
  99.  
  100. // Add hover and click effects
  101. copyButton.addEventListener('mouseover', () => {
  102. copyButton.style.filter = 'brightness(2) contrast(2)';
  103. });
  104. copyButton.addEventListener('mouseout', () => {
  105. copyButton.style.filter = 'none';
  106. });
  107. copyButton.addEventListener('mousedown', () => {
  108. copyButton.style.filter = 'brightness(0.1) contrast(0.1)';
  109. });
  110. copyButton.addEventListener('mouseup', () => {
  111. copyButton.style.filter = 'none';
  112. });
  113.  
  114. copyTitleButton = copyButton
  115.  
  116. const element = document.querySelector('[data-testid="issue-view-foundation.quick-add.quick-add-items-compact.add-button-dropdown--trigger"]');
  117. element.parentElement.parentElement.appendChild(copyTitleButton);
  118. };
  119.  
  120. // Add another button to copy the ticket ID
  121. const addCopyIdButton = () => {
  122. const existingCopyBtn = document.getElementById("copy-id-button")
  123. if (existingCopyBtn) return
  124. const copyButton = document.createElement('button');
  125. copyButton.innerText = 'Copy ID';
  126. copyButton.className = 'css-1l34k60';
  127. copyButton.id = "copy-id-button"
  128. copyButton.addEventListener('click', copyTicketId);
  129.  
  130. // Add hover and click effects
  131. copyButton.addEventListener('mouseover', () => {
  132. copyButton.style.filter = 'brightness(2) contrast(2)';
  133. });
  134. copyButton.addEventListener('mouseout', () => {
  135. copyButton.style.filter = 'none';
  136. });
  137. copyButton.addEventListener('mousedown', () => {
  138. copyButton.style.filter = 'brightness(0.1) contrast(0.1)';
  139. });
  140. copyButton.addEventListener('mouseup', () => {
  141. copyButton.style.filter = 'none';
  142. });
  143.  
  144. copyIdButton = copyButton
  145.  
  146. const element = document.querySelector('[data-testid="issue-view-foundation.quick-add.quick-add-items-compact.add-button-dropdown--trigger"]');
  147. element.parentElement.parentElement.appendChild(copyIdButton);
  148. };
  149.  
  150. const debounce = (func, delay) => {
  151. clearTimeout(debounceTimer);
  152. debounceTimer = setTimeout(func, delay);
  153. };
  154.  
  155. // Use MutationObserver to detect changes in the DOM
  156. const observer = new MutationObserver(() => {
  157. debounce(() => {
  158. waitForElement('[data-testid="issue.views.issue-base.foundation.summary.heading"]', () => { addCopyTitleButton(); addCopyIdButton(); })
  159. }, 100);
  160. });
  161.  
  162. // Observe changes in the body and its descendants
  163. observer.observe(document.body, {
  164. childList: true,
  165. subtree: true,
  166. });
  167.  
  168. })();