myconnectwise.net enhancements

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

目前為 2023-11-29 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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!");
});*/