dark mode fixes

This commit is contained in:
Guy Sandler 2026-09-01 22:01:18 -07:00
parent 2db66dbaed
commit 5447891608
2 changed files with 116 additions and 0 deletions

View File

@ -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;
}
`;

View File

@ -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();
}