From f5ad81ffa410560ec83bf63c97bddf23ba79b85e Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Thu, 15 Jan 2026 08:28:25 -0600 Subject: [PATCH] Add dark mode support. The color scheme (dark or light) that is used is automatically detected from the browser's settings initially, but can be set via a color scheme chooser in the page header. PG problems are still rendered in light mode regardless of the color mode for the rest of the page. Converting PG to honor dark mode will take some work. Note that there are some small changes needed for the image view dialog and knowls of PG to make sure that those dialogs render in light mode. There is also a minor change to make the scaffold buttons stay in light mode as well. Note that there were some issues with light mode (i.e., the existing themes). The contrast of the links in the site nav were not good when they were focused or hovered over with the mouse. This was due to an override of the link focus styles that made the colors lighter in those cases. That was needed for links in the masthead (the page header), but generally was bad elsewhere. So instead this uses the Bootstrap defaults for the hover/active colors which darkens the colors instead of lightening them. So now there is just a special case for links in the masthead, and the other links use bootstraps default colors which gives higher contrast in the site nav. One side advantage of the above approach since it darkens links on hover and focus instead of lightening them is that I can finally switch to a link color for the math4-yellow theme that is not off theme. Previously it was a reddish color that I never really like in the scope of the theme, but was needed for contrast. Now it uses a rather dark yellow and gives a more consistent feel for the theme. The MathJax `no-dark-mode` extension has been converted into a `bs-color-scheme` extension which rewrites the MathJax styles to honor the `data-bs-theme` value instead of using media queries for the browser mode. This means that MathJax will display in the correct mode wherever the element is in the page. For example, on a problem page the math in the problem will always be in light mode. On the problem grader page the math in the problem there will also be in light mode, but the formula student answers will be in dark mode if the page is set to that. Note that the MathJax dialogs will always be in dark mode if the page is set to that since those are injected outside of the problem content div. The menu will also always be dark mode in a problem if the page is set to that since there isn't an override for the menu in the `data-bs-theme` extension. Its styles work differently. Note that there was a small change needed for the PG CodeMirror editor to give its default light theme a white background. This was done in the `pg-codemirror-editor` repository, and the new npm package published and included in this pull request. I am sure that I missed some colors that need to be adjusted in dark mode and perhaps issues with forcing PG into light mode, but I can't find anything right now. So please check this carefully. The `README` files in the theme directories have been deleted, and replaced with a single `README.md` file in the parent `htdocs/themes` directory. It has instructions on how to create a custom theme, and documents the Sass and CSS variables that can be set. There is a small change in `ConfigValues.pm` to only list directories when listing theme directories, and to skip this `README.md` file. --- htdocs/js/GatewayQuiz/gateway.scss | 25 +- htdocs/js/MathJaxConfig/bs-color-scheme.js | 96 ++++++ htdocs/js/MathJaxConfig/mathjax-config.js | 4 +- htdocs/js/MathJaxConfig/no-dark-mode.js | 63 ---- htdocs/js/PGCodeMirror/pgeditor.scss | 4 +- htdocs/js/PGProblemEditor/pgproblemeditor.js | 1 + htdocs/js/RenderProblem/renderproblem.js | 1 + htdocs/js/System/color-scheme.js | 75 +++++ htdocs/js/System/system.scss | 274 +++++++++++++----- htdocs/package-lock.json | 32 +- htdocs/package.json | 2 +- htdocs/themes/README.md | 78 +++++ htdocs/themes/math4-green/README | 8 - htdocs/themes/math4-green/_theme-colors.scss | 1 - htdocs/themes/math4-red/README | 8 - htdocs/themes/math4-red/_theme-colors.scss | 1 + htdocs/themes/math4-red/_theme-overrides.scss | 3 - htdocs/themes/math4-yellow/README | 8 - htdocs/themes/math4-yellow/_theme-colors.scss | 9 +- .../themes/math4-yellow/_theme-overrides.scss | 28 +- htdocs/themes/math4/README | 18 -- htdocs/themes/math4/_theme-colors.scss | 2 +- htdocs/themes/math4/bootstrap.scss | 49 +++- lib/FormatRenderedProblem.pm | 11 +- lib/WeBWorK/ConfigValues.pm | 4 +- lib/WeBWorK/ContentGenerator.pm | 4 +- .../ContentGenerator/Instructor/Stats.pm | 1 + lib/WeBWorK/ContentGenerator/Problem.pm | 18 +- .../Base/login_status.html.ep | 25 +- .../ContentGenerator/GatewayQuiz.html.ep | 16 +- .../Instructor/ProblemGrader.html.ep | 17 +- .../Instructor/ProblemSetDetail.html.ep | 9 +- .../Instructor/ProblemSetList.html.ep | 1 + .../Instructor/SetMaker/problem_row.html.ep | 12 +- .../ShowAnswers/past-answers-table.html.ep | 13 +- .../Instructor/Stats/problem_menu.html.ep | 5 +- .../Stats/student_filter_menu.html.ep | 2 +- .../Instructor/UserDetail.html.ep | 1 + templates/HTML/StudentNav/student_nav.html.ep | 8 +- templates/layouts/system.html.ep | 54 +++- 40 files changed, 714 insertions(+), 277 deletions(-) create mode 100644 htdocs/js/MathJaxConfig/bs-color-scheme.js delete mode 100644 htdocs/js/MathJaxConfig/no-dark-mode.js create mode 100644 htdocs/js/System/color-scheme.js create mode 100644 htdocs/themes/README.md delete mode 100644 htdocs/themes/math4-green/README delete mode 100644 htdocs/themes/math4-red/README delete mode 100644 htdocs/themes/math4-yellow/README delete mode 100644 htdocs/themes/math4/README diff --git a/htdocs/js/GatewayQuiz/gateway.scss b/htdocs/js/GatewayQuiz/gateway.scss index 6d4b7ead5e..bb294426eb 100644 --- a/htdocs/js/GatewayQuiz/gateway.scss +++ b/htdocs/js/GatewayQuiz/gateway.scss @@ -1,13 +1,5 @@ /* gateway styles */ -div.gwMessage { - background-color: #ffeeaa; - box-shadow: 3px 3px 3px darkgray; - margin: 0 0 1rem 0; - padding: 0.25rem; - border-radius: 3px; -} - #gwTimer { position: sticky; width: 15em; @@ -60,6 +52,11 @@ table.attemptResults { border: 1px solid #ddd; border-radius: 3px; + [data-bs-theme='dark'] & { + border-color: #555; + background-color: var(--bs-primary-bg-subtle, 'black'); + } + h2.gw-problem-number { display: inline-block; font-size: 16px; @@ -85,12 +82,17 @@ table.attemptResults { } colgroup.page { - border-left: solid 1pt black; - border-right: solid 1pt black; + border-left: solid 1pt var(--bs-emphasis-color, black); + border-right: solid 1pt var(--bs-emphasis-color, black); } .page.active { background-color: #ffeeaa; + + [data-bs-theme='dark'] & { + color: white; + background-color: #80690a; + } } } @@ -98,8 +100,9 @@ div.gwDivider { margin: 0px 0px 10px 0px; } -/* Override the pg style so that the problem-content is not offset in gateway quizzes. */ +/* Override the pg style so that the problem-content is not offset in gateway quizzes and force a light color scheme. */ .problem-content { + color-scheme: light; padding: unset; background-color: unset; border: unset; diff --git a/htdocs/js/MathJaxConfig/bs-color-scheme.js b/htdocs/js/MathJaxConfig/bs-color-scheme.js new file mode 100644 index 0000000000..7da8714253 --- /dev/null +++ b/htdocs/js/MathJaxConfig/bs-color-scheme.js @@ -0,0 +1,96 @@ +if (MathJax.loader) MathJax.loader.checkVersion('[bs-color-scheme]', '4.1.0', 'extension'); + +const switchToBSStyle = (obj, key = '@media (prefers-color-scheme: dark)') => { + obj["[data-bs-theme='dark']"] = obj[key]; + delete obj[key]; + obj["[data-bs-theme='light']"] = structuredClone(obj); +}; + +for (const [immediate, extension, ready] of [ + [ + MathJax._.ui?.dialog, + 'core', + () => { + const { DraggableDialog } = MathJax._.ui.dialog.DraggableDialog; + switchToBSStyle(DraggableDialog.styles); + + // This is a workaround for a bug in MathJax 4.1.0. Delete this for the next version of MathJax. + // See https://github.com/mathjax/MathJax-src/pull/1414. + DraggableDialog.styles["[data-bs-theme='dark']"]['.mjx-dialog a[href]'] = + DraggableDialog.styles["[data-bs-theme='dark']"]['a[href]']; + delete DraggableDialog.styles["[data-bs-theme='dark']"]['a[href]']; + DraggableDialog.styles["[data-bs-theme='dark']"]['.mjx-dialog a[href]:visited'] = + DraggableDialog.styles["[data-bs-theme='dark']"]['a[href]:visited']; + delete DraggableDialog.styles["[data-bs-theme='dark']"]['a[href]:visited']; + } + ], + [ + MathJax._.a11y?.explorer, + 'a11y/explorer', + () => { + const Region = MathJax._.a11y.explorer.Region; + for (const region of ['LiveRegion', 'HoverRegion', 'ToolTip']) { + if (':root' in Region[region].style.styles) { + Region[region].style.styles["[data-bs-theme='light']"] = Region[region].style.styles[':root']; + + // The variable --mjx-bg1-color is defined to be 'rgba(var(--mjx-bg-blue), var(--mjx-bg-alpha))'. + // I suspect this is a typo as the variable -mjx-bg-alpha is not defined anywhere. In any case this + // change is needed to get the correct background color on the focused element in the explorer. + Region[region].style.styles["[data-bs-theme='light']"]['--mjx-bg1-color'] = + 'rgba(var(--mjx-bg-blue), var(--mjx-bg1-alpha))'; + } + Region[region].style.styles["[data-bs-theme='dark']"] = + Region[region].style.styles['@media (prefers-color-scheme: dark)']; + if (':root' in Region[region].style.styles["[data-bs-theme='dark']"]) { + Object.assign( + Region[region].style.styles["[data-bs-theme='dark']"], + Region[region].style.styles["[data-bs-theme='dark']"][':root'] + ); + delete Region[region].style.styles["[data-bs-theme='dark']"][':root']; + } + Region[region].style.styles['@media (prefers-color-scheme: dark)'] = {}; + } + Region.LiveRegion.style.styles['@media (prefers-color-scheme: dark)']['mjx-ignore'] = { ignore: 1 }; + MathJax.startup.extendHandler((handler) => { + switchToBSStyle( + handler.documentClass.speechStyles, + '@media (prefers-color-scheme: dark) /* explorer */' + ); + return handler; + }); + } + ], + [ + MathJax._.output?.chtml, + 'output/chtml', + () => { + const { CHTML } = MathJax._.output.chtml_ts; + switchToBSStyle(CHTML); + const { ChtmlMaction } = MathJax._.output.chtml.Wrappers.maction; + switchToBSStyle(ChtmlMaction.styles, '@media (prefers-color-scheme: dark) /* chtml maction */'); + } + ], + [ + MathJax._.output?.svg, + 'output/svg', + () => { + const { SVG } = MathJax._.output.svg_ts; + switchToBSStyle(SVG.commonStyles); + const { SvgMaction } = MathJax._.output.svg.Wrappers.maction; + switchToBSStyle(SvgMaction.styles, '@media (prefers-color-scheme: dark) /* svg maction */'); + } + ] +]) { + if (immediate) { + ready(); + } else { + const config = MathJax.config.loader; + config[extension] ??= {}; + config[extension].extraLoads ??= []; + const check = config[extension].checkReady; + config[extension].checkReady = async () => { + if (check) await check(); + return ready(); + }; + } +} diff --git a/htdocs/js/MathJaxConfig/mathjax-config.js b/htdocs/js/MathJaxConfig/mathjax-config.js index 762efe0a83..2b42f0928f 100644 --- a/htdocs/js/MathJaxConfig/mathjax-config.js +++ b/htdocs/js/MathJaxConfig/mathjax-config.js @@ -2,8 +2,8 @@ if (!window.MathJax) { window.MathJax = { tex: { packages: { '[+]': webworkConfig?.showMathJaxErrors ? [] : ['noerrors'] } }, loader: { - load: ['input/asciimath', '[tex]/noerrors', '[no-dark-mode]'], - paths: { 'no-dark-mode': webworkConfig?.mathJaxDarkModeUrl ?? './no-dark-mode.js' } + load: ['input/asciimath', '[tex]/noerrors', '[bs-color-scheme]'], + paths: { 'bs-color-scheme': webworkConfig?.mathJaxBSColorSchemeUrl ?? './bs-color-scheme.js' } }, startup: { ready() { diff --git a/htdocs/js/MathJaxConfig/no-dark-mode.js b/htdocs/js/MathJaxConfig/no-dark-mode.js deleted file mode 100644 index 755b06911c..0000000000 --- a/htdocs/js/MathJaxConfig/no-dark-mode.js +++ /dev/null @@ -1,63 +0,0 @@ -if (MathJax.loader) MathJax.loader.checkVersion('[no-dark-mode]', '4.1.0', 'extension'); - -for (const [immediate, extension, ready] of [ - [ - MathJax._.ui?.dialog, - 'core', - () => { - const { DraggableDialog } = MathJax._.ui.dialog.DraggableDialog; - delete DraggableDialog.styles['@media (prefers-color-scheme: dark)']; - } - ], - - [ - MathJax._.a11y?.explorer, - 'a11y/explorer', - () => { - const Region = MathJax._.a11y.explorer.Region; - for (const region of ['LiveRegion', 'HoverRegion', 'ToolTip']) { - Region[region].style.styles['@media (prefers-color-scheme: dark)'] = {}; - } - Region.LiveRegion.style.styles['@media (prefers-color-scheme: dark)']['mjx-ignore'] = { ignore: 1 }; - MathJax.startup.extendHandler((handler) => { - delete handler.documentClass.speechStyles['@media (prefers-color-scheme: dark) /* explorer */']; - return handler; - }); - } - ], - - [ - MathJax._.output?.chtml, - 'output/chtml', - () => { - const { CHTML } = MathJax._.output.chtml_ts; - delete CHTML.commonStyles['@media (prefers-color-scheme: dark)']; - const { ChtmlMaction } = MathJax._.output.chtml.Wrappers.maction; - delete ChtmlMaction.styles['@media (prefers-color-scheme: dark) /* chtml maction */']; - } - ], - - [ - MathJax._.output?.svg, - 'output/svg', - () => { - const { SVG } = MathJax._.output.svg_ts; - delete SVG.commonStyles['@media (prefers-color-scheme: dark)']; - const { SvgMaction } = MathJax._.output.svg.Wrappers.maction; - delete SvgMaction.styles['@media (prefers-color-scheme: dark) /* svg maction */']; - } - ] -]) { - if (immediate) { - ready(); - } else { - const config = MathJax.config.loader; - config[extension] ??= {}; - config[extension].extraLoads ??= []; - const check = config[extension].checkReady; - config[extension].checkReady = async () => { - if (check) await check(); - return ready(); - }; - } -} diff --git a/htdocs/js/PGCodeMirror/pgeditor.scss b/htdocs/js/PGCodeMirror/pgeditor.scss index 3f1cd895c1..384adc6d17 100644 --- a/htdocs/js/PGCodeMirror/pgeditor.scss +++ b/htdocs/js/PGCodeMirror/pgeditor.scss @@ -1,5 +1,5 @@ .code-mirror-editor { - border: 1px solid #ddd; + border: 1px solid var(--ww-layout-border-color, #ddd); min-height: 400px; overflow: auto; resize: vertical; @@ -25,7 +25,7 @@ // This style is used if the CodeMirror editor is disabled in localOverrides.conf. .text-area-editor { - border: 1px solid #ddd; + border: 1px solid var(--ww-layout-border-color, #ddd); padding: 2px; height: 550px; min-height: 400px; diff --git a/htdocs/js/PGProblemEditor/pgproblemeditor.js b/htdocs/js/PGProblemEditor/pgproblemeditor.js index 55645e1513..9f8f0162d7 100644 --- a/htdocs/js/PGProblemEditor/pgproblemeditor.js +++ b/htdocs/js/PGProblemEditor/pgproblemeditor.js @@ -423,6 +423,7 @@ const iframe = document.createElement('iframe'); iframe.title = 'Rendered content'; iframe.id = 'pgedit-render-iframe'; + iframe.style.colorScheme = 'light'; // Adjust the height of the iframe when the window is resized and when the iframe loads. const adjustIFrameHeight = () => { diff --git a/htdocs/js/RenderProblem/renderproblem.js b/htdocs/js/RenderProblem/renderproblem.js index ec4503a64a..f3bb2bbebb 100644 --- a/htdocs/js/RenderProblem/renderproblem.js +++ b/htdocs/js/RenderProblem/renderproblem.js @@ -69,6 +69,7 @@ iframe = document.createElement('iframe'); iframe.id = `${renderArea.id}_iframe`; iframe.style.border = 'none'; + iframe.style.colorScheme = 'light'; while (renderArea.firstChild) renderArea.firstChild.remove(); renderArea.append(iframe); diff --git a/htdocs/js/System/color-scheme.js b/htdocs/js/System/color-scheme.js new file mode 100644 index 0000000000..cf0164193a --- /dev/null +++ b/htdocs/js/System/color-scheme.js @@ -0,0 +1,75 @@ +'use strict'; + +(() => { + const getPreferredTheme = () => { + const storedTheme = localStorage.getItem('WW.color-scheme'); + if (storedTheme) return storedTheme; + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; + }; + + let flatpickrDarkTheme; + + const setTheme = (theme) => { + const themeValue = + theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme; + document.documentElement.setAttribute('data-bs-theme', themeValue); + + if (!flatpickrDarkTheme) flatpickrDarkTheme = document.getElementById('flatpickr-dark-theme'); + if (flatpickrDarkTheme) { + if (themeValue === 'dark') document.head.append(flatpickrDarkTheme); + else flatpickrDarkTheme.remove(); + } + }; + + setTheme(getPreferredTheme()); + + const showActiveTheme = (theme, focus = false) => { + const themeSwitcher = document.getElementById('color-scheme-chooser'); + if (!themeSwitcher) return; + + const activeThemeIcon = themeSwitcher.querySelector('.theme-icon-active'); + const btnToActive = document.querySelector(`[data-bs-theme-value="${theme}"]`); + + for (const element of document.querySelectorAll('[data-bs-theme-value]')) { + element.classList.remove('active'); + element.setAttribute('aria-pressed', 'false'); + } + + btnToActive.classList.add('active'); + btnToActive.setAttribute('aria-pressed', 'true'); + activeThemeIcon.classList.remove('fa-sun', 'fa-moon', 'fa-circle-half-stroke'); + activeThemeIcon.classList.add( + theme === 'light' ? 'fa-sun' : theme === 'dark' ? 'fa-moon' : 'fa-circle-half-stroke' + ); + themeSwitcher.setAttribute( + 'aria-label', + `${themeSwitcher.title} (${ + themeSwitcher.dataset[`${btnToActive.dataset.bsThemeValue}Text`] ?? btnToActive.dataset.bsThemeValue + })` + ); + + if (focus) themeSwitcher.focus(); + }; + + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => { + const storedTheme = localStorage.getItem('WW.color-scheme'); + if (storedTheme !== 'light' && storedTheme !== 'dark') { + const preferredTheme = getPreferredTheme(); + setTheme(preferredTheme); + showActiveTheme(preferredTheme); + } + }); + + window.addEventListener('DOMContentLoaded', () => { + showActiveTheme(getPreferredTheme()); + + for (const toggle of document.querySelectorAll('[data-bs-theme-value]')) { + toggle.addEventListener('click', () => { + const theme = toggle.getAttribute('data-bs-theme-value'); + localStorage.setItem('WW.color-scheme', theme); + setTheme(theme); + showActiveTheme(theme, true); + }); + } + }); +})(); diff --git a/htdocs/js/System/system.scss b/htdocs/js/System/system.scss index 340cb6c277..6894ccfc50 100644 --- a/htdocs/js/System/system.scss +++ b/htdocs/js/System/system.scss @@ -3,7 +3,7 @@ table caption { font-weight: bold; font-size: larger; - color: black; + color: var(--bs-emphasis-color, black); } .help-popup { @@ -20,6 +20,10 @@ table caption { .required-field { color: #dc3545; + + [data-bs-theme='dark'] & { + color: #f85149; + } } .visually-hidden-focusable:active, @@ -28,7 +32,6 @@ table caption { } $masthead-height: 70px !default; -$layout-divider-color: #aaa !default; $site-nav-width: 250px !default; /* Banner */ @@ -40,7 +43,7 @@ $site-nav-width: 250px !default; display: flex; height: $masthead-height; background-color: var(--bs-primary, #038); - border-bottom: 1px solid $layout-divider-color; + border-bottom: 1px solid var(--ww-layout-divider-color, #aaa); margin: 0; padding: 0; z-index: 20; @@ -63,7 +66,8 @@ $site-nav-width: 250px !default; display: flex; align-items: center; justify-content: space-between; - padding: 5px 0; + padding: 5px 0.5rem; + gap: 0.25rem; background-color: var(--ww-logo-background-color, #104aad); z-index: 20; width: $site-nav-width; @@ -83,18 +87,18 @@ $site-nav-width: 250px !default; } } - a, - span { + a { display: inline-block; - margin-right: 0.5rem; } } .institution-logo { display: flex; flex-grow: 1; + gap: 2rem; align-items: center; - padding: 8px 0; + justify-content: space-between; + padding: 0; max-height: $masthead-height - 1px; @media only screen and (max-width: 768px) { @@ -108,8 +112,14 @@ $site-nav-width: 250px !default; a { display: block; - margin-left: 0.5rem; - margin-right: 0.5rem; + } + + #color-scheme-chooser { + --bs-btn-color: var(--ww-primary-foreground-color, white) !important; + --bs-btn-hover-color: var(--ww-color-chooser-hover-color, #ccc); + --bs-btn-active-color: var(--ww-color-chooser-hover-color, #ccc); + --bs-btn-focus-shadow-rgb: var(--ww-color-chooser-focus-outline-color-rgb, 255, 255, 255); + text-decoration: none; } } @@ -118,16 +128,11 @@ $site-nav-width: 250px !default; height: $masthead-height - 1px; padding: 4px 10px 4px 0; color: var(--ww-primary-foreground-color, white); - text-align: right; font-size: 0.85em; font-weight: normal; a { color: black; - - &:first-child { - margin-bottom: 5px; - } } } } @@ -144,7 +149,7 @@ $site-nav-width: 250px !default; overflow-y: auto; transition-property: left, border-right-width; transition-duration: 0.3s; - border-right: 1px solid $layout-divider-color; + border-right: 1px solid var(--ww-layout-divider-color, #aaa); padding: 2px; &.toggle-width { @@ -176,7 +181,7 @@ $site-nav-width: 250px !default; .info-box { border-radius: 0; border: none; - border-top: 1px solid $layout-divider-color; + border-top: 1px solid var(--ww-layout-divider-color, #aaa); } .nav { @@ -208,7 +213,7 @@ $site-nav-width: 250px !default; padding-right: 0; li a:hover { - background: #e1e1e1; + background: var(--ww-site-nav-link-hover-background-color, #e1e1e1); } ul.nav { @@ -268,22 +273,15 @@ $site-nav-width: 250px !default; } #toggle-sidebar { - #toggle-sidebar-icon i { - padding: 0.25rem; - border-radius: 5px; - color: rgba(255, 255, 255, 0.85); - transition: - color 0.15s ease-in-out, - background-color 0.15s ease-in-out, - border-color 0.15s ease-in-out; + --bs-navbar-color: rgba(var(--ww-toggle-sidebar-icon-color-rgb, 255, 255, 255), 0.85); + --bs-navbar-toggler-border-radius: 0.375rem; + --bs-navbar-toggler-focus-width: 0.25rem; + --bs-navbar-toggler-padding-x: 0.25rem; + --bs-navbar-toggler-padding-y: 0.25rem; + --bs-navbar-toggler-transition: box-shadow 0.15s ease-in-out; - &:hover { - color: #fff; - } - } - - &:focus #toggle-sidebar-icon i { - outline: 1px solid var(--bs-link-hover-color); + &:hover { + --bs-navbar-color: var(--ww-toggle-sidebar-icon-hover-color, #fff); } } @@ -297,6 +295,12 @@ $site-nav-width: 250px !default; margin-bottom: 10px; align-items: center; + [data-bs-theme='dark'] & { + box-shadow: + inset 0 0 3px 2px #000, + 0 0 2px 1px #fff; + } + .progress-bar { box-shadow: inset 0 0 3px 2px #000; height: 100%; @@ -329,6 +333,7 @@ $site-nav-width: 250px !default; /* Show me another */ div.showMeAnotherBox { + color: #212529; background-color: #ede275; border-radius: 5px; border: 2px solid #fdd017; @@ -344,7 +349,7 @@ div.showMeAnotherBox { padding-left: 0.5rem; min-height: 38px; align-items: center; - border: 1px solid #e6e6e6; + border: 1px solid var(--ww-layout-border-color, #e6e6e6); border-radius: 4px; } } @@ -362,10 +367,6 @@ h1.page-title { } } -h2.page-title { - border-bottom: 1px solid #ccc; -} - .problem-sub-header { margin-top: 0.25rem; font-weight: bold; @@ -387,6 +388,10 @@ h2.page-title { direction: ltr; font-family: monospace; font-size: 9pt; + + [data-bs-theme='dark'] & { + color: var(--bs-danger-text-emphasis); + } } /* Question nav section */ @@ -396,10 +401,10 @@ h2.page-title { gap: 0.5rem; align-content: space-between; justify-content: space-between; - z-index: 20; + z-index: 19; position: sticky; top: $masthead-height; - background-color: white; + background-color: var(--bs-body-bg, white); margin-bottom: 1rem; padding: 0.25rem; margin-left: 0; @@ -449,6 +454,10 @@ h2.page-title { width: 60%; padding: 10px; text-align: left; + + [data-bs-theme='dark'] & { + background-color: #292900; + } } /* Home Page */ @@ -457,7 +466,7 @@ ul.courses-list { margin: 0; a { - border: 1px solid #e6e6e6; + border: 1px solid var(--ww-layout-border-color, #e6e6e6); display: block; padding: 0.5em; margin-bottom: 0.5em; @@ -465,6 +474,11 @@ ul.courses-list { width: 95%; font-weight: bold; + [data-bs-theme='dark'] & { + background: var(--bs-primary-bg-subtle, black); + color: var(--bs-primary-text-emphasis, white); + } + &:hover { text-decoration: none; background: var(--bs-primary, #038); @@ -493,11 +507,28 @@ ul.courses-list { td { white-space: nowrap; min-width: 20px; + + &.correct { + color: #060; + } + + &.incorrect { + color: #600; + } + + [data-bs-theme='dark'] & { + &.correct { + color: #0b0; + } + + &.incorrect { + color: #f66; + } + } } .table-rule { - border-top: 3px solid #d5d5d5; - padding-top: 5px; + border-top: 3px solid var(--ww-layout-divider-color); } .essay, @@ -583,7 +614,7 @@ ul.courses-list { .info-box { padding: 0.5em; border-radius: 8px; - border: 1px solid #e6e6e6; + border: 1px solid var(--ww-layout-border-color, #e6e6e6); h2, h3, @@ -660,6 +691,10 @@ ul.courses-list { background-color: #f5f5f5; margin-top: 10px; margin-bottom: 0; + + [data-bs-theme='dark'] & { + background-color: var(--bs-primary-bg-subtle, 'black'); + } } .lb-mlt-group { @@ -710,6 +745,14 @@ div.AuthorComment { a { color: #555; } + + [data-bs-theme='dark'] & { + color: #c6c6c6; + + a { + color: #999; + } + } } input.changed[type='text'] { @@ -740,6 +783,11 @@ input.changed[type='text'] { border-spacing: 2px; border-color: gray; border-radius: 0.25rem; + + [data-bs-theme='dark'] & { + background-color: #4a4a4a; + border-color: #939393; + } } .submit-buttons-container { @@ -760,28 +808,39 @@ input.changed[type='text'] { font-style: italic; color: #ca5000; background-color: inherit; + + [data-bs-theme='dark'] & { + color: #ca8253; + } } /* Text colors for Auditing, Current, and Dropped students */ .Audit { font-style: normal; color: purple; - background-color: inherit; } .Enrolled { font-weight: normal; - color: black; - background-color: inherit; } .Drop { font-style: italic; color: #555; - background-color: inherit; } .Observer { font-style: normal; color: green; - background-color: inherit; +} + +[data-bs-theme='dark'] { + .Audit { + color: #f400f4; + } + .Drop { + color: #958888; + } + .Observer { + color: #04a404; + } } /* Styles for the PGProblemEditor Page */ @@ -793,7 +852,7 @@ input.changed[type='text'] { } #pgedit-render-area { - border: 1px solid #ddd; + border: 1px solid var(--ww-layout-border-color, #ddd); min-height: 400px; height: 600px; resize: vertical; @@ -854,6 +913,14 @@ input.changed[type='text'] { .table { --bs-table-bg: #f5f5f5; } + + [data-bs-theme='dark'] & { + background-color: var(--bs-primary-bg-subtle, 'black'); + + .table { + --bs-table-bg: var(--bs-primary-bg-subtle, 'black'); + } + } } .pdr_placeholder { @@ -908,6 +975,10 @@ input.changed[type='text'] { .rpc_render_area_container { background-color: #f5f5f5; + + [data-bs-theme='dark'] & { + background-color: var(--bs-primary-bg-subtle, 'black'); + } } .rpc_render_area iframe { @@ -943,6 +1014,21 @@ input.changed[type='text'] { color: inherit; background-color: #88ecff; } + + [data-bs-theme='dark'] & { + &.correct { + color: black; + } + + &.incorrect { + color: white; + background-color: #bf5454; + } + + &.unattempted { + color: black; + } + } } } @@ -953,6 +1039,10 @@ input.changed[type='text'] { font-weight: bold; color: inherit; border-radius: 0; + + &:focus-visible { + box-shadow: 0 0 0 0.25rem var(--ww-course-config-tab-link-focus-outline-color, #00338840); + } } &:not(.active) { @@ -964,12 +1054,30 @@ input.changed[type='text'] { color: inherit; } + [data-bs-theme='dark'] & { + &:not(.active) { + background-color: #565656; + } + + &:not(.active):hover { + background-color: #414141; + } + } + &:focus { z-index: 2; } } } +/* Stats */ + +[data-bs-theme='dark'] .stats-image { + text { + fill: white; + } +} + /* File manager */ .file-manager-btn { margin-bottom: 0.25rem; @@ -988,25 +1096,55 @@ input.changed[type='text'] { /* Problem graders */ -span.needs-grading, -td.needs-grading { - background-color: #fff3cd; +#problem-grader-form { + .needs-grading { + background-color: #fff3cd; - div { - font-weight: bold; + [data-bs-theme='dark'] & { + background-color: #261d00; + } + + div { + font-weight: bold; + } } -} -span.alt-source, -td.alt-source { - background-color: #e6e7e9; -} + .alt-source { + background-color: #e6e7e9; -#problem-grader-form { - .past-answer:not(:last-child) { - border-bottom: 1px solid #d5d5d5; - margin-bottom: 2px; - padding-bottom: 5px; + [data-bs-theme='dark'] & { + background-color: #555; + } + } + + .problem-grader-legend-key span { + border: 1px solid var(--ww-layout-border-color); + } + + .past-answer { + &:not(:last-child) { + border-bottom: 1px solid var(--bs-table-border-color); + margin-bottom: 2px; + padding-bottom: 5px; + } + + &.correct { + color: #060; + } + + &.incorrect { + color: #600; + } + + [data-bs-theme='dark'] & { + &.correct { + color: #0b0; + } + + &.incorrect { + color: #f66; + } + } } .restricted-width-col { @@ -1069,3 +1207,9 @@ td.alt-source { mjx-help-background { z-index: 1055; } + +[data-bs-theme='dark'] .flatpickr-confirm { + svg { + fill: white; + } +} diff --git a/htdocs/package-lock.json b/htdocs/package-lock.json index 5dc322e1c0..d4d2bb0c5d 100644 --- a/htdocs/package-lock.json +++ b/htdocs/package-lock.json @@ -8,7 +8,7 @@ "license": "GPL-2.0+", "dependencies": { "@fortawesome/fontawesome-free": "^7.0.0", - "@openwebwork/pg-codemirror-editor": "^0.0.6", + "@openwebwork/pg-codemirror-editor": "^0.0.9", "bootstrap": "~5.3.7", "flatpickr": "^4.6.13", "iframe-resizer": "^4.4.2", @@ -37,6 +37,7 @@ "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.0.tgz", "integrity": "sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==", "license": "MIT", + "peer": true, "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", @@ -49,6 +50,7 @@ "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.0.tgz", "integrity": "sha512-2xUIc5mHXQzT16JnyOFkh8PvfeXuIut3pslWGfsGOhxP/lpgRm9HOl/mpzLErgt5mXDovqA0d11P21gofRLb9w==", "license": "MIT", + "peer": true, "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.4.0", @@ -120,6 +122,7 @@ "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz", "integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==", "license": "MIT", + "peer": true, "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", @@ -145,6 +148,7 @@ "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz", "integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==", "license": "MIT", + "peer": true, "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", @@ -156,6 +160,7 @@ "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz", "integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==", "license": "MIT", + "peer": true, "dependencies": { "@marijn/find-cluster-break": "^1.0.0" } @@ -177,6 +182,7 @@ "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.8.tgz", "integrity": "sha512-XcE9fcnkHCbWkjeKyi0lllwXmBLtyYb5dt89dJyx23I9+LSh5vZDIuk7OLG4VM1lgrXZQcY6cxyZyk5WVPRv/A==", "license": "MIT", + "peer": true, "dependencies": { "@codemirror/state": "^6.5.0", "crelt": "^1.0.6", @@ -265,6 +271,7 @@ "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", "license": "MIT", + "peer": true, "dependencies": { "@lezer/common": "^1.3.0" } @@ -334,9 +341,9 @@ } }, "node_modules/@openwebwork/pg-codemirror-editor": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/@openwebwork/pg-codemirror-editor/-/pg-codemirror-editor-0.0.6.tgz", - "integrity": "sha512-M9pq1FuIgq3LPd1wre1O9tH5goYsOpcXlbiTpJ15e2aP7b3cHeEUgE1Dk/w8x0ZQjH45TWlh5aTSXeLDsMQGwA==", + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/@openwebwork/pg-codemirror-editor/-/pg-codemirror-editor-0.0.9.tgz", + "integrity": "sha512-6uy28r0ejOzJOC9vkgCu6tx9Zyi6ZpYWmqsk4sJlPamOicOiVNL5riu7pb4aIxmbdZWw4pXsGGilzYB38bnYVA==", "license": "MIT", "dependencies": { "@codemirror/lang-html": "^6.4.11", @@ -844,6 +851,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001726", "electron-to-chromium": "^1.5.173", @@ -1682,6 +1690,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -2523,6 +2532,7 @@ "version": "6.20.0", "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.0.tgz", "integrity": "sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==", + "peer": true, "requires": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", @@ -2534,6 +2544,7 @@ "version": "6.10.0", "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.0.tgz", "integrity": "sha512-2xUIc5mHXQzT16JnyOFkh8PvfeXuIut3pslWGfsGOhxP/lpgRm9HOl/mpzLErgt5mXDovqA0d11P21gofRLb9w==", + "peer": true, "requires": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.4.0", @@ -2600,6 +2611,7 @@ "version": "6.11.3", "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz", "integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==", + "peer": true, "requires": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", @@ -2623,6 +2635,7 @@ "version": "6.5.11", "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz", "integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==", + "peer": true, "requires": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", @@ -2633,6 +2646,7 @@ "version": "6.5.2", "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz", "integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==", + "peer": true, "requires": { "@marijn/find-cluster-break": "^1.0.0" } @@ -2652,6 +2666,7 @@ "version": "6.38.8", "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.8.tgz", "integrity": "sha512-XcE9fcnkHCbWkjeKyi0lllwXmBLtyYb5dt89dJyx23I9+LSh5vZDIuk7OLG4VM1lgrXZQcY6cxyZyk5WVPRv/A==", + "peer": true, "requires": { "@codemirror/state": "^6.5.0", "crelt": "^1.0.6", @@ -2725,6 +2740,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", + "peer": true, "requires": { "@lezer/common": "^1.3.0" } @@ -2788,9 +2804,9 @@ } }, "@openwebwork/pg-codemirror-editor": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/@openwebwork/pg-codemirror-editor/-/pg-codemirror-editor-0.0.6.tgz", - "integrity": "sha512-M9pq1FuIgq3LPd1wre1O9tH5goYsOpcXlbiTpJ15e2aP7b3cHeEUgE1Dk/w8x0ZQjH45TWlh5aTSXeLDsMQGwA==", + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/@openwebwork/pg-codemirror-editor/-/pg-codemirror-editor-0.0.9.tgz", + "integrity": "sha512-6uy28r0ejOzJOC9vkgCu6tx9Zyi6ZpYWmqsk4sJlPamOicOiVNL5riu7pb4aIxmbdZWw4pXsGGilzYB38bnYVA==", "requires": { "@codemirror/lang-html": "^6.4.11", "@codemirror/lang-xml": "^6.1.0", @@ -3007,6 +3023,7 @@ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", "dev": true, + "peer": true, "requires": { "caniuse-lite": "^1.0.30001726", "electron-to-chromium": "^1.5.173", @@ -3538,6 +3555,7 @@ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "dev": true, + "peer": true, "requires": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", diff --git a/htdocs/package.json b/htdocs/package.json index 04f742b6da..168ce50480 100644 --- a/htdocs/package.json +++ b/htdocs/package.json @@ -14,7 +14,7 @@ }, "dependencies": { "@fortawesome/fontawesome-free": "^7.0.0", - "@openwebwork/pg-codemirror-editor": "^0.0.6", + "@openwebwork/pg-codemirror-editor": "^0.0.9", "bootstrap": "~5.3.7", "flatpickr": "^4.6.13", "iframe-resizer": "^4.4.2", diff --git a/htdocs/themes/README.md b/htdocs/themes/README.md new file mode 100644 index 0000000000..048ca09644 --- /dev/null +++ b/htdocs/themes/README.md @@ -0,0 +1,78 @@ +# Theming WeBWorK 2 + +This folder contains the themes for webwork2. If you would like to create a custom theme, then copy one of the existing +theme directories, and modify it as desired. It is recommended that you use either the `math4-green`, `math4-red`, or +`math4-yellow` theme as the basis for a new custom theme, but for more advanced theming you can copy the `math4` +directory itself. Generally only the `_theme-colors.scss` and `_theme-overrides.scss` files need to be modified. The +important things to change are the colors in the `_theme-colors.scss` file. Overrides for special cases can be done in +the `_theme-overrides.scss` file. The `math4-yellow` theme uses a light primary color, and so it is a good example to +follow if you also need a light primary color. It also shows how the `_theme-overrides.scss` file can be used to handle +certain special cases. + +The `math4-overrides.css` and `math4-overrides.js` files can also be created in the theme directory and customizations +can also be added to those files. However, usage of these files is deprecated and support for them will eventually be +dropped. There are `.dist` files in the `math4` directory you can copy, but the `.dist` files do not have anything of +value in them. + +Note that any changes made to the files in the `math4`, `math4-green`, `math4-red`, and `math4-yellow` files will cause +problems when you upgrade webwork2, (other than copies of the `math4-overrides.css.dist` and `math4-overrides.css.js` +files). + +To make the custom theme available for webwork2 to use, run `npm ci` from the `htdocs` directory. Then either set the +theme as the `$defaultTheme` in `conf/localOverrides.conf` or choose the theme in the `Course Configuration` of a course +to use it. Note that for any changes in the theme files to take effect, you must run `npm ci` again. You can also +execute `./generate-assets.js` from the `htdocs` directory to update the theme (this is actually part of what `npm ci` +does). See more details on theme creation on [the WeBWork Wiki](https://wiki.openwebwork.org/wiki/Customizing_WeBWorK). + +The theming system uses Sass which is an extension of CSS, and is compiled into CSS (by the `generate-assets.js` +script). Sass variables can be set in the `_theme-colors.scss` file that control many display aspects of the user +interface. Bootstrap has many Sass variables that can be customized. In addition there are CSS variables that can be +set. Many of these are set initially from the Sass variables, but they can also be changed in the +`_theme-overrides.scss` file. See the [Bootstrap documentation](https://getbootstrap.com/docs/5.3) for available Sass +and CSS variables. There are also Sass and CSS variables specifically for webwork2 that can be used. These are +documented below. In addition Bootstrap functions can be used to manipulate colors in the `_theme-colors.scss` file. +See the [Boottrap Sass function documentation](https://getbootstrap.com/docs/5.3/customize/sass/#functions). + +## WeBWorK 2 Sass Variables + +These must be set in the `_theme-colors.scss` file. + +- `$ww-logo-background-color`: WeBWorK logo background color in the banner. +- `$ww-achievement-level-color`: Color of the level progress bar on the achievements page. + +## WeBWorK 2 CSS Variables + +All of these are set to a default value in the `bootstrap.scss` file, but can be overridden in the +`_theme-overrides.scss` file. Note that values can even be set for a specific CSS selector to only apply to the elements +that match the selector and descendants of those elements. + +- `--ww-primary-foreground-color`: The color of text that is displayed before a primary colored background in the page + header, site navigation menu, and the course list on the webwork2 home page. This defaults to the result of + `#{color-contrast($primary)}` and rarely needs to be changed. +- `--ww-layout-divider-color`: This is the color of the border that separates the page header, site navigation menu, and + main content area. It is also used for the color of the border separates the primary part of the site navigation menu + from page specific sub menus (such as the list of problems when viewing a problem in a homework set). This defaults to + `#aaa` in light mode and `#666` in dark mode. +- `--ww-layout-border-color`: This is the border color for other regions such as the breadcrumb navigation at the top of + every page and the info box shown (the course information or set header box). This defaults to `#e6e6e6` in light mode + and `#495057` in dark mode. +- `--ww-toggle-sidebar-icon-color-rgb`: The color of the site navigation menu toggle button in RGB color components. + This defaults to `255, 255, 255`. +- `--ww-toggle-sidebar-icon-hover-color`: The color of the site navigation menu toggle button when it is hovered over + with the mouse cursor or has keyboard focus. This defaults to `#fff`. +- `--ww-site-nav-link-active-background-color`: The background color of the links in the site navigation menu when they + have keyboard focus. This defaults to `#{$primary}`. +- `--ww-site-nav-link-hover-background-color`: The background color of the links in the site navigation menu when the + mouse cursor hovers over them. This defaults to `#e1e1e1` in light mode and `#{shade-color($primary, 40%)}` in dark + mode. +- `--ww-course-config-tab-link-focus-outline-color`: The outline color of the tab selection buttons on the course + configuration page when they have keyboard focus. This defaults to `#{rgba($primary, $focus-ring-opacity)}` in light + mode and `#{rgba(color-contrast($body-bg-dark), $focus-ring-opacity)}` in dark mode. +- `--ww-logo-background-color`: The background color for the top left region of the page header that contains the + WeBWorK logo. This is set to the value of the `#{$ww-logo-background-color}` Sass variable, and there is no need to + ever modify this. Just set the Sass variable directly to what this should be. This is really only needed to get the + theme color to the other CSS files used by webwork2. +- `--ww-achievement-level-color`: The color of the level progress bar on the achievements page. This defaults to the + value of the `#{$ww-achievement-level-color}` Sass variable, and there is no need to ever modify this. Just set the + Sass variable directly to what this should be. This is really only needed to get the theme color to the other CSS + files used by webwork2. diff --git a/htdocs/themes/math4-green/README b/htdocs/themes/math4-green/README deleted file mode 100644 index e607958755..0000000000 --- a/htdocs/themes/math4-green/README +++ /dev/null @@ -1,8 +0,0 @@ -This is an "alternative" colorization to math4. If you want to provide -multiple themes to your users you should follow this as an example. - -Everything except for the math4-overrides.js, math4-overrides.css, -_theme-colors.scss, and _theme-overrides.scss files should be links pointing -back to the corresponding files in math4. This will make it so that all your -themes will automatically get updates. All of your changes should be in the -listed override files. diff --git a/htdocs/themes/math4-green/_theme-colors.scss b/htdocs/themes/math4-green/_theme-colors.scss index 89fa61322f..4a0d5d4e6e 100644 --- a/htdocs/themes/math4-green/_theme-colors.scss +++ b/htdocs/themes/math4-green/_theme-colors.scss @@ -7,7 +7,6 @@ $info: #618265; // Link colors $link-color: #283f2b; -$link-hover-color: #618265; // Webwork logo background color in the banner $ww-logo-background-color: darken($info, 8%); diff --git a/htdocs/themes/math4-red/README b/htdocs/themes/math4-red/README deleted file mode 100644 index e607958755..0000000000 --- a/htdocs/themes/math4-red/README +++ /dev/null @@ -1,8 +0,0 @@ -This is an "alternative" colorization to math4. If you want to provide -multiple themes to your users you should follow this as an example. - -Everything except for the math4-overrides.js, math4-overrides.css, -_theme-colors.scss, and _theme-overrides.scss files should be links pointing -back to the corresponding files in math4. This will make it so that all your -themes will automatically get updates. All of your changes should be in the -listed override files. diff --git a/htdocs/themes/math4-red/_theme-colors.scss b/htdocs/themes/math4-red/_theme-colors.scss index b34e6f2682..26de64dda7 100644 --- a/htdocs/themes/math4-red/_theme-colors.scss +++ b/htdocs/themes/math4-red/_theme-colors.scss @@ -8,6 +8,7 @@ $info: #c30; // Link colors $link-color: $primary; $link-hover-color: #c00; +$link-color-dark: tint-color($primary, 50%); // Webwork logo background color in the banner $ww-logo-background-color: darken($info, 8%); diff --git a/htdocs/themes/math4-red/_theme-overrides.scss b/htdocs/themes/math4-red/_theme-overrides.scss index 20b5856367..e69de29bb2 100644 --- a/htdocs/themes/math4-red/_theme-overrides.scss +++ b/htdocs/themes/math4-red/_theme-overrides.scss @@ -1,3 +0,0 @@ -a:not(.btn):focus { - outline-color: #{lighten($link-hover-color, 26%)}; -} diff --git a/htdocs/themes/math4-yellow/README b/htdocs/themes/math4-yellow/README deleted file mode 100644 index e607958755..0000000000 --- a/htdocs/themes/math4-yellow/README +++ /dev/null @@ -1,8 +0,0 @@ -This is an "alternative" colorization to math4. If you want to provide -multiple themes to your users you should follow this as an example. - -Everything except for the math4-overrides.js, math4-overrides.css, -_theme-colors.scss, and _theme-overrides.scss files should be links pointing -back to the corresponding files in math4. This will make it so that all your -themes will automatically get updates. All of your changes should be in the -listed override files. diff --git a/htdocs/themes/math4-yellow/_theme-colors.scss b/htdocs/themes/math4-yellow/_theme-colors.scss index cbc8f078bb..527681abff 100644 --- a/htdocs/themes/math4-yellow/_theme-colors.scss +++ b/htdocs/themes/math4-yellow/_theme-colors.scss @@ -5,13 +5,15 @@ $primary: #ffc700; $info: black; +$primary-bg-subtle-dark: shade-color($primary, 90%); +$info-text-emphasis-dark: tint-color($info, 50%); + // Override the default white for the foreground color of active components. // White has poor color contrast with the yellow primary color. $component-active-color: black; // Link colors -$link-color: darken(#bf5454, 30%); -$link-hover-color: lighten($link-color, 30%); +$link-color: shade-color($primary, 60%); // Webwork logo background color in the banner $ww-logo-background-color: $info; @@ -19,9 +21,6 @@ $ww-logo-background-color: $info; // Achievment level bar $ww-achievement-level-color: darken($primary, 15%); -// Make accordion buttons darker. -$accordion-button-active-color: shade-color($primary, 50%); - // Make the navbar colors dark. $navbar-dark-color: rgba(#000, 0.55); $navbar-dark-hover-color: rgba(#000, 0.75); diff --git a/htdocs/themes/math4-yellow/_theme-overrides.scss b/htdocs/themes/math4-yellow/_theme-overrides.scss index 0cf795757f..1046436e60 100644 --- a/htdocs/themes/math4-yellow/_theme-overrides.scss +++ b/htdocs/themes/math4-yellow/_theme-overrides.scss @@ -38,10 +38,30 @@ color: $link-color !important; } -a:not(.btn):focus { - outline-color: #{darken($link-hover-color, 1%)}; -} - :root { --ww-site-nav-link-active-background-color: #{$primary}; + --ww-color-chooser-hover-color: #555; + --ww-color-chooser-focus-outline-color-rgb: 0, 0, 0; + --ww-course-config-tab-link-focus-outline-color: #{rgba(shade-color($primary, 40%), $focus-ring-opacity)}; +} + +@include color-mode(dark) { + --ww-site-nav-link-hover-background-color: #{shade-color($primary, 60%)}; + --ww-course-config-tab-link-focus-outline-color: #{rgba(color-contrast($body-bg-dark), $focus-ring-opacity)}; + + .btn-outline-primary { + --#{$prefix}btn-color: #{tint_color($primary, 40%)}; + } +} + +.masthead { + .institution-logo { + a:not(.btn):focus { + outline-color: #{shade-color($link-hover-color-dark, 70%)}; + } + } + + .login-status .btn.btn-light { + --bs-btn-focus-shadow-rgb: 100, 100, 100; + } } diff --git a/htdocs/themes/math4/README b/htdocs/themes/math4/README deleted file mode 100644 index 0b92a6b6d6..0000000000 --- a/htdocs/themes/math4/README +++ /dev/null @@ -1,18 +0,0 @@ -This folder contains the files necessary for the math4 theme. These files are -tracked by git and any changes made to them will be overwritten when you -upgrade. The two exceptions are math4-overrides.css and math4-overrides.js. - -These files do not need to be present, but if they are they will be included in -system.conf and can be used for general overrides. They can created by copying -math4-overrides.css.dist and math4-overrides.js.dist. This is similar to how -localOverrides.conf interacts with defaults.conf and localOverrides.conf.dist. -In particular if you upgrade your server math4-overrides.js and -math4-overrides.css will not change, but their .dist versions and the other -math4 theme files may change. This might cause problems until you merge the -changes. - -If you want to customize math4 you should only change math4-overrides.css and -math4-overrides.js. Note: Because you can include arbitrary JavaScript in -math4-overrides.js you can actually change pretty much anything, including -adding new html or changing existing html. - diff --git a/htdocs/themes/math4/_theme-colors.scss b/htdocs/themes/math4/_theme-colors.scss index bd57046fb7..b40d760909 100644 --- a/htdocs/themes/math4/_theme-colors.scss +++ b/htdocs/themes/math4/_theme-colors.scss @@ -7,7 +7,7 @@ $info: #1a67ea; // Link colors $link-color: $primary; -$link-hover-color: $info; +$link-color-dark: tint-color($primary, 50%); // Webwork logo background color in the banner $ww-logo-background-color: darken($info, 14%); diff --git a/htdocs/themes/math4/bootstrap.scss b/htdocs/themes/math4/bootstrap.scss index 72cbc9cbd0..3114fe8445 100644 --- a/htdocs/themes/math4/bootstrap.scss +++ b/htdocs/themes/math4/bootstrap.scss @@ -17,10 +17,6 @@ $headings-font-weight: 600; $link-decoration: none; $link-hover-decoration: underline; -// Make breadcrumb dividers and active items a bit darker. -$breadcrumb-divider-color: #495057; -$breadcrumb-active-color: #495057; - @import './theme-colors'; // Include the remainder of bootstrap's scss configuration @@ -75,14 +71,55 @@ $breadcrumb-active-color: #495057; --ww-primary-foreground-color: #{color-contrast($primary)}; --ww-achievement-level-color: #{$ww-achievement-level-color}; --ww-site-nav-link-active-background-color: #{$primary}; + --ww-site-nav-link-hover-background-color: #e1e1e1; + --ww-layout-divider-color: #aaa; + --ww-layout-border-color: #e6e6e6; + --ww-course-config-tab-link-focus-outline-color: #{rgba($primary, $focus-ring-opacity)}; } // Overrides + a:not(.btn):focus { - color: $link-hover-color; outline-style: solid; - outline-color: #{lighten($link-hover-color, 8%)}; outline-width: 1px; + outline-color: #{$link_hover_color}; + box-shadow: none; +} + +@include color-mode(dark) { + --ww-site-nav-link-hover-background-color: #{shade-color($primary, 40%)}; + --ww-layout-divider-color: #666; + --ww-layout-border-color: #495057; + --ww-course-config-tab-link-focus-outline-color: #{rgba(color-contrast($body-bg-dark), $focus-ring-opacity)}; + + .bg-light { + color: var(--bs-body-color) !important; + background-color: var(--bs-primary-bg-subtle) !important; + } + + .btn-outline-primary { + --#{$prefix}btn-color: #{tint_color($primary, 60%)}; + --#{$prefix}btn-border-color: #{tint_color($primary, 20%)}; + } + + .text-danger { + color: var(--bs-danger-text-emphasis) !important; + } + + .text-success { + color: var(--bs-success-text-emphasis) !important; + } + + a:not(.btn):focus { + outline-color: #{$link_hover_color-dark}; + } +} + +.masthead { + a:not(.btn):focus { + outline-color: #{$link_hover_color_dark}; + outline-width: 2px; + } } @import 'theme-overrides'; diff --git a/lib/FormatRenderedProblem.pm b/lib/FormatRenderedProblem.pm index a270aaf50c..029de024af 100644 --- a/lib/FormatRenderedProblem.pm +++ b/lib/FormatRenderedProblem.pm @@ -196,11 +196,12 @@ sub formatRenderedProblem { $output->{input} = $ws->{input}; # The following could be constructed from the above, but this is a convenience - $output->{resultSummary} = $resultSummary->to_string if $resultSummary; - $output->{lang} = $PROBLEM_LANG_AND_DIR{lang}; - $output->{dir} = $PROBLEM_LANG_AND_DIR{dir}; - $output->{extra_css_files} = \@extra_css_files; - $output->{extra_js_files} = \@extra_js_files; + $output->{resultSummary} = $resultSummary->to_string if $resultSummary; + $output->{lang} = $PROBLEM_LANG_AND_DIR{lang}; + $output->{dir} = $PROBLEM_LANG_AND_DIR{dir}; + $output->{extra_css_files} = \@extra_css_files; + $output->{extra_js_files} = \@extra_js_files; + $output->{webwork_js_config} = $ws->c->webwork_js_config($ws->{inputs_ref}{showMathJaxErrors} // 0); # Include third party css and javascript files. Only jquery, jquery-ui, mathjax, and bootstrap are needed for # PG. See the comments before the subroutine definitions for load_css and load_js in pg/macros/PG.pl. diff --git a/lib/WeBWorK/ConfigValues.pm b/lib/WeBWorK/ConfigValues.pm index c1931dce2a..2bb2d95b71 100644 --- a/lib/WeBWorK/ConfigValues.pm +++ b/lib/WeBWorK/ConfigValues.pm @@ -1155,7 +1155,9 @@ sub getConfigValues ($ce) { }; # Get the list of theme folders in the theme directory. - my $themes = eval { path($ce->{webworkDirs}{themes})->list({ dir => 1 })->map('basename')->sort; }; + my $themes = eval { + path($ce->{webworkDirs}{themes})->list({ dir => 1 })->grep(sub {-d})->map('basename')->sort; + }; die "can't opendir $ce->{webworkDirs}{themes}: $@" if $@; # Get the list of all site hardcopy theme files. diff --git a/lib/WeBWorK/ContentGenerator.pm b/lib/WeBWorK/ContentGenerator.pm index 21b106dde9..8ba2a927e4 100644 --- a/lib/WeBWorK/ContentGenerator.pm +++ b/lib/WeBWorK/ContentGenerator.pm @@ -692,8 +692,8 @@ accessed by JavaScript files to obtain various webwork2 settings. sub webwork_js_config ($c, $showMathJaxErrors = 0) { return encode_json({ - webwork_url => $c->location, - mathJaxDarkModeUrl => getAssetURL($c->ce, 'js/MathJaxConfig/no-dark-mode.js'), + webwork_url => $c->location, + mathJaxBSColorSchemeUrl => getAssetURL($c->ce, 'js/MathJaxConfig/bs-color-scheme.js'), $showMathJaxErrors ? (showMathJaxErrors => true) : () }); } diff --git a/lib/WeBWorK/ContentGenerator/Instructor/Stats.pm b/lib/WeBWorK/ContentGenerator/Instructor/Stats.pm index 63ef67a7bb..ea59388414 100644 --- a/lib/WeBWorK/ContentGenerator/Instructor/Stats.pm +++ b/lib/WeBWorK/ContentGenerator/Instructor/Stats.pm @@ -567,6 +567,7 @@ sub build_bar_chart ($c, $data, %options) { viewbox => '-2 -2 ' . ($imageWidth + 3) . ' ' . ($imageHeight + 3), 'aria-labelledby' => "bar_graph_title_$id", role => 'img', + class => 'stats-image', -nocredits => 1 ); diff --git a/lib/WeBWorK/ContentGenerator/Problem.pm b/lib/WeBWorK/ContentGenerator/Problem.pm index 9e0c5f92f7..85a1c5e0ab 100644 --- a/lib/WeBWorK/ContentGenerator/Problem.pm +++ b/lib/WeBWorK/ContentGenerator/Problem.pm @@ -1042,7 +1042,14 @@ sub output_problem_body ($c) { } else { # For students render the body text of the problem with a message about error details. return $c->c( - $c->tag('div', id => 'output_problem_body', $c->b($c->{pg}{body_text})), + $c->tag( + 'div', + id => 'output_problem_body', + class => 'text-dark', + style => 'color-scheme: light', + data => { bs_theme => 'light' }, + $c->b($c->{pg}{body_text}) + ), $c->include( 'ContentGenerator/Base/error_output', error => $c->{pg}{errors}, @@ -1052,7 +1059,14 @@ sub output_problem_body ($c) { } } - return $c->tag('div', id => 'output_problem_body', $c->b($c->{pg}{body_text})); + return $c->tag( + 'div', + id => 'output_problem_body', + class => 'text-dark', + style => 'color-scheme: light', + data => { bs_theme => 'light' }, + $c->b($c->{pg}{body_text}) + ); } # Output messages about the problem diff --git a/templates/ContentGenerator/Base/login_status.html.ep b/templates/ContentGenerator/Base/login_status.html.ep index 1e835b84ff..aca49ad3ca 100644 --- a/templates/ContentGenerator/Base/login_status.html.ep +++ b/templates/ContentGenerator/Base/login_status.html.ep @@ -6,10 +6,12 @@ % my $effectiveUserID = param('effectiveUser'); % my $userName = $user->full_name || $user->user_id; % - <%= maketext('Logged in as [_1].', $userName) %> - <%= link_to $c->systemLink(url_for 'logout'), class => 'btn btn-light btn-sm ms-2', begin %> - <%= maketext('Log Out') %> - <% end %> +
+ <%= maketext('Logged in as [_1].', $userName) %> + <%= link_to $c->systemLink(url_for 'logout'), class => 'btn btn-light btn-sm ms-2', begin %> + <%= maketext('Log Out') %> + <% end %> +
% % if ($effectiveUserID ne $userID) { % my $effectiveUser = $db->getUser($effectiveUserID); @@ -22,13 +24,14 @@ % ? join(' ', $effectiveUser->full_name, '(' . $effectiveUser->user_id . ')') % : $effectiveUser->user_id; % -
- <%= maketext('Acting as [_1].', $effectiveUserName) %> - <%= link_to $c->systemLink(url_for, params => { effectiveUser => $userID }), - class => 'btn btn-light btn-sm ms-2', begin %> - <%= maketext('Stop Acting') %> - <% end %> +
+ <%= maketext('Acting as [_1].', $effectiveUserName) %> + <%= link_to $c->systemLink(url_for, params => { effectiveUser => $userID }), + class => 'btn btn-light btn-sm ms-2', begin %> + <%= maketext('Stop Acting') %> + <% end %> +
% } % } else { - <%= maketext('Not logged in.') =%> +
<%= maketext('Not logged in.') %>
% } diff --git a/templates/ContentGenerator/GatewayQuiz.html.ep b/templates/ContentGenerator/GatewayQuiz.html.ep index 65d8f322cf..186ef41b5e 100644 --- a/templates/ContentGenerator/GatewayQuiz.html.ep +++ b/templates/ContentGenerator/GatewayQuiz.html.ep @@ -169,7 +169,7 @@ % && $c->{can}{showScore} % ) % { -
+
<%== maketext( 'Your recorded score for this version is [_1]/[_2] ([_3]%).', '' . wwRound(2, $c->{recordedScore}) . '', @@ -182,7 +182,7 @@ % } % } elsif ($c->{will}{checkAnswers}) { % if ($c->{can}{showScore}) { -
+
<%= maketext('Your score on this (checked, not recorded) submission is [_1]/[_2].', $c->{attemptScore}, $c->{totalPossible}) =%> @@ -207,7 +207,7 @@ % && before($c->{set}->due_date, $submitTime) % && ($c->{can}{recordAnswersNextTime} || $c->{submitAnswers})) % { -
+
% my $text = maketext('You are in the Reduced Scoring Period. All work counts for [_1]% of the original.', % $ce->{pg}{ansEvalDefaults}{reducedScoringValue} * 100); @@ -309,7 +309,7 @@ % } % } else { % if (!$c->{checkAnswers} && !$c->{submitAnswers} && $c->{can}{showScore}) { -
+
<%== maketext( 'Your recorded score on this test (version [_1]) is [_2]/[_3] ([_4]%).', @@ -331,12 +331,12 @@ % } % % if ($c->{set}->version_last_attempt_time) { -
+
<%= maketext('Time taken on test: [_1] min ([_2] min allowed).', $c->{elapsedTime}, sprintf('%.0f', 10 * ($c->{set}->due_date - $c->{set}->open_date) / 6) / 100) %>
% } elsif ($c->{exceededAllowedTime} && $c->{recordedScore} != 0) { -
+
<%= maketext('(This test is overtime because it was not submitted in the allowed time.)') %>
% } @@ -594,9 +594,9 @@ % % delete stash->{briefErrorOutput}; % } else { -
{flags}, $ce->{perProblemLangAndDirSettingMode}, $ce->{language} - ) %>> + ) %> data-bs-theme="light"> <%== $pg->{body_text} =%>
% } diff --git a/templates/ContentGenerator/Instructor/ProblemGrader.html.ep b/templates/ContentGenerator/Instructor/ProblemGrader.html.ep index 920182f8a7..b2c267987e 100644 --- a/templates/ContentGenerator/Instructor/ProblemGrader.html.ep +++ b/templates/ContentGenerator/Instructor/ProblemGrader.html.ep @@ -168,11 +168,14 @@ % my @answers = split(/\t/, $_->{past_answer}->answer_string); % % for (my $i = 0; $i <= $#answers; $i++) { - % # Color the answer if it is not an essay answer. -
+
% if ($answerTypes[$i] && $answerTypes[$i] eq 'essay') { % # If the answer is an essay answer then display it line by line.
@@ -223,13 +226,13 @@ % if ($problemNeedsGrading) { -
+
<%= maketext('Name') %> \( = \) <%= maketext('Problem has an essay answer that needs to be graded.') =%>
% } -
+
<%= maketext('Latest Answers') %> \( = \) <%= maketext('Problem has a different source file than the currently rendered problem.') =%> diff --git a/templates/ContentGenerator/Instructor/ProblemSetDetail.html.ep b/templates/ContentGenerator/Instructor/ProblemSetDetail.html.ep index 0d81c50a5c..5c1991d475 100644 --- a/templates/ContentGenerator/Instructor/ProblemSetDetail.html.ep +++ b/templates/ContentGenerator/Instructor/ProblemSetDetail.html.ep @@ -5,6 +5,7 @@ % % content_for css => begin <%= stylesheet getAssetURL($ce, 'node_modules/flatpickr/dist/flatpickr.min.css') =%> + <%= stylesheet getAssetURL($ce, 'node_modules/flatpickr/dist/themes/dark.css'), id => 'flatpickr-dark-theme' =%> <%= stylesheet getAssetURL($ce, 'node_modules/flatpickr/dist/plugins/confirmDate/confirmDate.css') =%> % end % @@ -116,7 +117,7 @@ % } % @$userLinks = sort @$userLinks; % -
+
<%== maketext( @@ -151,7 +152,7 @@
% } else { -
+
<%== maketext( @@ -198,7 +199,7 @@
% % # General set information -
+
<%= maketext('Set Parameters') %>
@@ -226,7 +227,7 @@ % % # Display header information % if (!@editForUser) { -
+
<%= maketext("Headers") %>
% for my $headerType (@$headers) { diff --git a/templates/ContentGenerator/Instructor/ProblemSetList.html.ep b/templates/ContentGenerator/Instructor/ProblemSetList.html.ep index 8f0a71aee9..46a55b57af 100644 --- a/templates/ContentGenerator/Instructor/ProblemSetList.html.ep +++ b/templates/ContentGenerator/Instructor/ProblemSetList.html.ep @@ -2,6 +2,7 @@ % % content_for css => begin <%= stylesheet getAssetURL($ce, 'node_modules/flatpickr/dist/flatpickr.min.css') =%> + <%= stylesheet getAssetURL($ce, 'node_modules/flatpickr/dist/themes/dark.css'), id => 'flatpickr-dark-theme' =%> <%= stylesheet getAssetURL($ce, 'node_modules/flatpickr/dist/plugins/confirmDate/confirmDate.css') =%> % end % diff --git a/templates/ContentGenerator/Instructor/SetMaker/problem_row.html.ep b/templates/ContentGenerator/Instructor/SetMaker/problem_row.html.ep index 87cfaf1353..c1af9c734e 100644 --- a/templates/ContentGenerator/Instructor/SetMaker/problem_row.html.ep +++ b/templates/ContentGenerator/Instructor/SetMaker/problem_row.html.ep @@ -46,7 +46,7 @@ % if ($stats->{students_attempted}) { % content_for "global-problem-stats-$cnt" => begin
- begin
- > + $ce->{sessionTimeout} ? 'class="table-rule"' : '' %>> % # Show the problem seed for instructors. % if ($isInstructor) { @@ -47,9 +46,13 @@ % % # Color the answer if the user is an instructor, there is an answer, % # there is a score, and it is not an essay question. - class="px-3 <%== $answerType eq 'essay' ? 'essay' : '' %>"> + % if ($answer eq '') { <%= maketext('empty') %> % } else { diff --git a/templates/ContentGenerator/Instructor/Stats/problem_menu.html.ep b/templates/ContentGenerator/Instructor/Stats/problem_menu.html.ep index a0aea45c9f..4b8c7c792c 100644 --- a/templates/ContentGenerator/Instructor/Stats/problem_menu.html.ep +++ b/templates/ContentGenerator/Instructor/Stats/problem_menu.html.ep @@ -10,7 +10,7 @@ url_for('instructor_set_statistics'), params => param('filter') ? { filter => param('filter') } : {} ), - class => 'dropdown-item', $c->{prettyID} ? () : (style => 'background-color: #8F8') =%> + class => join(' ', 'dropdown-item', $c->{prettyID} ? () : 'active') =%> % for (@$problems) { % my $probID = @@ -22,8 +22,7 @@ url_for('instructor_problem_statistics', problemID => $_->problem_id), params => param('filter') ? { filter => param('filter') } : {} ), - class => 'dropdown-item', - $probID eq ($c->{prettyID} // '') ? (style => 'background-color: #8F8') : () =%> + class => join(' ', 'dropdown-item', $probID eq ($c->{prettyID} // '') ? 'active' : ()) =%> % } diff --git a/templates/ContentGenerator/Instructor/Stats/student_filter_menu.html.ep b/templates/ContentGenerator/Instructor/Stats/student_filter_menu.html.ep index 98ba7f083e..12308273d6 100644 --- a/templates/ContentGenerator/Instructor/Stats/student_filter_menu.html.ep +++ b/templates/ContentGenerator/Instructor/Stats/student_filter_menu.html.ep @@ -13,7 +13,7 @@ % for (@$filters) {
  • <%= link_to $_->[0] => $c->systemLink(url_for, params => { %$params, filter => $_->[1] }), - $_->[1] eq $filter ? (style => 'background-color: #8F8') : (), class => 'dropdown-item' =%> + class => join(' ', 'dropdown-item', $_->[1] eq $filter ? 'active' : ()) =%>
  • % } diff --git a/templates/ContentGenerator/Instructor/UserDetail.html.ep b/templates/ContentGenerator/Instructor/UserDetail.html.ep index 1ef3720df6..4e4c0447fd 100644 --- a/templates/ContentGenerator/Instructor/UserDetail.html.ep +++ b/templates/ContentGenerator/Instructor/UserDetail.html.ep @@ -2,6 +2,7 @@ % % content_for css => begin <%= stylesheet getAssetURL($ce, 'node_modules/flatpickr/dist/flatpickr.min.css') =%> + <%= stylesheet getAssetURL($ce, 'node_modules/flatpickr/dist/themes/dark.css'), id => 'flatpickr-dark-theme' =%> <%= stylesheet getAssetURL($ce, 'node_modules/flatpickr/dist/plugins/confirmDate/confirmDate.css') =%> % end % diff --git a/templates/HTML/StudentNav/student_nav.html.ep b/templates/HTML/StudentNav/student_nav.html.ep index f9a8919eb7..10070e7512 100644 --- a/templates/HTML/StudentNav/student_nav.html.ep +++ b/templates/HTML/StudentNav/student_nav.html.ep @@ -36,8 +36,9 @@ url_for, params => { effectiveUser => $_->user_id, $filter ? (studentNavFilter => $filter) : () } ), - $eUserID ne $userID && $_->{currentUser} ? (style => 'background-color:#8F8') : (), - class => 'dropdown-item' =%> + class => + join(' ', 'dropdown-item', $eUserID ne $userID && $_->{currentUser} ? 'active' : ()) + =%> % } @@ -86,8 +87,7 @@ studentNavFilter => $_ } ), - ($filter || '') eq $_ ? (style => 'background-color:#8F8') : (), - class => 'dropdown-item' =%> + class => join(' ', 'dropdown-item', ($filter || '') eq $_ ? 'active' : ()) =%> % } diff --git a/templates/layouts/system.html.ep b/templates/layouts/system.html.ep index 282890b782..ba838025ab 100644 --- a/templates/layouts/system.html.ep +++ b/templates/layouts/system.html.ep @@ -23,6 +23,8 @@ % % # JS Loads +% # The color scheme JavaScript must be loaded early and not deferred to prevent flickering as the color scheme is set. +<%= javascript $c->url({ type => 'webwork', name => 'htdocs', file => 'js/System/color-scheme.js' }) =%> <%= javascript $c->url({ type => 'webwork', name => 'htdocs', file => 'js/MathJaxConfig/mathjax-config.js' }), defer => undef =%> <%= javascript $c->url({ type => 'webwork', name => 'htdocs', file => 'node_modules/mathjax/tex-svg.js' }), @@ -47,24 +49,66 @@ % <%= link_to 'Skip to main content' => '#page-title', - class => 'visually-hidden-focusable bg-white p-2 m-3 position-absolute' =%> + class => 'visually-hidden-focusable bg-light p-2 m-3 position-absolute' =%> % % # Header % # Navigation % if ($c->can('links') || $c->can('siblings') || $c->can('options')) {