Jira Server: toggleable checkboxes in issue descriptions

Renders Markdown style toggleable checkboxes in Jira Server issue descriptions

当前为 2024-09-12 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Jira Server: toggleable checkboxes in issue descriptions
  3. // @description Renders Markdown style toggleable checkboxes in Jira Server issue descriptions
  4. // @author Antti Kaihola
  5. // @namespace https://github.com/akaihola
  6. // @version 0.1
  7. // @license MIT
  8. // @match https://*/*/RapidBoard.jspa*
  9. // @match https://jira.*/browse/*-*
  10. // @grant none
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. const slugify = text => text.toLowerCase().replace(/\W+/g, '-').replace(/^-+|-+$/g, '');
  17. let lastSeenDescription = '';
  18.  
  19. function handleCheckboxes() {
  20. const description = jQuery('#description-val > div');
  21. description.off('click').on('click', 'input[type="checkbox"]', function() {
  22. const checkbox = jQuery(this);
  23. const li = checkbox.closest('li');
  24. const checked = checkbox.prop('checked');
  25. const content = li.contents().filter(function () {return this.nodeType === 3;}).first().text();
  26.  
  27. jQuery('#description-val > span[role="button"]').click();
  28.  
  29. const waitForEditor = setInterval(() => {
  30. const textarea = jQuery('textarea#description');
  31. if (textarea.length) {
  32. clearInterval(waitForEditor);
  33. const lines = textarea.val().split('\n');
  34. const updatedLines = lines.map(line => {
  35. const match = line.match(/^(\s*)-\s*\[([ \u2002xX])\]\s*(.+)/);
  36. return match && slugify(match[3]) === slugify(content)
  37. ? `${match[1]}- [${checked ? 'x' : '\u2002'}] ${match[3]}`
  38. : line;
  39. });
  40. textarea.val(updatedLines.join('\n'));
  41. jQuery('#description-form > div.save-options button.submit').click();
  42. }
  43. }, 50);
  44. });
  45.  
  46. description.find('li').each(function() {
  47. const li = jQuery(this);
  48. const textNode = li.contents().filter(function () {return this.nodeType === 3;}).first();
  49. if (!textNode.length) return;
  50. const firstChild = li.children().first();
  51. let content = textNode.text().trim();
  52. let checked;
  53. if (firstChild.is("span") && firstChild.text().trim().match(/^\[[xX]]/)) {
  54. firstChild.remove();
  55. checked = " checked";
  56. } else {
  57. const match = content.match(/^\[([ \u2002xX])\]\s*(.+)/);
  58. if (!match) return;
  59. content = match[2];
  60. textNode[0].nodeValue = match[2];
  61. checked = match[1].toLowerCase() === 'x' ? " checked" : "";
  62. }
  63. li.attr('id', slugify(content)).css('list-style-type', 'none');
  64. const checkbox = jQuery(`<input type="checkbox" style="margin-left: -1.5em"${checked}>`);
  65. li.prepend(checkbox);
  66. });
  67. }
  68.  
  69. setInterval(() => {
  70. const description = document.querySelector('#description-val > div');
  71. if (description) {
  72. if (description.innerHTML !== lastSeenDescription) {
  73. handleCheckboxes();
  74. lastSeenDescription = description.innerHTML;
  75. }
  76. }
  77. }, 200);
  78. })();