myconnectwise.net enhancements

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

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

// ==UserScript==
// @name        myconnectwise.net enhancements
// @namespace   Violentmonkey Scripts
// @match       https://aus.myconnectwise.net/v2022_2/connectwise.aspx
// @grant       none
// @version     2.0
// @author      mikeInside
// @description 9/20/2023, 12:22:02 PM
// @require https://cdn.jsdelivr.net/npm/@violentmonkey/dom@2
// @require https://cdn.jsdelivr.net/npm/@violentmonkey/shortcut@1
// ==/UserScript==


const disconnect = VM.observe(document.body, () => {
  // Find the target node
  const node = document.querySelector('#SR_Service_RecID-input');
  const rowWrap = document.querySelectorAll('.TicketNote-rowWrap');

  if (node) { //runs on service board pages to stop autocomplete from blocking visibility
    node.setAttribute("autocomplete", "off");
    return true;
  }

  // runs on individual ticket pages
  if (rowWrap[0]) { // wait until the ticket notes have been generated before starting the script

    // start by generating the primary global buttons
    let bCss = "padding:3px; margin-bottom:12px; margin-top:0px; font-size:0.90em; width:50%";
    let butt;

    butt = document.createElement("button");
    butt.style.cssText = bCss;
    butt.textContent = "Collapse All";
    butt.addEventListener("click", function() {
      let coll = document.getElementsByClassName('collapsible');
      if (coll.length == 0) { // check if collapsibles have been wiped out (eg. by a ticket note refresh)
        generateButtons();
      }
      for (let i = 0; i < coll.length; ++i) {
        displayChange(coll[i], 0);
      }
    });
    rowWrap[0].before(butt);

    butt = document.createElement("button");
    butt.style.cssText = bCss;
    butt.textContent = "Expand All";
    butt.addEventListener("click", function() {
      let coll = document.getElementsByClassName('collapsible');
      if (coll.length == 0) { // check if collapsibles have been wiped out (eg. by a ticket note refresh)
        generateButtons();
      }
      for (let i = 0; i < coll.length; ++i) {
        displayChange(coll[i], 1);
      }
    });
    rowWrap[0].before(butt);

    // wait an additonal couple of seconds for all ticket notes to finish loading
    setTimeout(function() {
      generateButtons();
    }, 2000);

    // disconnect observer
    return true;
  }
});

function generateButtons(){
  const rowWrap = document.querySelectorAll('.TicketNote-rowWrap');
  rowWrap.forEach((rowItem) => {

    // create button to be placed above each ticket note
    let butt = document.createElement("button");
    butt.classList.add("collapsible");
    butt.style.cssText = 'padding:3px; margin-bottom:0px; margin-top:0px; width:100%; font-size:0.90em';
    butt.innerHTML = "";

    let basicName = classText(rowItem, "TicketNote-basicName", "<strong>", "</strong>")
    let clickableName = classText(rowItem, "TicketNote-clickableName", "<strong>", "</strong>")
    butt.innerHTML += basicName + clickableName; // add name to button

    //let timeDateText = classText(rowItem, "TimeText-date", " [","]"); // copy date to button
    // change date to Australian locale:
    let timeDateChild = rowItem.getElementsByClassName("TimeText-date");
    if (timeDateChild[0]) {
      let timeDateText = timeDateChild[0].textContent;
      let timeDateHTML = timeDateChild[0].innerHTML;
      const regexDate = new RegExp(/(\d{1,2})\/(\d{1,2})\/(\d{4})/, "g"); //matches date, connectwise uses en-US M/D/YYYY format
      //let timeDateFormat = timeDateText.replace(regexDate, "$2/$1/$3"); // simple method to just swap month and day around
      let timeDateMatch = regexDate.exec(timeDateText);
      let dateObject = new Date(timeDateMatch[3],timeDateMatch[1]-1,timeDateMatch[2]); //creates JS Date object
      const dateOptions = {
        weekday: "long",
        year: "numeric",
        month: "long",
        day: "numeric",
      };
      let dateFormat = dateObject.toLocaleString("en-AU", dateOptions);
      let timeDateFormat = timeDateText.replace(regexDate, dateFormat); //adds the time info back to formatted date
      butt.innerHTML += " [" + timeDateFormat + "]"; //adds date to button
      //timeDateChild[0].innerHTML = timeDateChild[0].innerHTML.replace(timeDateText, timeDateFormat); //replaces the existing date inside ticket note with formatted date
      timeDateChild[0].innerHTML = timeDateChild[0].innerHTML.replace(timeDateText, ""); //deletes the existing date
    }

    // set custom styles for each ticket button
    if (rowItem.getElementsByClassName("TicketNote-pill")[0]) { //only internal notes have this class
      butt.style.cssText += ";background-color:#026CCF;color:#DDEEFF;text-align:right";
    } else if (clickableName.length > 0) { //only falco team have this class
      butt.style.cssText += ";background-color:#AABBEE;color:#3366AA;text-align:right";
    } else if (basicName.length > 0) { // only end users have this class
      butt.style.cssText += ";background-color:#CCAAEE;color:#6644AA;text-align:left";
    }

    //rowItem.style.removeProperty('margin-top');
    //rowItem.parentElement.insertBefore(butt, rowItem); //previous method
    // we place the button inside the "TicketNote-rowWrap" class so that it will get wiped if the ticket notes are refreshed
    let rowChild = rowItem.getElementsByClassName("TicketNote-row");
    rowChild[0].before(butt);

  } );

  //add click listener functions to all collapsible buttons
  var coll = document.getElementsByClassName("collapsible");
  for (let i = 0; i < coll.length; i++) {
    coll[i].addEventListener("click", function() {
      this.classList.toggle("active");
      displayChange(this, -1);
    });
  }
}

// toggle the display state of the sibling element that is directly after the passed parameter
function displayChange(collapsible, state = -1) {
  // state -1 toggle, 0 off, 1 on
  var content = collapsible.nextElementSibling;
  if ((state != 0 && content.style.display === "none"))  {
    content.style.display = "flex";
  } else if (state < 1) {
    content.style.display = "none";
  }
}


// find first matching classString that is a child of the startParent, return its innerText with optional pre/postfix text
function classText(startParent, classString, prefix = "", postfix = "") {
  let foundChild = startParent.getElementsByClassName(classString);
  if (foundChild[0]) {
    return prefix + foundChild[0].innerText + postfix;
  } else {
    return "";
  }
}

/*VM.shortcut.register('c-i', () => {
  console.log('You just pressed Ctrl-I');
  alert("I am an alert box!");
});*/