diff --git a/tdx-enhanced.js b/tdx-enhanced.js index 05d93f1..e92f590 100644 --- a/tdx-enhanced.js +++ b/tdx-enhanced.js @@ -1,7 +1,7 @@ // ==UserScript== // @name tdx-enhanced // @namespace ecn -// @version 2024-09-03-01 +// @version 2025-02-03-01 // @description enhanced tdx coloring & formatting. follows system color scheme. // @author Purdue STEM IT - it@purdue.edu // @match https://service.purdue.edu/TDNext/* @@ -67,6 +67,8 @@ 'zsite' : {'bg' : '#98A4AE', 'txt' : 'white'}, }; + var tdxtoolsUrl = "https://engineering.purdue.edu" + //regex for matching inline highlights var colorsByStatus = { '!!': {style: {background: 'var(--col-highlight-1)'}, type: 'highlight', re: new RegExp("\!! (.*) \!!","g")}, @@ -97,6 +99,25 @@ 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 { + } + } } @@ -117,15 +138,17 @@ heading.appendChild(countSpan) } - var countTxt + var countTxt = null if (totalItems) { countTxt = `${numItems} of ${totalItems} ${totalItems == 1 ? 'item' : 'items'}` - } else { + } else if (numItems) { countTxt = `${numItems} ${numItems == 1 ? 'item' : 'items'}` } - countSpan.innerText = countTxt + if (countTxt) { + countSpan.innerText = countTxt + } } } @@ -143,24 +166,99 @@ let newFormat = "YYYY-MM-DDTHH:mm"; [...calendars].forEach(calendar=>{ let date = moment(calendar.value,originalFormat) - let iso = date.format(newFormat) let newCal = document.createElement("input") newCal.id = `${calendar.id}-new` newCal.classList = calendar.classList newCal.type = "datetime-local" - newCal.value = iso + + function updateDate() { + //update old tdx calendar + let parsedFormat = date.format(originalFormat) + calendar.value = parsedFormat + + //update new calendar + let iso = date.format(newFormat) + newCal.value = iso + calTxt.innerText = date.calendar() + + console.log("Cal update!",date) + } //convert to original format newCal.addEventListener("input",event=>{ let parsedValue = moment(event.target.value,newFormat) - let parsedFormat = parsedValue.format(originalFormat) - calendar.value = parsedFormat + date = parsedValue + updateDate() }) + //date modifier shortcuts + let calBox = document.createElement("div") + calBox.classList = ["calBox"] + let calTxt = document.createElement("span") + calTxt.innerText = date.format('dddd') + + //modify date + function btnModify(btn) { + console.log("Modify date:",btn) + if ('add' in btn) { + date.add(btn.add) + } else if ('set' in btn) { + date.set(btn.set) + } + + //check business days + let day = date.isoWeekday() + let hour = date.hour() + let min = date.minute() + let wkday = date.weekday() + + console.log("WkDay:",wkday,"Day:",day,"hour:",hour,"min:",min) + + //if Friday after 5pm + if (day>=5 && (hour>17 || (hour==17 && min>0))) { + date.set({ + h: 8, + d: 1+7, + m: 0 + }) + } else if (day>5) { + //if sat/sun + date.set({ + d: 1+7 + }) + } + + + updateDate() + } + + let buttons = [ + {name:"+1 day",add: {days:1}}, + {name:"+1 week",add: {weeks:1}}, + {name:"+1 month",add: {months:1}}, + {name:"12PM",set: {h:12,m:0}}, + {name:"5PM",set: {h:17,m:0}} + ] + + for (const btn of buttons) { + let button = document.createElement("button") + button.classList = "btn btn-calendar" + button.innerText = btn.name + button.addEventListener("click",(event)=>{ + event.preventDefault() + btnModify(btn) + }) + calBox.appendChild(button) + } + + updateDate() + //old input still needs to exist for proper format conversion calendar.style.display = "none" calendar.parentElement.append(newCal) + calendar.parentElement.append(calTxt) + calendar.parentElement.append(calBox) }); } @@ -271,6 +369,9 @@ let table = t.querySelector("table.report-viewer") let items = []; + var numItems = null + var totalItems = null + if (table) { let headers = []; //console.log("Table",table); @@ -309,8 +410,8 @@ }) //update panel heading above table - var numItems = items.length - var totalItems = null + numItems = items.length + //totalItems = null let pagination = t.querySelector(".pull-right .bootstrap-pagination-label") if (pagination) { @@ -319,10 +420,31 @@ totalItems = pTxt } - updateHeading(t.parentElement,numItems,totalItems) - console.log("Items:",items) + //console.log("Items:",items) } + + updateHeading(t.parentElement,numItems,totalItems) + } + + function createHighlightBubble(element,bgColor,txtColor) { + + //should the bubble carry links over? + //let copy = element.cloneNode(true) + + //create new element to highlight + let newSpan = document.createElement("span") + newSpan.innerText = element.innerText //item["2285"].txt + newSpan.style.backgroundColor = bgColor + newSpan.style.color = txtColor + newSpan.classList.add("qHighlight") + + //if we use the copy method + //newSpan.appendChild(copy) + + //replace cell with new span + element.replaceChildren(newSpan) + return newSpan } //modify/color the cells @@ -335,15 +457,7 @@ if (qTxt in colorsByQueue) { let q = colorsByQueue[qTxt] - //create new element to highlight - let newSpan = document.createElement("span") - newSpan.innerText = item["2285"].txt - newSpan.style.backgroundColor = q.bg - newSpan.style.color = q.txt - newSpan.classList.add("qHighlight") - - //replace cell with new span - qCell.replaceChildren(newSpan) + createHighlightBubble(qCell,q.bg,q.txt) } } @@ -394,9 +508,10 @@ let hours = duration.asHours() let alpha = hours / ageThreshold + alpha = alpha > 1 ? 1 : alpha let cell = modDate.cell - cell.style.background = `rgba(255,0,0,${alpha}` + handleHighlight("dateModified",alpha,cell) cell.classList.add(alpha > 0.5 ? "light" : "dark") } } @@ -409,34 +524,29 @@ //reply from user if (fromUser.txt == lastModified.txt && fromUser.txt != assignedTo.txt && assignedTo.txt != "Unassigned") { - item.row.style.backgroundColor = "var(--col-reply)"; + //item.row.style.backgroundColor = "var(--col-reply)"; + handleHighlight("reply",null,item.row) } //modified by internal if (lastModified.txt != fromUser.txt && lastModified.txt != assignedTo.txt) { let cell = item.LastModifiedByFullName.cell - cell.style.backgroundColor = "var(--col-modified)" + //cell.style.backgroundColor = "var(--col-modified)" + handleHighlight("userModified",null,cell) } } + //find internal users and highlight them + if ('ResponsibleFullName' in item) { + handleHighlight("person",item.ResponsibleFullName.txt,item.ResponsibleFullName.cell) + } else if ('Responsibility' in item) { + handleHighlight("person",item.Responsibility.txt,item.Responsibility.cell) + } + //find inline highlights for tasks if ('Title' in item) { let title = item.Title - for (const [key,color] of Object.entries(colorsByStatus)) { - let re = color.re.exec(title.txt) - if (re) { - let newTitle = re[1] - - let link = title.cell.querySelector("a") - if (link) { - title.cell.style.backgroundColor = color.style.background - link.innerText = newTitle - } - - //reset regex - color.re.lastIndex = 0 - } - } + handleHighlight("title",title.txt,title.cell) } //find links & open in new tab, if configured @@ -526,6 +636,92 @@ } + function handleHighlight(type, txt, element) { + + var re + var style = null + + for (const [key,color] of Object.entries(colorsByStatus)) { + re = color.re.exec(txt) + if (re) { + style = color.style + break + } + } + + const customHighlights = settings('get','customHighlights') || [] + for (const customHighlight of customHighlights) { + let customType = customHighlight.type + if (customType=="highlight") { + let char = customHighlight.value + let reg = new RegExp(`\\${char} (.*) \\${char}`,"g") + re = reg.exec(txt) + if (re) { + style = customHighlight.style + break + } + } + + if (type=="reply" && customType=="reply") { + style = customHighlight.style + } + + if (type=="dateModified" && customType=="dateModified") { + style = customHighlight.style + if (style.background) { + let a = Math.floor(txt * 255).toString(16); + style.background = style.background + a + } + } + + if (type=="userModified" && customType=="userModified") { + style = customHighlight.style + } + + if (type=="report" && customType=="report") { + if (customHighlight.value==txt) { + style = customHighlight.style + } + } + + if (type=="person" && customType=="person") { + if (customHighlight.value==txt) { + element = createHighlightBubble(element) + style = customHighlight.style + } + } + } + + if (style) { + //console.log("Apply custom highlight:",txt) + + let link = element.querySelector("a") + if (re && link) { + let newTitle = re[1] + link.innerText = newTitle + //reset regex + re.lastIndex = 0 + } + + for (const [attr,val] of Object.entries(style)) { + element.style[attr] = val + } + //element.style.backgroundColor = style.background + + } else { + //apply defaults + if (type=="reply") { + element.style.backgroundColor = "var(--col-reply)"; + } + if (type=="userModified") { + element.style.backgroundColor = "var(--col-modified)"; + } + if (type=="dateModified") { + element.style.backgroundColor = `rgba(255,0,0,${txt}`; + } + } + } + function generatePopup(href,w,h,l,t,source) { } @@ -789,6 +985,13 @@ +
Custom Highlights
+
+
+ + +
+

Link Behavior

@@ -830,6 +1033,19 @@ form.addEventListener("change", (event)=>{ settings("update",form) }); + form.addEventListener("submit", (event)=>{ + event.preventDefault() + }); + + //send/recieve data from popup + let customColorButton = settingsPage.querySelector("#colorPopup") + customColorButton.addEventListener("click", (event)=>{ + let url = `${tdxtoolsUrl}/ecnuds/tdx/userscript/coloreditor` + let colorWindow = window.open(url,"TDX Color Editor","popup=true") + colorWindow.onload = function() { + colorWindow.postMessage({msg:"here's the data"},"*") + } + }); //links let parent = iconBar.parentElement @@ -844,7 +1060,10 @@ let statusTime = statusLink.querySelector("#statusTime") let statusText = statusLink.querySelector("#statusText") - let operationalText = "All Systems Operational" + let operationalText = "All systems operational." + //check the first 5 words. do they contain operational or green? + //since the default message can vary sometimes + let operationalRegex = /^(?=(?:\w+\s){0,5}?(?:operational|green)\b(?!-))(\w+(?:\s\w+){0,5})/gm if (statusData) { if (statusData.msg.length==0) { @@ -856,7 +1075,7 @@ } } - if (!statusData.msg == operationalText) { + if (!operationalRegex.test(statusData.msg)) { statusIcon.classList = ("fa-solid fa-triangle-exclamation") } else { statusIcon.classList = ("fa-solid fa-check") @@ -896,15 +1115,22 @@ var menu try { data = JSON.parse(localStorage.getItem("userSettings")) + if (!data) { throw "Data is null" + } else { + //resolve stringified objects + if ('customHighlights' in data) { + data.customHighlights = JSON.parse(data.customHighlights) + } } } catch(e) { - console.error("Couldn't grab settings!",e) + //console.error("Couldn't grab settings!",e) //set defaults data = { colorMode: getColorMode(), - linkBehavior: "tabs" + linkBehavior: "tabs", + customHighlights: [], } } @@ -913,6 +1139,11 @@ for (const setting in data) { let form = document.forms.settingsForm let value = data[setting] + + if (typeof value == "object") { + value = JSON.stringify(value) + } + console.log("Apply setting:",setting,value) if (form) { let elements = form.elements @@ -932,6 +1163,10 @@ setColors('theme',data.theme) } } + + if ('customHighlights' in data) { + //parseCustomHighlights(data.highlightData) + } break } @@ -954,7 +1189,7 @@ } case 'update': { - let form = value + let form = document.querySelector("#settingsForm") let formData = new FormData(form) data = Object.fromEntries([...formData]) console.log("Settings update!",data) @@ -964,6 +1199,10 @@ } } + function parseCustomHighlights(data) { + console.log("Custom highlights:",data) + } + /* TDX TOOLS */ function checkPath() { let path = document.location.pathname @@ -972,12 +1211,30 @@ if (pre) { console.log("Got key!",pre.innerText) let key = pre.innerText - window.opener.postMessage({tdxkey:key},"https://engineering.purdue.edu") + window.opener.postMessage({tdxkey:key},tdxtoolsUrl) window.close() } } } + window.addEventListener("message",(event) => { + let data = event.data + let origin = event.origin + let trgt = event.currentTarget + console.log("Message from:",origin,data,event) + + if (data.colorsReady) { + let colors = settings('get','customHighlights') + event.source.postMessage({ customColors: colors }, "*") + } + if (data.colorData) { + let element = document.getElementById("customHighlights") + let strData = JSON.stringify(data.colorData) + element.value = strData + settings('update') + } + },false); + changeTitle() //checkColorMode() settings("apply") @@ -1036,10 +1293,10 @@ --dark-txt-5: var(--light-txt-1); --dark-border-0: color-mix(in srgb,var(--dark-bg-primary),#fff 18%); --dark-col-reply: #1e3438; - --dark-col-highlight-1: #625714; - --dark-col-highlight-2: #311462; + --dark-col-highlight-1: #ffdb0059; + --dark-col-highlight-2: #5f00ff5e; --dark-col-currentuser: #09482f; - --dark-col-modified: #30241c; + --dark-col-modified: #ff660012; --dark-txt-invert: 1; --dark-txt-hue: 180deg; @@ -1088,10 +1345,10 @@ --txt-hue: var(--light-txt-hue); --border-0: var(--light-border-0); --col-reply: var(--light-col-reply); - --col-highlight-1: var(--light-col-highlight-1); - --col-highlight-2: var(--light-col-highlight-2); + --col-highlight-1: var(--dark-col-highlight-1); + --col-highlight-2: var(--dark-col-highlight-2); --col-currentuser: var(--light-col-currentuser); - --col-modified: var(--light-col-modified); + --col-modified: var(--dark-col-modified); } } @@ -1172,10 +1429,10 @@ --txt-hue: var(--light-txt-hue); --border-0: var(--light-border-0); --col-reply: var(--light-col-reply); - --col-highlight-1: var(--light-col-highlight-1); - --col-highlight-2: var(--light-col-highlight-2); + --col-highlight-1: var(--dark-col-highlight-1); + --col-highlight-2: var(--dark-col-highlight-2); --col-currentuser: var(--light-col-currentuser); - --col-modified: var(--light-col-modified); + --col-modified: var(--dark-col-modified); } /* Global */ @@ -1205,6 +1462,37 @@ gap: 24px; } +#settingsForm .textBox { + display: flex; + flex-direction: column; + gap: 8px; +} + +.customEmbed { + border: 0; + width: 100%; + height: 400px; +} + +.calBox { + display: flex; + justify-content: space-around; + flex-wrap: wrap; + padding-top: 4px; + gap: 4px; + max-width: 220px; +} + +.btn-calendar { + flex-grow: 1; + padding: 2px; + background-color: var(--bg-3); +} + +.btn-calendar:hover { + background-color: var(--bg-4); +} + #statusBox:hover #statusMsg { display: block; width: 400px; @@ -1239,6 +1527,7 @@ .qHighlight { padding: 4px; border-radius: 8px; + white-space: nowrap; } .select2-container-active .select2-choice, .select2-container-multi.select2-container-active .select2-choices { @@ -1334,7 +1623,7 @@ a:focus, a:hover { .label-success { background-color: var(--col-0); - /*color: var(--txt-0);*/ + color: var(--txt-5); } .green, .green-hover:hover { @@ -1398,7 +1687,7 @@ a:focus, a:hover { .select2-results .select2-no-results,.select2-results .select2-searching,.select2-results .select2-selection-limit { background-color: var(--col-2); - color: var(--txt-2); + color: var(--txt-5); } .select2-results .select2-highlighted { @@ -1556,6 +1845,10 @@ ul.dropdown-menu .btn.btn.btn-link[data-v-0b9084d2]:hover { color: var(--txt-2); } +.well { + background-color: var(--bg-3); +} + /* Replies */ #ttDescription, .feed-item-text { /* color: var(--txt-2); */ @@ -1708,7 +2001,7 @@ ul.nav-pills li.dropdown button.dropdown-toggle:hover { } .btn-danger { - + color: var(--txt-5) !important; } .btn-primary { @@ -1746,6 +2039,10 @@ ul.nav-pills li.dropdown button.dropdown-toggle:hover { background-color: var(--bg-1); } +.checkbox input[type=checkbox], .checkbox-inline input[type=checkbox], .radio input[type=radio], .radio-inline input[type=radio] { + accent-color: var(--col-1); +} + /* Oddly grouped headers */ .h3, h3 { color: var(--txt-2); @@ -1816,6 +2113,10 @@ table>thead>tr.TDGridHeader>th, tr.TDGridHeader td, .table-header>div { color: var(--txt-2); } +table>tbody>tr.TDGridHeader>th, table>thead>tr.TDGridHeader>th, tr.TDGridHeader td { + color: var(--txt-3); +} + .TDFooterRow { background-color: var(--bg-1); } @@ -1936,6 +2237,11 @@ hr { border-color: var(--border-0); } +table>tbody>tr.TDGridHeader>th, table>thead>tr.TDGridHeader>th, tr.TDGridHeader td { + border-top: 1px solid var(--border-0); + border-bottom: 1px solid var(--border-0); +} + .border-top, tr.TDGridHeader { border-top: 1px solid var(--border-0); } @@ -2153,6 +2459,10 @@ div.feed-entry { border-bottom: 1px solid var(--border-0) !important; } +.well { + border: 1px solid var(--border-0); +} + `