myconnectwise.net enhancements

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

当前为 2023-11-29 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 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!");
});*/