Jira copy summary

Adds a "Copy summary" button for issue pages on Jira.

安装此脚本
作者推荐脚本

您可能也喜欢Confluence: copy link buttons

安装此脚本
  1. // ==UserScript==
  2. // @name Jira copy summary
  3. // @namespace https://github.com/rybak/atlassian-tweaks
  4. // @version 5.1
  5. // @license MIT
  6. // @description Adds a "Copy summary" button for issue pages on Jira.
  7. // @author Sergey Lukashevich, Andrei Rybak, Dmitry Trubin
  8. // @homepageURL https://github.com/rybak/atlassian-tweaks
  9. // @include https://*jira*/browse/*
  10. // @match https://jira.example.com/browse/*
  11. // @match https://jira.example.com/browse/*
  12. // @icon https://jira.atlassian.com/favicon.ico
  13. //
  14. // @require https://cdn.jsdelivr.net/gh/odyniec/MonkeyConfig@0eaeb525733a36d9e721ec4d9cf3b744b527bfcf/monkeyconfig.js
  15. // @grant GM_registerMenuCommand
  16. // @grant GM_addStyle
  17. // @grant GM_getValue
  18. // @grant GM_setValue
  19. //
  20. // ==/UserScript==
  21.  
  22. /*
  23. * Copyright 2017-2023 Sergey Lukashevich
  24. * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
  25. * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
  26. * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
  27. * persons to whom the Software is furnished to do so, subject to the following conditions:
  28. *
  29. * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
  30. * Software.
  31. *
  32. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  33. * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  34. * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  35. * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  36. */
  37.  
  38. /*
  39. version 5.1
  40. - handling of ticket summaries with angle brackets "<>" has been fixed
  41. version 5.0
  42. - Jira 9 is now supported in addition to versions 7 and 8.
  43. version 4.6
  44. - More precise reference to monkeyconfig.js library.
  45. version 4.5
  46. - Migrated to jsDelivr as CDN for library code
  47. version 4.4
  48. - UX of copy-pasting the summary formatted with italics into a rich
  49. text editor has been improved. Now the text that you type after
  50. pasting will not be formatted in italics automatically.
  51. version 4.2
  52. - Styling of the button has been updated to be compatible with Jira
  53. version 8.20.*
  54. version 4.0
  55. - Resurrection of the button has been made more robust by relying on
  56. Jira's own events about edits on the issue pages.
  57. version 3.8
  58. - Italic formatting is now configurable via extension menu, and the
  59. configuration persists across script updates.
  60. version 3.7
  61. - User script now supports automatic updates via @updateURL.
  62. version 3.6
  63. - User script now has @icon, which can be rendered by the browser
  64. extension in the list of user scripts, dashboard, etc.
  65. version 3.5
  66. - Adding italics to format the summary text is now "configurable" via a
  67. constant in code.
  68. version 3.4.1
  69. - Fixed the button not working in Jira 8
  70. version 3.4
  71. - dependency on jQuery has been removed to improve compatibility with
  72. different versions of Jira, which may use different versions of
  73. jQuery themselves.
  74. version 3.3
  75. - Compatibility with Jira 8 has been improved.
  76. version 3.2
  77. - "Copy Summary" button will now work in "Detail View" of JQL search
  78. results.
  79. version 3.1
  80. - jQuery version has been downgraded to 1.7.2 to avoid clashing with
  81. Jira's version of jQuery
  82. version 3.0
  83. - Resurrection of the button has been made more aggressive to handle
  84. more use-cases.
  85. - User script used to incorrectly use link or summary of a previously
  86. opened ticket, which has been corrected.
  87. version 2.2
  88. - Code clean up
  89. version 2.1
  90. - Summary text (after ticket id) is italicized, to make it easier to see
  91. where the summary ends
  92. version 2.0
  93. - Jira 8 is now supported in addition to Jira 7
  94. version 1.2
  95. - Button "Copy summary" no longer breaks after editing a Jira ticket
  96. */
  97.  
  98. (function () {
  99. 'use strict';
  100.  
  101. /*
  102. * User configuration
  103. */
  104. var cfg = new MonkeyConfig({
  105. title: 'Jira copy summary configuration',
  106. menuCommand: true,
  107. params: {
  108. italic_summary: {
  109. type: 'checkbox',
  110. default: true
  111. }
  112. }
  113. });
  114.  
  115. function getMeta(metaName) {
  116. const metas = document.getElementsByTagName('meta');
  117.  
  118. for (let i = 0; i < metas.length; i++) {
  119. if (metas[i].getAttribute('name') === metaName) {
  120. return metas[i].getAttribute('content');
  121. }
  122. }
  123.  
  124. return '';
  125. }
  126.  
  127. var textResult = '';
  128. var htmlResult = '';
  129.  
  130. function handleCopyEvent(e) {
  131. var clipboardData;
  132.  
  133. // Stop event propogation
  134. e.stopPropagation();
  135. e.preventDefault();
  136.  
  137. clipboardData = e.clipboardData || window.clipboardData;
  138. clipboardData.setData('text/plain', textResult);
  139. clipboardData.setData('text/html', htmlResult);
  140. }
  141.  
  142. var COPY_BUTTON_ID = "copycopy";
  143. var copyButton;
  144.  
  145. function getJiraMajorVersion() {
  146. return document.querySelector('meta[name="application-name"]').attributes.getNamedItem("data-version").value.split(".")[0];
  147. }
  148.  
  149. function createButtonForJira7() {
  150. var ul = document.createElement("ul");
  151. ul.classList.add("toolbar-group");
  152. ul.classList.add("pluggable-ops");
  153.  
  154. var li = document.createElement("li");
  155. li.classList.add("toolbar-item");
  156.  
  157. copyButton = document.createElement("a");
  158. copyButton.id = COPY_BUTTON_ID;
  159. copyButton.classList.add("toolbar-trigger");
  160. copyButton.classList.add("zeroclipboard-is-hover");
  161. copyButton.textContent = "Copy summary*";
  162.  
  163. ul.appendChild(li);
  164. li.appendChild(copyButton);
  165.  
  166. return ul;
  167. }
  168.  
  169. function createButtonForJira8and9() {
  170. var div = document.createElement("div");
  171. div.id = "opsbar-copycopy_container"
  172. div.classList.add("aui-buttons");
  173. div.classList.add("pluggable-ops");
  174.  
  175. copyButton = document.createElement("a");
  176. copyButton.id = COPY_BUTTON_ID;
  177. copyButton.classList.add("aui-button");
  178. copyButton.classList.add("toolbar-trigger");
  179. copyButton.textContent = "Copy summary*";
  180.  
  181. div.appendChild(copyButton);
  182.  
  183. return div;
  184. }
  185.  
  186. function htmlEncode(s) {
  187. const tmp = document.createElement("div");
  188. tmp.appendChild(document.createTextNode(s));
  189. return tmp.innerHTML;
  190. }
  191.  
  192. function getTextResult(ticketId, summaryText) {
  193. return '[' + ticketId + '] ' + summaryText;
  194. }
  195.  
  196. function getHtmlResult(ticketId, summaryText) {
  197. const jiraUrl = getMeta("ajs-jira-base-url");
  198. const fullLink = jiraUrl + "/browse/" + ticketId;
  199. let htmlSummary = htmlEncode(summaryText);
  200. if (cfg.get('italic_summary')) {
  201. htmlSummary = '<i>' + htmlSummary + '</i> &#x200b;';
  202. }
  203. return '[<a href="' + fullLink + '">' + ticketId + '</a>] ' + htmlSummary;
  204. }
  205.  
  206. function copyClickAction() {
  207. const summaryText = document.getElementById("summary-val").textContent;
  208. var ticketIdSource = document.querySelector("#dx-issuekey-val-h1 a");
  209. if (!ticketIdSource) {
  210. ticketIdSource = document.querySelector(".aui-page-header-main .issue-link");
  211. }
  212. const ticketId = ticketIdSource.dataset.issueKey;
  213. textResult = getTextResult(ticketId, summaryText);
  214. htmlResult = getHtmlResult(ticketId, summaryText);
  215. document.addEventListener('copy', handleCopyEvent);
  216. document.execCommand('copy');
  217. document.removeEventListener('copy', handleCopyEvent);
  218. return false;
  219. };
  220.  
  221. function createButton() {
  222. try {
  223. copyButton = document.getElementById(COPY_BUTTON_ID);
  224. // if for some reason it doesn't exist - create one
  225. if (!copyButton) {
  226. const jiraMajorVersion = getJiraMajorVersion();
  227. var container;
  228. var button;
  229. switch (jiraMajorVersion) {
  230. case "7":
  231. container = document.getElementById("stalker").getElementsByClassName("toolbar-split toolbar-split-left")[0];
  232. button = createButtonForJira7();
  233. break;
  234. case "8":
  235. case "9":
  236. container = document.getElementById("stalker").getElementsByClassName("aui-toolbar2-primary")[0];
  237. button = createButtonForJira8and9();
  238. break;
  239. default:
  240. console.log("Jira v" + jiraMajorVersion + " is not supported");
  241. return;
  242. }
  243. container.appendChild(button);
  244. console.log("Created the button");
  245. } else {
  246. console.log("Using existing button");
  247. }
  248. copyButton.onclick = copyClickAction;
  249. } catch (e) {
  250. console.warn("Could not create 'Copy summary' button ", e);
  251. }
  252. }
  253.  
  254. createButton();
  255.  
  256. JIRA.bind(JIRA.Events.NEW_CONTENT_ADDED, () => {
  257. console.log("Something changed, recreating button...");
  258. createButton();
  259. });
  260. })();