diff --git a/css/darkmodecss.js b/css/darkmodecss.js index 2d01238..e0f810f 100644 --- a/css/darkmodecss.js +++ b/css/darkmodecss.js @@ -1435,4 +1435,30 @@ div[class*='view-tabs__container']:has(#currentTab, #pastTab), .notification_account_content a { color: var(--bclinks) !important; } +/* Grades page: opening the comment tray marks the row with + .selected-assignment, which Canvas paints light blue (#e0ebf5) with a + .3s background transition — reads as a white row in dark mode. Repaint + it as the theme's raised surface so the highlight stays visible but + dark. */ +#grades_summary tr.selected-assignment { + background-color: var(--bcbackground-2) !important; +} +/* Grades page (…/courses/:id/grades): the comment chip in each assignment + row (speech-bubble icon + count, .toggle_comments_link) keeps Canvas's + light-gray pill background and border in dark mode; repaint it as a + theme chip. */ +.toggle_comments_link, +.toggle_comments_link:hover { + background: var(--bcbackground-2) !important; + border-color: var(--bcborders) !important; +} +/* Grades page "late"/"missing" status pills: InstUI renders the inner pill + as a white chip with colored text. The emotion class hashes change between + Canvas releases, so match the stable "-pill" fragment scoped under the + server-rendered status cell. */ +td.status [class*="-pill"], +td.status [class*="-pill"]:hover { + background: var(--bcbackground-2) !important; + color: var(--bctext-1) !important; +} `; \ No newline at end of file diff --git a/js/content.js b/js/content.js index dde20a5..5102fe6 100644 --- a/js/content.js +++ b/js/content.js @@ -4843,6 +4843,88 @@ function generateDarkModeCSS() { } let darkStyleInserted = false; +// --- Submission tray dark-mode fixer --------------------------------------- +// The comment chip on the grades page (…/grades) opens an InstUI +// "Submission Comments Tray". Its "Attempt N Feedback" card, close-button +// glyph, and some labels arrive with Canvas's light surface and dark ink +// through emotion classes whose hashes change between Canvas releases, so +// they can't be themed from the static dark stylesheet. Instead, watch for +// the tray and recolor at runtime, scoped strictly to elements inside it. +// Touched elements are tagged (data-crdarkfix) so turning dark mode off can +// restore the original colors. +let submissionTrayObserver = null; +let submissionTrayFixScheduled = false; + +function crApplySubmissionTrayFix(el, props) { + const applied = el.dataset.crdarkfix ? el.dataset.crdarkfix.split(" ") : []; + for (const prop of Object.keys(props)) { + if (!applied.includes(prop)) applied.push(prop); + el.style.setProperty(prop, props[prop], "important"); + } + el.dataset.crdarkfix = applied.join(" "); +} + +function fixSubmissionTrayColors(root) { + const preset = options.dark_preset; + if (!preset) return; + const light = (r, g, b) => r >= 225 && g >= 225 && b >= 225; + const darkInk = (r, g, b) => r <= 70 && g <= 70 && b <= 70; + const rgb = (str) => { const m = str && str.match(/rgb\((\d+), ?(\d+), ?(\d+)/); return m ? [+m[1], +m[2], +m[3]] : null; }; + root.querySelectorAll("*").forEach(el => { + if (el instanceof HTMLImageElement || el instanceof HTMLVideoElement) return; + const cs = getComputedStyle(el); + const bg = rgb(cs.backgroundColor); + if (bg && light(bg[0], bg[1], bg[2])) { + // light card/surface -> theme surface with readable text + crApplySubmissionTrayFix(el, { + "background-color": preset["background-2"], + "color": preset["text-0"] + }); + } else { + const c = rgb(cs.color); + if (c && darkInk(c[0], c[1], c[2])) { + // dark ink on dark tray (headings, icon glyphs) + crApplySubmissionTrayFix(el, { "color": preset["text-0"] }); + } + } + const bc = rgb(cs.borderColor); + if (bc && light(bc[0], bc[1], bc[2])) { + crApplySubmissionTrayFix(el, { "border-color": preset["borders"] }); + } + }); +} + +function setupSubmissionTrayWatcher() { + if (submissionTrayObserver) return; + const run = () => { + submissionTrayFixScheduled = false; + if (options.dark_mode !== true && options.device_dark !== true) return; + const tray = document.querySelector('[data-testid="submission-tray"]'); + if (tray) fixSubmissionTrayColors(tray); + }; + // Canvas mutates the DOM constantly; debounce to one run per frame (same + // pattern as the footer observer in startExtension). Comments load into + // the tray asynchronously, so re-run whenever the tray subtree changes. + submissionTrayObserver = new MutationObserver(() => { + if (submissionTrayFixScheduled) return; + submissionTrayFixScheduled = true; + requestAnimationFrame(run); + }); + submissionTrayObserver.observe(document.documentElement, { childList: true, subtree: true }); + run(); +} + +function teardownSubmissionTrayFixes() { + if (submissionTrayObserver) { + submissionTrayObserver.disconnect(); + submissionTrayObserver = null; + } + document.querySelectorAll("[data-crdarkfix]").forEach(el => { + (el.dataset.crdarkfix || "").split(" ").forEach(prop => el.style.removeProperty(prop)); + delete el.dataset.crdarkfix; + }); +} + function toggleDarkMode() { const css = generateDarkModeCSS(); const darkOn = options.dark_mode === true || options.device_dark === true; @@ -4857,6 +4939,14 @@ function toggleDarkMode() { style.textContent = css; style.className = darkOn ? "canvasrefined-darkmode-enabled" : ""; darkStyleInserted = true; + // The InstUI submission tray is themed at runtime (emotion class hashes + // change between Canvas releases); keep the watcher in sync with the + // current mode, and undo inline fixes when dark mode turns off. + if (darkOn) { + setupSubmissionTrayWatcher(); + } else { + teardownSubmissionTrayFixes(); + } runiframeChecker(); }