myconnectwise.net enhancements

9/20/2023, 12:22:02 PM

目前为 2023-11-29 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name myconnectwise.net enhancements
  3. // @namespace Violentmonkey Scripts
  4. // @match https://aus.myconnectwise.net/v2022_2/connectwise.aspx
  5. // @grant none
  6. // @version 2.0
  7. // @author mikeInside
  8. // @description 9/20/2023, 12:22:02 PM
  9. // @require https://cdn.jsdelivr.net/npm/@violentmonkey/dom@2
  10. // @require https://cdn.jsdelivr.net/npm/@violentmonkey/shortcut@1
  11. // ==/UserScript==
  12.  
  13.  
  14. const disconnect = VM.observe(document.body, () => {
  15. // Find the target node
  16. const node = document.querySelector('#SR_Service_RecID-input');
  17. const rowWrap = document.querySelectorAll('.TicketNote-rowWrap');
  18.  
  19. if (node) { //runs on service board pages to stop autocomplete from blocking visibility
  20. node.setAttribute("autocomplete", "off");
  21. return true;
  22. }
  23.  
  24. // runs on individual ticket pages
  25. if (rowWrap[0]) { // wait until the ticket notes have been generated before starting the script
  26.  
  27. // start by generating the primary global buttons
  28. let bCss = "padding:3px; margin-bottom:12px; margin-top:0px; font-size:0.90em; width:50%";
  29. let butt;
  30.  
  31. butt = document.createElement("button");
  32. butt.style.cssText = bCss;
  33. butt.textContent = "Collapse All";
  34. butt.addEventListener("click", function() {
  35. let coll = document.getElementsByClassName('collapsible');
  36. if (coll.length == 0) { // check if collapsibles have been wiped out (eg. by a ticket note refresh)
  37. generateButtons();
  38. }
  39. for (let i = 0; i < coll.length; ++i) {
  40. displayChange(coll[i], 0);
  41. }
  42. });
  43. rowWrap[0].before(butt);
  44.  
  45. butt = document.createElement("button");
  46. butt.style.cssText = bCss;
  47. butt.textContent = "Expand All";
  48. butt.addEventListener("click", function() {
  49. let coll = document.getElementsByClassName('collapsible');
  50. if (coll.length == 0) { // check if collapsibles have been wiped out (eg. by a ticket note refresh)
  51. generateButtons();
  52. }
  53. for (let i = 0; i < coll.length; ++i) {
  54. displayChange(coll[i], 1);
  55. }
  56. });
  57. rowWrap[0].before(butt);
  58.  
  59. // wait an additonal couple of seconds for all ticket notes to finish loading
  60. setTimeout(function() {
  61. generateButtons();
  62. }, 2000);
  63.  
  64. // disconnect observer
  65. return true;
  66. }
  67. });
  68.  
  69. function generateButtons(){
  70. const rowWrap = document.querySelectorAll('.TicketNote-rowWrap');
  71. rowWrap.forEach((rowItem) => {
  72.  
  73. // create button to be placed above each ticket note
  74. let butt = document.createElement("button");
  75. butt.classList.add("collapsible");
  76. butt.style.cssText = 'padding:3px; margin-bottom:0px; margin-top:0px; width:100%; font-size:0.90em';
  77. butt.innerHTML = "";
  78.  
  79. let basicName = classText(rowItem, "TicketNote-basicName", "<strong>", "</strong>")
  80. let clickableName = classText(rowItem, "TicketNote-clickableName", "<strong>", "</strong>")
  81. butt.innerHTML += basicName + clickableName; // add name to button
  82.  
  83. //let timeDateText = classText(rowItem, "TimeText-date", " [","]"); // copy date to button
  84. // change date to Australian locale:
  85. let timeDateChild = rowItem.getElementsByClassName("TimeText-date");
  86. if (timeDateChild[0]) {
  87. let timeDateText = timeDateChild[0].textContent;
  88. let timeDateHTML = timeDateChild[0].innerHTML;
  89. const regexDate = new RegExp(/(\d{1,2})\/(\d{1,2})\/(\d{4})/, "g"); //matches date, connectwise uses en-US M/D/YYYY format
  90. //let timeDateFormat = timeDateText.replace(regexDate, "$2/$1/$3"); // simple method to just swap month and day around
  91. let timeDateMatch = regexDate.exec(timeDateText);
  92. let dateObject = new Date(timeDateMatch[3],timeDateMatch[1]-1,timeDateMatch[2]); //creates JS Date object
  93. const dateOptions = {
  94. weekday: "long",
  95. year: "numeric",
  96. month: "long",
  97. day: "numeric",
  98. };
  99. let dateFormat = dateObject.toLocaleString("en-AU", dateOptions);
  100. let timeDateFormat = timeDateText.replace(regexDate, dateFormat); //adds the time info back to formatted date
  101. butt.innerHTML += " [" + timeDateFormat + "]"; //adds date to button
  102. //timeDateChild[0].innerHTML = timeDateChild[0].innerHTML.replace(timeDateText, timeDateFormat); //replaces the existing date inside ticket note with formatted date
  103. timeDateChild[0].innerHTML = timeDateChild[0].innerHTML.replace(timeDateText, ""); //deletes the existing date
  104. }
  105.  
  106. // set custom styles for each ticket button
  107. if (rowItem.getElementsByClassName("TicketNote-pill")[0]) { //only internal notes have this class
  108. butt.style.cssText += ";background-color:#026CCF;color:#DDEEFF;text-align:right";
  109. } else if (clickableName.length > 0) { //only falco team have this class
  110. butt.style.cssText += ";background-color:#AABBEE;color:#3366AA;text-align:right";
  111. } else if (basicName.length > 0) { // only end users have this class
  112. butt.style.cssText += ";background-color:#CCAAEE;color:#6644AA;text-align:left";
  113. }
  114.  
  115. //rowItem.style.removeProperty('margin-top');
  116. //rowItem.parentElement.insertBefore(butt, rowItem); //previous method
  117. // we place the button inside the "TicketNote-rowWrap" class so that it will get wiped if the ticket notes are refreshed
  118. let rowChild = rowItem.getElementsByClassName("TicketNote-row");
  119. rowChild[0].before(butt);
  120.  
  121. } );
  122.  
  123. //add click listener functions to all collapsible buttons
  124. var coll = document.getElementsByClassName("collapsible");
  125. for (let i = 0; i < coll.length; i++) {
  126. coll[i].addEventListener("click", function() {
  127. this.classList.toggle("active");
  128. displayChange(this, -1);
  129. });
  130. }
  131. }
  132.  
  133. // toggle the display state of the sibling element that is directly after the passed parameter
  134. function displayChange(collapsible, state = -1) {
  135. // state -1 toggle, 0 off, 1 on
  136. var content = collapsible.nextElementSibling;
  137. if ((state != 0 && content.style.display === "none")) {
  138. content.style.display = "flex";
  139. } else if (state < 1) {
  140. content.style.display = "none";
  141. }
  142. }
  143.  
  144.  
  145. // find first matching classString that is a child of the startParent, return its innerText with optional pre/postfix text
  146. function classText(startParent, classString, prefix = "", postfix = "") {
  147. let foundChild = startParent.getElementsByClassName(classString);
  148. if (foundChild[0]) {
  149. return prefix + foundChild[0].innerText + postfix;
  150. } else {
  151. return "";
  152. }
  153. }
  154.  
  155. /*VM.shortcut.register('c-i', () => {
  156. console.log('You just pressed Ctrl-I');
  157. alert("I am an alert box!");
  158. });*/
  159.  
  160.