Greasy Fork镜像 还支持 简体中文。

Jira Copy Ticket

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

  1. // ==UserScript==
  2. // @name Jira Copy Ticket
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.3.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. function getClassNames() {
  19. const element = document.querySelector('[data-testid="issue-view-foundation.quick-add.quick-add-items-compact.add-button-dropdown--trigger"]');
  20. return element ? element.className : null;
  21. }
  22.  
  23. // Wait for the Jira ticket title element to be available
  24. const waitForElement = (selector, callback) => {
  25. const element = document.querySelector(selector);
  26. if (element) {
  27. callback();
  28. } else {
  29. setTimeout(() => {
  30. waitForElement(selector, callback);
  31. }, 500);
  32. }
  33. };
  34.  
  35. // copy ticket title to clipboard
  36. const copyTicketTitle = () => {
  37. copyTitleButton.innerText = 'Loading...';
  38. const ticketTitleElement = document.querySelector('[data-testid="issue.views.issue-base.foundation.summary.heading"]');
  39. const ticketLinkElement = document.querySelector('[data-testid="issue.views.issue-base.foundation.breadcrumbs.current-issue.item"]');
  40. if (ticketTitleElement && ticketLinkElement) {
  41. const ticketTitle = ticketTitleElement.innerText;
  42. const ticketLink = ticketLinkElement.href
  43. const ticketID = ticketLinkElement.firstChild.innerText
  44. const html = `<a href="${ticketLink}">${ticketID}: ${ticketTitle}</a>`
  45. const clipboardItem = new ClipboardItem({
  46. 'text/html': new Blob([html], {
  47. type: 'text/html'
  48. }),
  49. 'text/plain': new Blob([html], {
  50. type: 'text/plain'
  51. })
  52. });
  53. navigator.clipboard.write([clipboardItem]).then(_ => {
  54. // Change button text to "Copied!" for a moment
  55. copyTitleButton.innerText = 'Copied!';
  56. setTimeout(() => {
  57. // Change button text back to the original text after a delay
  58. copyTitleButton.innerText = 'Copy Ticket Title';
  59. }, 1000); // You can adjust the delay (in milliseconds) as needed
  60. }, error => alert(error));
  61. } else {
  62. alert('Ticket title element not found!');
  63. }
  64. };
  65.  
  66. // copy ticket ID to clipboard
  67. const copyTicketId = () => {
  68. copyIdButton.innerText = 'Loading...';
  69. const idElement = document.querySelector('[data-testid="issue.views.issue-base.foundation.breadcrumbs.current-issue.item"]');
  70.  
  71. if (idElement) {
  72. const ticketID = idElement.firstChild.innerText
  73. const html = `${ticketID}`
  74. const clipboardItem = new ClipboardItem({
  75. 'text/html': new Blob([html], {
  76. type: 'text/html'
  77. }),
  78. 'text/plain': new Blob([html], {
  79. type: 'text/plain'
  80. })
  81. });
  82. navigator.clipboard.write([clipboardItem]).then(_ => {
  83. // Change button text to "Copied!" for a moment
  84. copyIdButton.innerText = 'Copied!';
  85. setTimeout(() => {
  86. // Change button text back to the original text after a delay
  87. copyIdButton.innerText = 'Copy ID';
  88. }, 1000); // You can adjust the delay (in milliseconds) as needed
  89. }, error => alert(error));
  90. } else {
  91. alert('Ticket title element not found!');
  92. }
  93. };
  94.  
  95. // Add button next to the ticket title
  96. const addCopyTitleButton = () => {
  97. const existingCopyBtn = document.getElementById("copy-title-button")
  98. if (existingCopyBtn) return
  99. const copyButton = document.createElement('button');
  100. const JiraButtonClassName = getClassNames()
  101. copyButton.innerText = 'Copy Ticket Title';
  102. copyButton.className = JiraButtonClassName;
  103. copyButton.style.width = "auto"
  104. copyButton.style.paddingInline = "8px"
  105. copyButton.id = "copy-title-button"
  106. copyButton.addEventListener('click', copyTicketTitle);
  107.  
  108. copyTitleButton = copyButton
  109.  
  110. const element = document.querySelector('[data-testid="issue-view-foundation.quick-add.quick-add-items-compact.add-button-dropdown--trigger"]');
  111. element.parentElement.parentElement.parentElement.parentElement.appendChild(copyTitleButton);
  112. };
  113.  
  114. // Add another button to copy the ticket ID
  115. const addCopyIdButton = () => {
  116. const existingCopyBtn = document.getElementById("copy-id-button")
  117. if (existingCopyBtn) return
  118. const copyButton = document.createElement('button');
  119. const JiraButtonClassName = getClassNames()
  120. copyButton.innerText = 'Copy ID';
  121. copyButton.className = JiraButtonClassName;
  122. copyButton.style.width = "auto"
  123. copyButton.style.paddingInline = "8px"
  124. copyButton.id = "copy-id-button"
  125. copyButton.addEventListener('click', copyTicketId);
  126.  
  127. copyIdButton = copyButton
  128.  
  129. const element = document.querySelector('[data-testid="issue-view-foundation.quick-add.quick-add-items-compact.add-button-dropdown--trigger"]');
  130. element.parentElement.parentElement.parentElement.parentElement.appendChild(copyIdButton);
  131. };
  132.  
  133. const debounce = (func, delay) => {
  134. clearTimeout(debounceTimer);
  135. debounceTimer = setTimeout(func, delay);
  136. };
  137.  
  138. // Use MutationObserver to detect changes in the DOM
  139. const observer = new MutationObserver(() => {
  140. debounce(() => {
  141. waitForElement('[data-testid="issue.views.issue-base.foundation.summary.heading"]', () => { addCopyTitleButton(); addCopyIdButton(); })
  142. }, 100);
  143. });
  144.  
  145. // Observe changes in the body and its descendants
  146. observer.observe(document.body, {
  147. childList: true,
  148. subtree: true,
  149. });
  150.  
  151. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址