diff --git a/tdx-enhanced.js b/tdx-enhanced.js index ca10fe2..04919ce 100644 --- a/tdx-enhanced.js +++ b/tdx-enhanced.js @@ -1,21 +1,26 @@ // ==UserScript== // @name tdx-enhanced -// @namespace ecn -// @version 2025-02-05-01 +// @namespace purdue-it +// @version 2025-05-29-558 // @description enhanced tdx coloring & formatting. follows system color scheme. // @author Purdue STEM IT - it@purdue.edu -// @match https://service.purdue.edu/TDNext/* -// @match https://service.purdue.edu/TDWebApi/* +// @match https://service.purdue.edu/TDWorkManagement* +// @match https://service.purdue.edu/TDNext* +// @match https://service.purdue.edu/TDWebApi* +// @match https://purdue.teamdynamixpreview.com/TDWorkManagement* +// @match https://purdue.teamdynamixpreview.com/TDNext* // @require https://momentjs.com/downloads/moment.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/tinycolor/1.6.0/tinycolor.min.js -// @downloadURL https://raw.github.itap.purdue.edu/ECN/tdx-userscript/main/tdx-enhanced.js -// @updateURL https://raw.github.itap.purdue.edu/ECN/tdx-userscript/main/tdx-enhanced.js -// @grant GM.addStyle +// @grant GM_getResourceText +// @grant GM_addStyle // @run-at document-end // ==/UserScript== (function() { 'use strict'; + + const primaryTitle = "Purdue University IT" + /* QUEUE COLORS */ //from main webqueue script @@ -80,7 +85,7 @@ /* BEGIN FUNCTIONS */ function updateHeading(mutation,numItems,totalItems) { - let headings = mutation.querySelectorAll(".panel-title") + let headings = mutation.querySelectorAll(".tdx-control-bar__title") for (const heading of headings) { //color the header if queue name is found @@ -90,64 +95,24 @@ let parent = heading.parentElement - //console.log("HEADING:",headingTxt) - - for (const qK of Object.keys(colorsByQueue)) { - let q = colorsByQueue[qK] - if (headingFirst == qK) { - //console.log("Match:",parent) - heading.style.color = q.txt - parent.style.backgroundColor = q.bg - } - handleHighlight("report",headingTxt,heading.parentElement) - - //try embedding - if (headingTxt.startsWith("{")) { - try { - let embedData = JSON.parse(headingTxt) - - let title = document.createElement("h4") - title.classList = heading.classList - title.innerText = embedData.name - let moduleBody = heading.parentNode.parentNode.querySelector(".ModuleContent") - let embed = document.createElement("iframe") - embed.classList = "customEmbed" - embed.src = embedData.url - heading.replaceWith(title) - moduleBody.replaceWith(embed) - } catch { - } + handleHighlight("report",headingTxt,heading) + + //try embedding + if (headingTxt.startsWith("{")) { + try { + let embedData = JSON.parse(headingTxt) + + let title = document.createElement("h4") + title.classList = heading.classList + title.innerText = embedData.name + let moduleBody = heading.parentNode.parentNode.querySelector(".ModuleContent") + let embed = document.createElement("iframe") + embed.classList = "customEmbed" + embed.src = embedData.url + heading.replaceWith(title) + moduleBody.replaceWith(embed) + } catch { } - - } - - //append number of items to title - //let parentDiv = document.createElement("div") - - //let header = document.createElement("h4") - //header.innerText = headingTxt - - var countSpan = heading.querySelector(".subtitle") - - if (!countSpan) { - countSpan = document.createElement("span") - //countSpan.innerText = `${numItems} items` - countSpan.classList.add("subtitle") - - heading.innerText = headingTxt - heading.appendChild(countSpan) - } - - var countTxt = null - - if (totalItems) { - countTxt = `${numItems} of ${totalItems} ${totalItems == 1 ? 'item' : 'items'}` - } else if (numItems) { - countTxt = `${numItems} ${numItems == 1 ? 'item' : 'items'}` - } - - if (countTxt) { - countSpan.innerText = countTxt } } } @@ -156,7 +121,7 @@ let qBlock = document.querySelector("#ctlAttribute2285") //ticket or task - let header = document.getElementById("thTicket_spnTitle") || document.querySelector("#upTaskHeader>h1") + let header = document.getElementById("thTicket_spnTitle") || document.querySelector("#upTaskHeader>div>h1") //find calendar inputs when editing tickets/tasks //let calendars = document.querySelectorAll("input.hasDatepicker") @@ -318,6 +283,8 @@ feedItems = document.querySelectorAll("#ttDescription") } + //console.log("Feed items:",feedItems) + if (feedItems) { for (const feedItem of feedItems) { [...feedItem.querySelectorAll("*[style*='color']")].forEach(feedElement=>{ @@ -360,7 +327,15 @@ } function parseOtherElements() { - let path = document.location.pathname + let path = document.location.pathname; + + //inject styles into search bar + [...document.querySelectorAll("#globalSearchBar, #input_ticketSearch33, .tdx-leftnav__filter-bar")].forEach(searchBar=>{ + let shadow = searchBar.shadowRoot + if (shadow) { + injectOtherStyles(shadow) + } + }) let desktopLayout = settings('get','layout') let desktop = document.querySelector("#divContent") @@ -412,10 +387,10 @@ return (Number(`0x1${hex}`) ^ 0xFFFFFF).toString(16).substr(1).toUpperCase() } - function parseTable(mutation) { - let t = mutation.target; - //console.error("BEGIN MUTATION") - let table = t.querySelector("table.report-viewer") + function parseTable(element) { + //let t = mutation.target; + //console.error("BEGIN MUTATION",t) + let table = element.querySelector("table") let items = []; var numItems = null @@ -427,10 +402,12 @@ //get headers of the tables [...table.querySelectorAll("th")].forEach((header)=>{ - var txt = header.querySelector("a") - txt = txt.dataset.sort - txt = txt.split(" ")[0] - headers.push(txt) + var txt = header.querySelector("span") + //txt = txt.dataset.sort + //txt = txt.split(" ")[0] + if (txt) { + headers.push(txt.textContent) + } //console.log("i:",header.cellIndex,txt) }); @@ -441,13 +418,16 @@ //console.log("row:",row); let item = {}; //parse each cell - [...row.querySelectorAll("td")].forEach((cell)=>{ - let i = cell.cellIndex; + let i = 0; + [...row.querySelectorAll("td:not(:has(input))")].forEach((cell)=>{ + //let i = cell.cellIndex; //can't use cellindex if there's a checkbox in the table let txt = cell.textContent; + txt = txt.trim() //console.log("Cell:",i,cell); item[headers[i]] = {txt:txt,cell:cell} + i++ }) item.row = row @@ -462,18 +442,20 @@ numItems = items.length //totalItems = null + /* let pagination = t.querySelector(".pull-right .bootstrap-pagination-label") if (pagination) { let pTxt = pagination.innerText pTxt = pTxt.split(" ")[0] totalItems = pTxt } + */ //console.log("Items:",items) } - updateHeading(t.parentElement,numItems,totalItems) + updateHeading(element,numItems,totalItems) } function createHighlightBubble(element,bgColor,txtColor) { @@ -499,10 +481,12 @@ //modify/color the cells function parseItem(item) { + //console.log("Parse item:",item) + //color queue names - if ('2285' in item) { - let qCell = item["2285"].cell - let qTxt = item["2285"].txt.toLowerCase() + if ('STEM Support' in item) { + let qCell = item["STEM Support"].cell + let qTxt = item["STEM Support"].txt.toLowerCase() if (qTxt in colorsByQueue) { let q = colorsByQueue[qTxt] @@ -511,7 +495,7 @@ } //change date modified - let relativeDates = ['LastModifiedDate','ModifiedDate','CreatedDate'] + let relativeDates = ['Modified','Created'] for (const dType of relativeDates) { if (dType in item) { let dTxt = item[dType].txt @@ -521,14 +505,14 @@ let dTxtNew = date.fromNow() if (dTxtNew == "Invalid date") { - console.warn("Failed date:",dTxt,item) + //console.warn("Failed date:",dTxt,item) } else { dCell.innerText = dTxtNew } } } - let detailedDates = ['DueDate','StartDate'] + let detailedDates = ['Date Due','Due','Created','Start'] for (const dType of detailedDates) { if (dType in item) { let dTxt = item[dType].txt @@ -538,7 +522,7 @@ let dTxtNew = date.calendar() if (dTxtNew == "Invalid date") { - console.warn("Failed date:",dTxt,item) + //console.warn("Failed date:",dTxt,item) } else { dCell.innerText = dTxtNew } @@ -546,7 +530,7 @@ } //highlight modified/age red - let modifiedDates = ['LastModifiedDate','ModifiedDate'] + let modifiedDates = ['Modified'] for (const dType of modifiedDates) { if (dType in item) { let modDate = item[dType] @@ -566,10 +550,10 @@ } //find user replies & last modified by internal - if ('CustomerName' in item && 'LastModifiedByFullName' in item && 'ResponsibleFullName' in item) { - let fromUser = item.CustomerName - let lastModified = item.LastModifiedByFullName - let assignedTo = item.ResponsibleFullName + if ('Requestor' in item && 'Modified By' in item && 'Prim Resp' in item) { + let fromUser = item["Requestor"] + let lastModified = item["Modified By"] + let assignedTo = item["Prim Resp"] //reply from user if (fromUser.txt == lastModified.txt && fromUser.txt != assignedTo.txt && assignedTo.txt != "Unassigned") { @@ -579,7 +563,7 @@ //modified by internal if (lastModified.txt != fromUser.txt && lastModified.txt != assignedTo.txt) { - let cell = item.LastModifiedByFullName.cell + let cell = lastModified.cell //cell.style.backgroundColor = "var(--col-modified)" handleHighlight("userModified",null,cell) } @@ -650,6 +634,8 @@ } } else { + return + //leave default link.onclick = (event)=>{ event.preventDefault() var params @@ -729,6 +715,7 @@ if (type=="report" && customType=="report") { if (customHighlight.value==txt) { + element.classList.add("reportTitle") style = customHighlight.style } } @@ -776,9 +763,21 @@ } function injectOtherStyles(element) { + + let s = document.createElement("style") + s.id = "customStyles" + s.innerText = customStyles + //find iframes - likely text editors if (element.tagName == "IFRAME") { - let frame = element.contentWindow.document + let frame = element.contentWindow.document; + + //attempt color coded tables on non-desktop pages + let tables = frame.querySelectorAll("table"); + [...tables].forEach(table=>{ + console.log("Table in frame:",table,table.parentElement) + parseTable(table.parentElement) + }) //console.log("Found iFrame:",frame) @@ -787,27 +786,35 @@ let head = frame.querySelector("head") let html = frame.querySelector("html") html.classList.add(settings("get","colorMode")) - let s = document.createElement("style") - s.id = "customStyles" - s.innerText = customStyles - head.appendChild(s) } + } else { + if (!element.querySelector("#customStyles")) { + element.appendChild(s) + } } -} + } //setup observer to watch report/table changes/refreshes let observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { let t = mutation.target - //console.log("Mutation:",mutation) - if (t.classList.contains("ModuleContent")) { - parseTable(mutation) + //console.error("Mutation:",t) + if (t.querySelector("div > table")) { + //let table = t.firstElementChild.querySelector("table") + //console.log("Matched table",t) + parseTable(t) } //parse the feed if it updates if (t.classList.contains("feed")) { parseTicket(mutation.addedNodes) + } else if (t.classList.contains("feed-body")) { + parseTicket(t.querySelectorAll(".feed-reply")) + } + + if (t.id=="tdx-workmgmt-container-collection") { + parseOtherElements() } //search whole document, frames may reset when reopened @@ -831,9 +838,9 @@ } async function changeTitle() { - let title = document.querySelector(".organization-link a") + let title = document.querySelector(".tdx-headerbar-headline a") if (title) { - title.innerText = "Purdue University - STEM" + title.innerText = primaryTitle } await injectToolbar() @@ -914,62 +921,12 @@ } - /* - function getColorMode() { - let localScheme = localStorage.getItem("styles") - let autoScheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? "darkMode" : "lightMode" - let scheme - - if (localScheme == "auto") { - scheme = autoScheme - } else if (!localScheme) { - localScheme = autoScheme == "darkMode" ? "lightMode" : "darkMode" - } else { - scheme = localScheme - } - - return scheme - } - - function checkColorMode() { - let localScheme = localStorage.getItem("styles") - let autoScheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? "darkMode" : "lightMode" - - let colors = localStorage.getItem("colors") - if (colors) { - console.log("Setting color from init") - setColors(colors) - } - - let primaryColor = document.querySelector("#primaryColor") - if (primaryColor) { - primaryColor.value = colors - } - - if (localScheme != autoScheme && localScheme != "auto") { - setColorMode(localScheme) - } else { - setColorMode(autoScheme,false) - } - - addEventListener("storage", (event) => { - //console.log("Storage event:",event) - if (event.key == "styles") { - setColorMode(event.newValue,false) - } else if (event.key == "colors") { - setColors(event.newValue) - } - }); - } - */ - - async function injectToolbar() { - let iconBar = document.querySelector(".tdbar-settings .pull-right") + let iconBar = document.querySelector("#globalSearchBar") let statusHTML = `