diff --git a/htdocs/js/DragNDrop/dragndrop.js b/htdocs/js/DragNDrop/dragndrop.js index f4f2edc9a..983967389 100644 --- a/htdocs/js/DragNDrop/dragndrop.js +++ b/htdocs/js/DragNDrop/dragndrop.js @@ -410,6 +410,7 @@ htmlBucket(label, removable, indices = []) { const bucketElement = document.createElement('div'); bucketElement.classList.add('dd-bucket'); + bucketElement.dataset.bsTheme = 'light'; const bucketLabel = document.createElement('div'); bucketLabel.classList.add('dd-bucket-label'); diff --git a/htdocs/js/DropDown/dropdown.scss b/htdocs/js/DropDown/dropdown.scss index 9bb5620ac..819f05a60 100644 --- a/htdocs/js/DropDown/dropdown.scss +++ b/htdocs/js/DropDown/dropdown.scss @@ -16,6 +16,15 @@ --bs-btn-active-border-color: #ccc; --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + [data-bs-theme='dark'] & { + --bs-btn-color: #bbb; + --bs-btn-bg: black; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #2c2b2a; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #2c2b2a; + } + &.show { border-color: rgba(112, 154, 192, 0.8); outline: 0; @@ -37,5 +46,11 @@ --bs-dropdown-link-active-color: black; --bs-dropdown-link-active-bg: lightgray; --bs-dropdown-link-hover-bg: #d3d3d387; + + [data-bs-theme='dark'] & { + --bs-dropdown-link-active-color: white; + --bs-dropdown-link-active-bg: #737373; + --bs-dropdown-link-hover-bg: #77777777; + } } } diff --git a/htdocs/js/GraphTool/graphtool.js b/htdocs/js/GraphTool/graphtool.js index 064d3dafb..14d213a36 100644 --- a/htdocs/js/GraphTool/graphtool.js +++ b/htdocs/js/GraphTool/graphtool.js @@ -126,6 +126,7 @@ window.graphTool = (containerId, options) => { descriptionSpan.textContent = options.ariaDescription ?? 'Interactively graph objects'; gt.board.containerObj.after(descriptionSpan); gt.board.containerObj.setAttribute('aria-describedby', descriptionSpan.id); + gt.board.containerObj.dataset.bsTheme = 'light'; gt.board.suspendUpdate(); diff --git a/htdocs/js/GraphTool/graphtool.scss b/htdocs/js/GraphTool/graphtool.scss index e046612fe..97127c699 100644 --- a/htdocs/js/GraphTool/graphtool.scss +++ b/htdocs/js/GraphTool/graphtool.scss @@ -6,6 +6,10 @@ border-radius: 10px; box-shadow: inset 0 0 5px 5px rgba(0, 0, 0, 0.15); + [data-bs-theme='dark'] & { + box-shadow: inset 0 0 5px 5px rgba(255, 255, 255, 0.15); + } + @media only screen and (max-width: 600px) { width: 342px; } @@ -276,6 +280,7 @@ flex-direction: column; width: calc(100% - 40px); background-color: #fff; + color: #000; opacity: 0; transition: all 0.2s ease-in-out; @@ -339,11 +344,15 @@ } .gt-fullscreenwrap:fullscreen { - background-color: #ccc; + background-color: #f5f5f5; padding: 0; width: 100%; height: 100%; + [data-bs-theme='dark'] & { + background-color: #252525; + } + .graphtool-container { margin: 0 auto; diff --git a/htdocs/js/ImageView/imageview.js b/htdocs/js/ImageView/imageview.js index 2a489ee26..34c3ff3c3 100644 --- a/htdocs/js/ImageView/imageview.js +++ b/htdocs/js/ImageView/imageview.js @@ -31,12 +31,6 @@ modal.setAttribute('aria-label', 'image view dialog'); modal.tabIndex = -1; - // Force the dialog into light mode. This is needed for a webwork2 page in dark mode since the dialog is outside - // of the problem content. At least until PG is updated to honor dark mode. Further discussion on this will - // also be needed at that time since many images have transparent backgrounds that will not work with a dark - // background. - modal.dataset.bsTheme = 'light'; - const dialog = document.createElement('div'); dialog.classList.add('modal-dialog'); @@ -118,6 +112,7 @@ const body = document.createElement('div'); body.classList.add('modal-body'); + body.dataset.bsTheme = 'light'; let graphDiv = null; if (imgType == 'div') { @@ -335,26 +330,44 @@ } }; - // Set up images that are already in the page. - document.querySelectorAll('.image-view-elt').forEach((elt) => { - elt.addEventListener('click', imageViewDialog); - elt.addEventListener('keydown', keyHandler); - }); + const handleBrokenImage = (img) => { + img.classList.add('broken'); + img.removeAttribute('role'); + }; + + const attachListeners = (img) => { + img.removeEventListener('click', imageViewDialog); + img.removeEventListener('keydown', keyHandler); + img.addEventListener('click', imageViewDialog); + img.addEventListener('keydown', keyHandler); + }; - const attachListeners = (node) => { - node.removeEventListener('click', imageViewDialog); - node.removeEventListener('keydown', keyHandler); - node.addEventListener('click', imageViewDialog); - node.addEventListener('keydown', keyHandler); + const initializeImgViewElt = (img) => { + if (img instanceof HTMLImageElement) { + if (img.complete) { + if (img.naturalWidth === 0) handleBrokenImage(img); + else attachListeners(img); + } else { + img.addEventListener('error', () => handleBrokenImage(img)); + img.addEventListener('load', () => attachListeners(img)); + } + } else { + attachListeners(img); + } }; + // Set up images that are already in the page. + for (const elt of document.querySelectorAll('.image-view-elt')) { + initializeImgViewElt(elt); + } + // Deal with images that are added to the page later. const observer = new MutationObserver((mutationsList) => { mutationsList.forEach((mutation) => { mutation.addedNodes.forEach((node) => { if (node instanceof Element) { - if (node.classList.contains('image-view-elt')) attachListeners(node); - else node.querySelectorAll('.image-view-elt').forEach(attachListeners); + if (node.classList.contains('image-view-elt')) initializeImgViewElt(node); + else node.querySelectorAll('.image-view-elt').forEach(initializeImgViewElt); } }); }); diff --git a/htdocs/js/ImageView/imageview.scss b/htdocs/js/ImageView/imageview.scss index b0b3f5129..1aff91151 100644 --- a/htdocs/js/ImageView/imageview.scss +++ b/htdocs/js/ImageView/imageview.scss @@ -1,8 +1,12 @@ .image-view-elt { max-width: 100%; - &:hover { - cursor: pointer; + &:not(.broken) { + background-color: #f5f5f5; + + &:hover { + cursor: pointer; + } } &.top { @@ -32,6 +36,7 @@ padding: 8px; text-align: center; box-sizing: content-box !important; + background-color: white; img { max-width: 100%; @@ -54,6 +59,7 @@ } .btn { + --bs-btn-box-shadow: none; padding: 0 0.2rem; margin: 0 0.25rem 0 0; border: none; diff --git a/htdocs/js/Knowls/knowl.js b/htdocs/js/Knowls/knowl.js index 0894f988f..5e23b67e2 100644 --- a/htdocs/js/Knowls/knowl.js +++ b/htdocs/js/Knowls/knowl.js @@ -24,8 +24,8 @@ knowl.knowlModal.setAttribute('aria-labelledby', `${knowl.knowlModal.id}-title`); knowl.knowlModal.setAttribute('aria-hidden', 'true'); - // Force the dialog into light mode. This is needed for a webwork2 page in dark mode since the dialog is - // outside of the problem content. At least until PG and the help files are updated to honor dark mode. + // Force the dialog into light mode. This is needed at least until + // the knowl css and help files are updated to honor dark mode. knowl.knowlModal.dataset.bsTheme = 'light'; const knowlDialog = document.createElement('div'); diff --git a/htdocs/js/MathQuill/mqeditor.js b/htdocs/js/MathQuill/mqeditor.js index f131951b9..0ab938c7d 100644 --- a/htdocs/js/MathQuill/mqeditor.js +++ b/htdocs/js/MathQuill/mqeditor.js @@ -170,18 +170,17 @@ title.textContent = 'Equation Editor'; const closeButton = document.createElement('button'); - // When bootstrap is upgraded to version 5.3 this will need to be changed. - // btn-close-white will be deprecated and data-bs-theme="dark" is used instead. - closeButton.classList.add('btn-close', 'btn-close-white'); + closeButton.classList.add('btn-close'); closeButton.type = 'button'; closeButton.setAttribute('aria-label', 'Close'); + closeButton.dataset.bsTheme = 'dark'; closeButton.dataset.bsToggle = 'collapse'; closeButton.dataset.bsTarget = `#${answerLabel}-equation-editor`; cardHeader.append(title, closeButton); const cardBody = document.createElement('div'); - cardBody.classList.add('card-body', 'p-2', 'd-flex', 'align-items-center'); + cardBody.classList.add('card-body', 'p-2', 'd-flex', 'align-items-center', 'bg-light-subtle'); cardBody.append(answerQuill); // Insert text at a the current cursor position in a text input replacing the current selection if any. @@ -213,7 +212,7 @@ 'pb-2', 'px-2', 'gap-2', - 'bg-white', + 'bg-light-subtle', 'border-top-0' ); diff --git a/htdocs/js/MathQuill/mqeditor.scss b/htdocs/js/MathQuill/mqeditor.scss index 870b99cbb..2ae630505 100644 --- a/htdocs/js/MathQuill/mqeditor.scss +++ b/htdocs/js/MathQuill/mqeditor.scss @@ -8,6 +8,10 @@ span[id^='mq-answer'] { background-color: white; margin-right: 0; margin-left: 0; + + [data-bs-theme='dark'] & { + background-color: black; + } } input[type='text'].codeshard.mq-edit { diff --git a/htdocs/js/Problem/problem.scss b/htdocs/js/Problem/problem.scss index 442a07684..21bb2e775 100644 --- a/htdocs/js/Problem/problem.scss +++ b/htdocs/js/Problem/problem.scss @@ -13,6 +13,11 @@ border-radius: 4px; box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); + [data-bs-theme='dark'] & { + background-color: #252525; + border-color: #e3e3e3; + } + p { margin-top: 1rem; margin-bottom: 1rem; @@ -39,6 +44,11 @@ border: 1px solid #ccc; border-radius: 4px; background-color: white; + + [data-bs-theme='dark'] & { + color: #bbb; + background-color: black; + } } textarea, @@ -357,6 +367,9 @@ .popover-header { --bs-popover-header-bg: #ffc107; --bs-popover-header-color: black; + .btn-close { + --bs-btn-close-filter: invert(0) grayscale(100%) brightness(200%); + } } } @@ -385,6 +398,10 @@ .card { --bs-card-cap-bg: #ddd; + [data-bs-theme='dark'] & { + --bs-card-cap-bg: #333; + } + .card-header { border-radius: 0; @@ -400,6 +417,10 @@ .parsehilight { background-color: yellow; + + [data-bs-theme='dark'] & { + background-color: #550; + } } .ArrayLayout { @@ -414,6 +435,7 @@ &.feedback-message { direction: ltr; background-color: #ede275; + color: #212529; &:not(:last-child) { border-bottom: 1px solid black; } diff --git a/htdocs/js/Scaffold/scaffold.scss b/htdocs/js/Scaffold/scaffold.scss index 5393d530b..8bc6b636f 100644 --- a/htdocs/js/Scaffold/scaffold.scss +++ b/htdocs/js/Scaffold/scaffold.scss @@ -58,6 +58,10 @@ & > div.accordion-collapse { background: #fafafa; border-top: 1px solid rgba(0, 0, 0, 0.125); + + [data-bs-theme='dark'] & { + background: #151515; + } } } diff --git a/lib/Plots/JSXGraph.pm b/lib/Plots/JSXGraph.pm index 7c8f09db2..21d034154 100644 --- a/lib/Plots/JSXGraph.pm +++ b/lib/Plots/JSXGraph.pm @@ -43,7 +43,7 @@ sub HTML { my $divs = qq!
!; + . qq!style="width: ${width}px; height: ${height}px;" data-bs-theme="light"$aria_details>!; $divs = qq!
$divs$details
! if $details; my $axes = $plots->axes; diff --git a/macros/core/PGbasicmacros.pl b/macros/core/PGbasicmacros.pl index e6bd3f4b9..d4676bced 100644 --- a/macros/core/PGbasicmacros.pl +++ b/macros/core/PGbasicmacros.pl @@ -955,7 +955,7 @@ sub SOLUTION { class => 'accordion-item', tag( 'summary', - class => 'accordion-button collapsed text-primary fw-bold py-2', + class => 'accordion-button collapsed text-primary-emphasis fw-bold py-2', tag('span', class => 'accordion-header user-select-none', SOLUTION_HEADING()) ) . tag( @@ -997,7 +997,7 @@ sub HINT { class => 'accordion-item', tag( 'summary', - class => 'accordion-button collapsed text-primary fw-bold py-2', + class => 'accordion-button collapsed text-primary-emphasis fw-bold py-2', tag('span', class => 'accordion-header user-select-none', HINT_HEADING()) ) . tag( @@ -2845,7 +2845,7 @@ sub image { . tag( 'div', id => 'LONG-DESCRIPTION-ID', - class => 'image-details-content bg-white py-2 px-3 my-2 border', + class => 'image-details-content bg-light-subtle py-2 px-3 my-2 border', $description_details . tag( 'div',