NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name Tab Titles for ticket system ManageEngine ServiceDesk Plus
// @name:de-DE Tabtitel für Ticketsystem ManageEngine ServiceDesk Plus
// @description Automatically make ticket ids clickable and embed IK svg. (Less features than in v1.5 because GM_config needs code changes due to updates.)
// @description:de-DE Automatisch Ticket-Links klickbar machen und IK SVG einbetten. (Weniger Features als in v1.5, da für GM_config Code-Anpassungen notwendig wären.)
// @namespace jandunker
// @version 1.6
// @match http://servicedesk/*
// @match http://servicedesk.hgroup.intra/*
// @match https://servicedesk/*
// @match https://servicedesk.hgroup.intra/*
// @exclude */framework/html/blank.html
// @noframes
// @license GPL-3.0-or-later; http://www.gnu.org/licenses/gpl-3.0.txt
// @copyright 2020-2024, eckende (https://openuserjs.org/users/eckende)
// @updateURL https://openuserjs.org/meta/eckende/Tab_Titles_for_ticket_system_ManageEngine_ServiceDesk_Plus.meta.js
// @downloadURL https://openuserjs.org/install/eckende/Tab_Titles_for_ticket_system_ManageEngine_ServiceDesk_Plus.user.js
// @icon data:image/gif;base64,R0lGODlhEAAQAHAAACH5BAkAAAMALAAAAAAQABAAgQAAAHSK///TdAAAAAI7nI85wIDtDhAiGhCCXXRj/XRP5nXNQp4TJX4ZuLKOS3KVmjK2V0smlGuYTKkUS0bLHYEvHUS3eCqmiAIAOw==
// @icon64 data:image/gif;base64,R0lGODlhQABAAHAAACH5BAkAAAMALAAAAABAAEAAgQAAAHSK///TdAAAAAL/nI+py+0Po5y02ouzPqD7DwJUSHpjSZ4oqK7m5LJw/Ep0HX3Czu9fGwgKg79Zp9cr2jzDoTLnQfKekE9TSH3opIKsw3oNeBtb6ZgBvp4XZeRakW6+E+0krsoMzxH16V2bp/b3FcW1xxEoN0hWaLaIluj0yNboNgkXiXVJV2nXAaS3ydfpdzPaESZ2I4J4ZLh6CpC6ymrQ5wPbKhtqqsvVlWubSRQ8cPtr+ambyqypLOyKjPwU1yz4bEwq7VmbjWo9i3O8DYxdDe7cPb5NPYzeHk2erO4ODg8g/2peb32f77jvGzpFAfHpoxErXC9oBgEiXMbrIcNftBJGjBGLYrFzoMQWems4D9S1bnjicRNJkCQgk6VUEhKYsoVGbCV3jZR5kBZHVTpBzOzpDqjPnEAVCgXJTahRoT+PlVgalERTbeVAQIUpKcRUlrhCXLWZ0qlUoljT7SzxbxqJgc3QpiULlm1Yqmmfyr1I95/du3O16l0ll1ZdwGwF/70ReNVgxIVT6BIK6ajLj5ApSY58FHNlTJctZ94AOrTo0aRLmz5NoQAAOw==
// @author Jan Dunker
// ==/UserScript==
// global variable declaration
var ticketlinksuccess = false;
// Language
// 0: de
// 1: en
const lang = navigator.language == "de" ? 0 : 1;
// debug mode
var logLevel = 0; // 0, 1 or 2
// clickable ticket no. links
//
// If activated, the texts on ticket pages are searched for ticket no.. Results get replaced by links.
const doAddTicketLinks = true;
// Open ticket links in new tabs
const openLinksInNewTab = true;
// The search runs every x ms:
const ticketlinksinterval = 5000;
// Regex to detect tickets
const ticketregex = new RegExp('\\b([5-9]\\d{5})\\b'); // 6 digits starting with 5, 6, 7, 8 or 9
// embed svg screenshot attachment
// /!\ This is a company specific feature. You can disable this if you work for a different employer.
const doEmbedSVG = true;
// Update interval for svg embed
// The smaller the number, the more often and more bad for performance
const updateinterval = 1000 // 1 Sekunde
const emptyfield = "";
// document.querySelector with handling
function querySel(selector){
var elem = document.querySelector(selector);
if (elem) {
if (elem.innerText.trim() != "") return elem.innerText.trim();
else return elem.title.trim();
}
else {
console.log('[UserScript Tab Titles] "' + selector + '" not found, returning "' + emptyfield + '" instead. Try document.querySelector("' + selector + '")');
return emptyfield;
}
}
// adding ticket links: where to look/replace depending on current page
function addTicketLinks(){
// if tab is not open (page is not visible) do not waste computing power and skip the update
// except if tab title has not been set yet (if the tab was opened in the background) or if the tab was just hidden (see event handler)
if (document.visibilityState != 'visible' && ticketlinksuccess){
return;
}
let url = location.href;
if (url.match(/^https?:\/\/[\w\d.-]*\/WorkOrder\.do\?(?:.*&)?woMode=viewWO/)) {
addTicketLinksForTicket();
} else if (url.match(/^https?:\/\/[\w\d.-]*\/SearchN\.do/) && document.getElementById("requestId")) {
addTicketLinksForTicket();
}
}
// adding ticket links: where to look/replace on ticket pages
function addTicketLinksForTicket(){
ticketlinksuccess = true;
if (logLevel > 0) console.time('addTicketLinksForTicket');
// Subject
if (logLevel > 1) console.time('addTicketLinksForTicket_Subject');
addTicketLinksToId('req_subject');
if (logLevel > 1) console.timeEnd('addTicketLinksForTicket_Subject');
// Body
if (logLevel > 1) console.time('addTicketLinksForTicket_Description');
addTicketLinksToId('req-desc-body');
if (logLevel > 1) console.timeEnd('addTicketLinksForTicket_Description');
// Conversations
if (logLevel > 1) console.time('addTicketLinksForTicket_Conversations');
addTicketLinksToQuery('.panel-collapse.collapse');
if (logLevel > 1) console.timeEnd('addTicketLinksForTicket_Conversations');
if (logLevel > 0) console.timeEnd('addTicketLinksForTicket');
}
// adding ticket links: call function for element
function addTicketLinksToId(id){
// if not found log to console and return
let elem = document.getElementById(id);
if (!elem) {
console.log('[UserScript Tab Titles] Adding ticket links failed: "' + id + '" not found. Try document.getElementById("' + id + '")');
return;
}
addTicketLinksToElem(elem);
}
// adding ticket links: call function for all elements
function addTicketLinksToQuery(selector){
// if not found log to console and return
let elems = document.querySelectorAll(selector);
if (!elems) {
console.log('[UserScript Tab Titles] Adding ticket links failed: "' + selector + '" not found. Try document.querySelectorAll("' + selector + '")');
return;
}
for (let i=0; i<elems.length; i++) {
addTicketLinksToElem(elems[i])
}
}
// adding ticket links: the actual work
function addTicketLinksToElem(elem){
if (elem) {
// get all text nodes (within element)
let iter = document.createNodeIterator(elem, NodeFilter.SHOW_TEXT);
let textnode;
// iterate through all text nodes
while (textnode = iter.nextNode()) {
// skip if parent is link to avoid nesting links
if (textnode.parentNode.tagName != 'A') {
// split text (e.g. ['My ticket is #', '123456', '. Thank you.'])
let arr = textnode.nodeValue.split(ticketregex);
for (let i = 0; i < arr.length; i++) {
let a;
if (ticketregex.test(arr[i])){
// if ticket number (and != current ticket) -> create a element
a = document.createElement('a');
a.href = '/WorkOrder.do?woMode=viewWO&woID=' + arr[i];
if (openLinksInNewTab) a.target = '_blank';
a.innerText = arr[i];
} else {
// else -> create text node
a = document.createTextNode(arr[i]);
}
// insert replacement nodes
textnode.parentNode.insertBefore(a, textnode);
}
// remove original text node
textnode.parentNode.removeChild(textnode);
}
}
}
}
// SVG Embed
// Embedding SVG can be done in many ways - here it's done by inserting the svg element into the html. This way, text can be selected and copied.
function embedSVG(){
if (document.querySelector('a[download^=Screenshot_]') && document.getElementById('desc-content').innerText.match(/\[IK_SupportMail\]\s*$/) && !document.getElementById('ik_screenshot')) {
if (logLevel > 0) console.time('embedSVG_all');
if (logLevel > 1) console.time('embedSVG_prepare');
// get screenshot attachment url
let url = document.querySelector('a[download^=Screenshot_]').href;
// prepare localized error messages just in case
let embeddingErrorMsg;
if (lang == 0) embeddingErrorMsg = 'Nachträgliches Einbetten des Screenshots fehlgeschlagen. Stattdessen den Anhang öffnen.'
else embeddingErrorMsg = 'Dynamic embedding of the screenshot failed. Open the attachment instead.';
// create new ik_screenshot container
let section = document.createElement('template');
section.innerHTML = '<div id="desc-ik_screenshot" class="atp-container-target atp-container m0 p10 clearfix"><p class="sb">Screenshot</p><hr class="mt0"><div id="ik_screenshot"></div></div>';
section = section.content.firstChild;
// add at new position
let att = document.getElementById('desc-attachments');
att.parentNode.insertBefore(section, att);
// until SVG file is replaced, show 'Loading...'
document.getElementById('ik_screenshot').innerHTML = 'Loading...';
if (logLevel > 1) console.time('embedSVG_download');
// fetch SVG file and insert contents into the container
fetch(url).then(
(response) => {
response.text()
.then(
(text) => {
if (logLevel > 1) console.timeEnd('embedSVG_download');
if (logLevel > 1) console.time('embedSVG_insert');
// use a sandboxed iframe to avoid XSS
document.getElementById('ik_screenshot').innerHTML = '<iframe id="ik_screenshot_iframe" sandbox style="border: 1px solid #bbb; width: 100%; height: 400px; resize: vertical"></iframe>';
document.getElementById('ik_screenshot_iframe').srcdoc = text;
if (logLevel > 1) console.timeEnd('embedSVG_insert');
if (logLevel > 0) console.timeEnd('embedSVG_all');
}
)
.catch(
(e) => {
if (logLevel > 1) console.timeEnd('embedSVG_download');
console.log('Response failed: ' + e.message);
document.getElementById('ik_screenshot').innerHTML = '[UserScript Tab Title] ' + embeddingErrorMsg + ' (Response, ' + e.message + ')';
if (logLevel > 1) console.timeEnd('embedSVG_insert');
if (logLevel > 0) console.timeEnd('embedSVG_all');
}
)
}
)
.catch(
(e) => {
if (logLevel > 1) console.timeEnd('embedSVG_download');
console.log('Fetch failed: ' + e.message);
document.getElementById('ik_screenshot').innerHTML = '[UserScript Tab Title] ' + embeddingErrorMsg + ' (Fetch, ' + e.message + ')';
if (logLevel > 1) console.timeEnd('embedSVG_insert');
if (logLevel > 0) console.timeEnd('embedSVG_all');
}
);
if (logLevel > 1) console.timeEnd('embedSVG_prepare');
}
}
// Hauptfunktion / Core Loop
function update(){
// if tab is not open (page is not visible) do not waste computing power and skip the update
// except if tab title has not been set yet (if the tab was opened in the background) or if the tab was just hidden (see event handler)
if (document.visibilityState != 'visible' && ticketlinksuccess){
return;
}
if (logLevel > 0) console.time('update');
// embed SVG if available
if (doEmbedSVG) embedSVG();
if (logLevel > 0) console.timeEnd('update');
}
console.log('[UserScript Tab Title] Injected at \'' + window.location + '\'');
// make sure update runs once after a page is hidden
addEventListener('visibilitychange', event => {
if (document.visibilityState == 'hidden') {
ticketlinksuccess = false;
}
});
// run first update
update();
// schedule to run every x ms
if (updateinterval > 50) window.setInterval(update, updateinterval);
// add ticket links
if (doAddTicketLinks && ticketlinksinterval > 50) window.setInterval(addTicketLinks, ticketlinksinterval);