From 1942a85f4b028fa92090e075822ad28d29cc8410 Mon Sep 17 00:00:00 2001 From: Ling Zheng Date: Wed, 5 Nov 2025 17:04:58 -0500 Subject: [PATCH] its wraps dude --- README.md | 1 + content/content.js | 22 +++-- content/modules/color.js | 1 - content/modules/highlighting.js | 76 +++++++++------- content/modules/observer.js | 14 ++- content/modules/parsing.js | 151 +++++++++++++++++++++----------- content/modules/settings.js | 39 ++++++--- content/modules/ui.js | 104 +++++++++++++--------- content/modules/utils.js | 29 ++++-- manifest.json | 2 +- 10 files changed, 287 insertions(+), 152 deletions(-) diff --git a/README.md b/README.md index 96cfdec..46db812 100644 --- a/README.md +++ b/README.md @@ -228,3 +228,4 @@ Both are included as minified files in the `lib/` directory. 4. Test thoroughly on TDX pages 5. Submit a pull request + diff --git a/content/content.js b/content/content.js index 525d998..720a6cb 100644 --- a/content/content.js +++ b/content/content.js @@ -57,20 +57,30 @@ // Listen for messages from popup windows (color editor) window.addEventListener("message", (event) => { + // Only accept messages from trusted origin + if (event.origin !== tdxtoolsUrl) { + return + } + let data = event.data - let origin = event.origin - console.log("Message from:", origin, data, event) + if (!data) { + return + } if (data.colorsReady) { let colors = settings('get', 'customHighlights') - event.source.postMessage({ customColors: colors }, "*") + event.source.postMessage({ customColors: colors }, tdxtoolsUrl) } if (data.colorData) { let element = document.getElementById("customHighlights") if (element) { - let strData = JSON.stringify(data.colorData) - element.value = strData - settings('update') + try { + let strData = JSON.stringify(data.colorData) + element.value = strData + settings('update') + } catch (e) { + console.warn("Failed to process color data:", e) + } } } }, false); diff --git a/content/modules/color.js b/content/modules/color.js index 230061c..a98f7ba 100644 --- a/content/modules/color.js +++ b/content/modules/color.js @@ -338,7 +338,6 @@ function getFilterForColor(rgbColor) { } } - console.log("Total iterations: ", iterationCount); // Log the total iterations return bestResult; } diff --git a/content/modules/highlighting.js b/content/modules/highlighting.js index d88e68c..7473740 100644 --- a/content/modules/highlighting.js +++ b/content/modules/highlighting.js @@ -19,60 +19,74 @@ 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 + if (typeof txt === "string") { + for (const [key, color] of Object.entries(colorsByStatus)) { + // Reset regex lastIndex to avoid state issues + color.re.lastIndex = 0 + 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") { + 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 (char && typeof txt === "string") { + try { + let reg = new RegExp(`\\${char} (.*) \\${char}`, "g") + re = reg.exec(txt) + if (re) { + style = customHighlight.style + break + } + } catch (e) { + console.warn("Invalid highlight pattern:", char, e) + } } } - if (type == "reply" && customType == "reply") { + 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 === "dateModified" && customType === "dateModified") { + style = Object.assign({}, customHighlight.style) // Create copy to avoid modifying original + if (style.background && typeof txt === "number") { + let alpha = Math.floor(txt * 255).toString(16).padStart(2, '0') + style.background = style.background + alpha } } - if (type == "userModified" && customType == "userModified") { + if (type === "userModified" && customType === "userModified") { style = customHighlight.style } - if (type == "report" && customType == "report") { - if (customHighlight.value == txt) { + if (type === "report" && customType === "report") { + if (customHighlight.value === txt) { element.classList.add("reportTitle") style = customHighlight.style } } - if (type == "report" && customType == "report-regex") { - if ((new RegExp(customHighlight.value).exec(txt))) { - element.classList.add("reportTitle") - style = customHighlight.style + if (type === "report" && customType === "report-regex") { + try { + if ((new RegExp(customHighlight.value).test(txt))) { + element.classList.add("reportTitle") + style = customHighlight.style + } + } catch (e) { + console.warn("Invalid regex pattern for report highlight:", customHighlight.value, e) } } - if (type == "person" && customType == "person") { - if (customHighlight.value == txt) { - element = createHighlightBubble(element) + if (type === "person" && customType === "person") { + if (customHighlight.value === txt) { + element = createHighlightBubble(element, style?.background || "var(--col-highlight-1)", style?.color || "inherit") style = customHighlight.style } } @@ -105,14 +119,14 @@ function handleHighlight(type, txt, element) { } else { //apply defaults - if (type == "reply") { + if (type === "reply") { element.style.backgroundColor = "var(--col-reply)"; } - if (type == "userModified") { + if (type === "userModified") { element.style.backgroundColor = "var(--col-modified)"; } - if (type == "dateModified") { - element.style.backgroundColor = `rgba(255,0,0,${txt}`; + if (type === "dateModified") { + element.style.backgroundColor = `rgba(255,0,0,${txt})`; } } } diff --git a/content/modules/observer.js b/content/modules/observer.js index 917f9ef..f77c524 100644 --- a/content/modules/observer.js +++ b/content/modules/observer.js @@ -46,12 +46,22 @@ let observer = new MutationObserver((mutations) => { }); }); -if (true) { - console.log("Observing...") +// Start observing DOM changes +if (document.body) { observer.observe(document.body, { characterDataOldValue: true, subtree: true, childList: true, characterData: true }); +} else { + // Wait for body to be available + document.addEventListener('DOMContentLoaded', () => { + observer.observe(document.body, { + characterDataOldValue: true, + subtree: true, + childList: true, + characterData: true + }); + }); } diff --git a/content/modules/parsing.js b/content/modules/parsing.js index f677d90..8caff99 100644 --- a/content/modules/parsing.js +++ b/content/modules/parsing.js @@ -5,9 +5,7 @@ function updateHeading(mutation) { for (const heading of headings) { //color the header if queue name is found - let headingTxt = heading.innerText - let headingFirst = headingTxt.split(" ")[0] - headingFirst = headingFirst.toLowerCase() + let headingTxt = heading.innerText || heading.textContent handleHighlight("report", headingTxt, heading) @@ -38,18 +36,29 @@ function parseTicket(mutation = null) { //find calendar inputs when editing tickets/tasks let calendars = document.querySelectorAll("#txtStartDate, #txtEndDate") //tasks for now, will add more as found - if (calendars) { + if (calendars && calendars.length > 0) { let originalFormat = "M/D/YYYY h:mm A"; let newFormat = "YYYY-MM-DDTHH:mm"; [...calendars].forEach(calendar => { - let date = window.moment(calendar.value, originalFormat) + let date = window.moment(calendar.value, originalFormat) let newCal = document.createElement("input") newCal.id = `${calendar.id}-new` newCal.classList = calendar.classList newCal.type = "datetime-local" + //date modifier shortcuts + let calBox = document.createElement("div") + calBox.classList = ["calBox"] + let calTxt = document.createElement("span") + if (date && date.isValid()) { + calTxt.textContent = date.format('dddd') + } + function updateDate() { + if (!date || !date.isValid()) { + return + } //update old tdx calendar let parsedFormat = date.format(originalFormat) calendar.value = parsedFormat @@ -57,27 +66,26 @@ function parseTicket(mutation = null) { //update new calendar let iso = date.format(newFormat) newCal.value = iso - calTxt.innerText = date.calendar() - - console.log("Cal update!", date) + if (calTxt) { + calTxt.textContent = date.calendar() + } } //convert to original format newCal.addEventListener("input", event => { let parsedValue = window.moment(event.target.value, newFormat) - date = parsedValue - updateDate() + if (parsedValue && parsedValue.isValid()) { + 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 (!date || !date.isValid()) { + return + } + if ('add' in btn) { date.add(btn.add) } else if ('set' in btn) { @@ -88,21 +96,18 @@ function parseTicket(mutation = null) { 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))) { + if (day >= 5 && (hour > 17 || (hour === 17 && min > 0))) { date.set({ h: 8, - d: 1 + 7, + d: date.date() + 7, m: 0 }) } else if (day > 5) { //if sat/sun date.set({ - d: 1 + 7 + d: date.date() + 7 }) } @@ -147,19 +152,29 @@ function parseTicket(mutation = null) { } //add queue tag to ticket title - if (qBlock && !document.querySelector(".qBox")) { + if (qBlock && header && !document.querySelector(".qBox")) { let qElement = qBlock.querySelector("span.wrap-text") - let qTxt = qElement.innerText + if (!qElement) { + return + } + + let qTxt = (qElement.innerText || qElement.textContent || "").trim() + + if (!qTxt) { + return + } let newBox = document.createElement("span") - newBox.innerText = qTxt + newBox.textContent = qTxt newBox.classList.add("qBox") + let qTxtLower = qTxt.toLowerCase() for (const qK of Object.keys(colorsByQueue)) { let q = colorsByQueue[qK] - if (qTxt.toLowerCase() == qK) { + if (qTxtLower === qK) { newBox.style.color = q.txt newBox.style.backgroundColor = q.bg + break } } @@ -251,9 +266,7 @@ function parseOtherElements() { let col3 = desktop.querySelector("#Column3") //100% / 33-66% - if (desktopLayout == "1_100-66-33" && col2 && col3) { - console.log("Applying layout:", desktopLayout) - + if (desktopLayout === "1_100-66-33" && col2 && col3) { //define width classes let col2WidthClass = "col-md-8" let col3WidthClass = "col-md-4" @@ -261,7 +274,6 @@ function parseOtherElements() { //resize column 2 for (const cls of col2.classList) { if (cls.startsWith("col-md")) { - console.log("Found col2 width class:", cls) col2.classList.remove(cls) col2.classList.add(col2WidthClass) break @@ -330,10 +342,10 @@ function parseTable(element) { //modify/color the cells function parseItem(item) { //color queue names - if ('CSS Support' in item) { + if ('CSS Support' in item && item["CSS Support"]) { let qCell = item["CSS Support"].cell - let qTxt = item["CSS Support"].txt.toLowerCase() - if (qTxt in colorsByQueue) { + let qTxt = (item["CSS Support"].txt || "").toLowerCase().trim() + if (qTxt && qCell && qTxt in colorsByQueue) { let q = colorsByQueue[qTxt] createHighlightBubble(qCell, q.bg, q.txt) } @@ -346,6 +358,21 @@ function parseItem(item) { let dTxt = item[dType].txt let dCell = item[dType].cell + if (!dTxt || !dCell) { + continue + } + + // Skip if already a relative time string (e.g., "3 hours ago", "in 2 days", "a few seconds ago") + // This prevents trying to parse already-converted relative times + // Check for common relative time indicators + let txtTrimmed = dTxt.trim().toLowerCase() + let isRelativeTime = txtTrimmed.endsWith("ago") || + txtTrimmed.includes("from now") || + txtTrimmed.match(/^(a |an |in )?(few |\d+)\s*(second|minute|hour|day|week|month|year)s?\s*(ago|from now)$/i) + if (isRelativeTime) { + continue + } + // Try parsing with common formats first let date = window.moment(dTxt, [ "ddd M/D/YY h:mm A", @@ -356,16 +383,20 @@ function parseItem(item) { "YYYY-MM-DDTHH:mm:ss" ], true) - // If parsing fails, try without strict mode + // Only try loose parsing if strict parsing failed and it doesn't look like a relative time if (!date.isValid()) { - date = window.moment(dTxt) + // Check if it looks like a date format before trying loose parsing + // Skip if it's clearly not a date (e.g., relative time strings) + if (!relativeTimePattern.test(dTxt.trim())) { + date = window.moment(dTxt) + } } - let dTxtNew = date.fromNow() - - if (dTxtNew == "Invalid date" || !date.isValid()) { - } else { - dCell.innerText = dTxtNew + if (date.isValid()) { + let dTxtNew = date.fromNow() + if (dTxtNew && dTxtNew !== "Invalid date") { + dCell.textContent = dTxtNew + } } } } @@ -391,11 +422,11 @@ function parseItem(item) { date = window.moment(dTxt) } - let dTxtNew = date.calendar() - - if (dTxtNew == "Invalid date" || !date.isValid()) { - } else { - dCell.innerText = dTxtNew + if (date.isValid()) { + let dTxtNew = date.calendar() + if (dTxtNew && dTxtNew !== "Invalid date") { + dCell.textContent = dTxtNew + } } } } @@ -405,8 +436,21 @@ function parseItem(item) { for (const dType of modifiedDates) { if (dType in item) { let modDate = item[dType] + if (!modDate || !modDate.txt || !modDate.cell) { + continue + } + const ageThreshold = 336 + // Skip if already a relative time string - we can't calculate age from it + let txtTrimmed = modDate.txt.trim().toLowerCase() + let isRelativeTime = txtTrimmed.endsWith("ago") || + txtTrimmed.includes("from now") || + txtTrimmed.match(/^(a |an |in )?(few |\d+)\s*(second|minute|hour|day|week|month|year)s?\s*(ago|from now)$/i) + if (isRelativeTime) { + continue + } + // Try parsing with common formats first let date = window.moment(modDate.txt, [ "ddd M/D/YY h:mm A", @@ -417,9 +461,16 @@ function parseItem(item) { "YYYY-MM-DDTHH:mm:ss" ], true) - // If parsing fails, try without strict mode + // Only try loose parsing if strict parsing failed and it doesn't look like a relative time + if (!date.isValid()) { + if (!isRelativeTime) { + date = window.moment(modDate.txt) + } + } + + // Only highlight if we have a valid date if (!date.isValid()) { - date = window.moment(modDate.txt) + continue } let duration = window.moment.duration(window.moment().diff(date)) @@ -441,12 +492,12 @@ function parseItem(item) { let assignedTo = item["Prim Resp"] //reply from user - if (fromUser.txt == lastModified.txt && fromUser.txt != assignedTo.txt && assignedTo.txt != "Unassigned") { + if (fromUser.txt === lastModified.txt && fromUser.txt !== assignedTo.txt && assignedTo.txt !== "Unassigned") { handleHighlight("reply", null, item.row) } //modified by internal - if (lastModified.txt != fromUser.txt && lastModified.txt != assignedTo.txt) { + if (lastModified.txt !== fromUser.txt && lastModified.txt !== assignedTo.txt) { let cell = lastModified.cell handleHighlight("userModified", null, cell) } diff --git a/content/modules/settings.js b/content/modules/settings.js index 9abd0ee..11c1b6d 100644 --- a/content/modules/settings.js +++ b/content/modules/settings.js @@ -34,16 +34,16 @@ function settings(action, value = null) { value = JSON.stringify(value) } - console.log("Apply setting:", setting, value) try { - if (form) { - let elements = form.elements + if (form && form[setting]) { form[setting].value = value - if (form[setting].type == "checkbox") { - form[setting].checked = value == "enabled" ? true : false + if (form[setting].type === "checkbox") { + form[setting].checked = value === "enabled" } } - } catch {} + } catch (e) { + // Setting may not exist in form, ignore + } } if ('colorMode' in data) { @@ -72,8 +72,11 @@ function settings(action, value = null) { case 'toggle': { let menu = document.querySelector("#settingsMenu") + if (!menu) { + break + } let newState = menu.classList.contains("show") ? "hide" : "show" - let oldState = newState == "show" ? "hide" : "show" + let oldState = newState === "show" ? "hide" : "show" menu.classList.add(newState) menu.classList.remove(oldState) break @@ -81,16 +84,24 @@ function settings(action, value = null) { case 'update': { let form = document.querySelector("#settingsForm") - let formData = new FormData(form) - data = Object.fromEntries([...formData]) - console.log("Settings update!", data) - localStorage.setItem("userSettings", JSON.stringify(data)) - settings("apply") - setCssFilters() + if (!form) { + break + } + try { + let formData = new FormData(form) + data = Object.fromEntries([...formData]) + localStorage.setItem("userSettings", JSON.stringify(data)) + settings("apply") + setCssFilters() + } catch (e) { + console.warn("Failed to update settings:", e) + } } } } function parseCustomHighlights(data) { - console.log("Custom highlights:", data) + // Legacy function - kept for compatibility + // Custom highlights are now handled directly in highlighting.js } + diff --git a/content/modules/ui.js b/content/modules/ui.js index ddc5c2e..977068c 100644 --- a/content/modules/ui.js +++ b/content/modules/ui.js @@ -37,7 +37,7 @@ async function injectToolbar() { let settingsHTML = `