diff --git a/requirements.txt b/requirements.txt index ce6e490..2bb51a2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ Django==6.0.4 # Base django module -django-bootstrap4==26.1 # CSS base +django-bootstrap5 # CSS base django-crispy-forms==2.6 # Nice looking forms django-tables2==3.0.0 # Tables for lists django-filter==25.2 # Filtering the lists diff --git a/rodatraden/static/rodatraden/block_detail.js b/rodatraden/static/rodatraden/block_detail.js index 59a3904..9240410 100644 --- a/rodatraden/static/rodatraden/block_detail.js +++ b/rodatraden/static/rodatraden/block_detail.js @@ -335,14 +335,19 @@ function main() { createCategorySumChart(); // Show the name of the file to be uploaded when a new file has been selected. - document.querySelector('.custom-file-input').addEventListener( - 'change', - function(e) { - const fileName = document.getElementById("inputExcelFile").files[0].name; - let label = e.target.nextElementSibling; - label.innerText = fileName; - } - ); + const inputExcelFile = document.getElementById('inputExcelFile'); + if (inputExcelFile) { + inputExcelFile.addEventListener('change', function(e) { + if (!inputExcelFile.files || inputExcelFile.files.length === 0) { + return; + } + const fileName = inputExcelFile.files[0].name; + const label = e.target.nextElementSibling; + if (label) { + label.innerText = fileName; + } + }); + } const courseOccasions = CourseOccasion.fromElement('course-occasions-data'); @@ -436,9 +441,9 @@ function main() { return; } - // Handle clicks on elements with data-dismiss="modal" (close buttons). + // Handle clicks on elements with data-bs-dismiss="modal" (close buttons). // This makes Bootstrap-style close buttons work with native . - const dismissButton = event.target.closest('[data-dismiss="modal"]'); + const dismissButton = event.target.closest('[data-bs-dismiss="modal"]'); if (dismissButton) { event.preventDefault(); closeModalWithAnimation(); diff --git a/rodatraden/static/rodatraden/block_schedule/block_schedule_renderer.js b/rodatraden/static/rodatraden/block_schedule/block_schedule_renderer.js index c69f65e..9e61837 100644 --- a/rodatraden/static/rodatraden/block_schedule/block_schedule_renderer.js +++ b/rodatraden/static/rodatraden/block_schedule/block_schedule_renderer.js @@ -673,13 +673,13 @@ export function updateCourseBlocks(courseContainer, // The attributes are added to all course blocks, but this doesn't matter // since the tooltip is only shown for the blocks if the title attribute // contains something. - // NOTE: The "data-toggle" attribute is missing on purpose for unmet + // NOTE: The "data-bs-toggle" attribute is missing on purpose for unmet // prerequisites. This is so that the tooltip isn't enabled when - // "$('[data-toggle="tooltip"]').tooltip()" is called somewhere else. + // "$("[data-bs-toggle="tooltip"]").tooltip()" is called somewhere else. // For retakes, we always show the tooltip. course - .attr("data-placement", "left") - .attr("data-toggle", course => { + .attr("data-bs-placement", "left") + .attr("data-bs-toggle", course => { // Only enable tooltip automatically for retakes (not for unmet prerequisites) if (course.isRetake && course.unmetPrerequisiteIDs.length === 0) { return "tooltip"; @@ -757,8 +757,8 @@ export function addFooter(term, isLoggedIn, blockCourseListUrl, blockSchedule) { // Setup data for the pop-up modal. newButton - .attr("data-toggle", "tooltip") - .attr("data-placement", "left") + .attr("data-bs-toggle", "tooltip") + .attr("data-bs-placement", "left") .attr('data-url', button => { // XXX: Why use the start weeks 0, 10, 20 and 30 when the period number // would do? The code would be clearer if the server did the week @@ -864,10 +864,10 @@ export function updatePrerequisiteWarnings() { const shouldUseWarningColor = checkbox.checked && hasUnmet && !isManualSkip; if (shouldShowWarning) { - course.setAttribute('data-toggle', 'tooltip'); + course.setAttribute('data-bs-toggle', 'tooltip'); $(course).tooltip('enable'); } else { - course.removeAttribute('data-toggle'); + course.removeAttribute('data-bs-toggle'); $(course).tooltip('disable'); } diff --git a/rodatraden/static/rodatraden/category_form.js b/rodatraden/static/rodatraden/category_form.js index 5a2438e..c3c3908 100644 --- a/rodatraden/static/rodatraden/category_form.js +++ b/rodatraden/static/rodatraden/category_form.js @@ -50,5 +50,5 @@ $(function() { } }); - $('[data-toggle="tooltip"]').tooltip(); + $('[data-bs-toggle="tooltip"]').tooltip(); }); diff --git a/rodatraden/static/rodatraden/course/form_categories.js b/rodatraden/static/rodatraden/course/form_categories.js index d864717..17b2919 100644 --- a/rodatraden/static/rodatraden/course/form_categories.js +++ b/rodatraden/static/rodatraden/course/form_categories.js @@ -49,5 +49,5 @@ $(function() { $(this).parent().remove(); }); - $('[data-toggle="tooltip"]').tooltip() + $('[data-bs-toggle="tooltip"]').tooltip() }); diff --git a/rodatraden/static/rodatraden/rodatraden.css b/rodatraden/static/rodatraden/rodatraden.css index ddb0158..083207e 100644 --- a/rodatraden/static/rodatraden/rodatraden.css +++ b/rodatraden/static/rodatraden/rodatraden.css @@ -36,7 +36,24 @@ } .card-columns { - @include media-breakpoint-only(lg) { + column-count: 1; + column-gap: 1.25rem; +} + +.card-columns .card { + display: inline-block; + width: 100%; + margin: 0 0 1.25rem; +} + +@media (min-width: 768px) { + .card-columns { + column-count: 2; + } +} + +@media (min-width: 1200px) { + .card-columns { column-count: 3; } } @@ -97,7 +114,7 @@ } /* Keep page action buttons clickable even when nearby content overlaps. */ -.main-template > .text-right { +.main-template > .text-end { position: relative; z-index: 3; } @@ -131,16 +148,34 @@ background-color: transparent; } -/* Red links */ +/* Red links (low specificity so contextual rules like .course a can override). */ a:not(.default) { color: #D6001E; + text-decoration: none; } -a:not(.default):hover{ +a:not(.default):hover { color: #b30018; text-decoration: none; } +/* Keep navbar links using navbar palette rather than global link color. */ +.primary-nav .navbar-brand, +.primary-nav .nav-link, +.primary-nav .dropdown-item { + color: inherit; +} + +/* Keep dropdown menu items in navbar red as before migration. */ +.primary-nav .dropdown-menu .dropdown-item { + color: #D6001E; +} + +.primary-nav .dropdown-menu .dropdown-item:hover, +.primary-nav .dropdown-menu .dropdown-item:focus { + color: #fff; +} + /* List without background */ .list-group-item { background-color: transparent; @@ -185,6 +220,11 @@ a:not(.default):hover{ opacity: 0; } +/* Ensure fetched course info content always has a solid modal background. */ +.native-modal .modal-content { + background-color: var(--bs-modal-bg, #fff); +} + .native-modal::backdrop { background: rgb(0 0 0 / 0.5); } diff --git a/rodatraden/static/rodatraden/rodatraden.js b/rodatraden/static/rodatraden/rodatraden.js index 15e5578..4b0a268 100644 --- a/rodatraden/static/rodatraden/rodatraden.js +++ b/rodatraden/static/rodatraden/rodatraden.js @@ -1,7 +1,48 @@ -/* Bootstrap nice tooltips */ -$(function () { $('[data-toggle="tooltip"]').tooltip() }) -/* BOotstrap dropdown */ -$(function(){ $('.dropdown-toggle').dropdown(); }); +/* Provide jQuery compatibility wrappers for Bootstrap 5 APIs. */ +(function (window) { + if (!window.jQuery || !window.bootstrap) { + return; + } + + const $ = window.jQuery; + + if (!$.fn.tooltip && window.bootstrap.Tooltip) { + $.fn.tooltip = function (action) { + return this.each(function () { + const instance = window.bootstrap.Tooltip.getOrCreateInstance(this); + if (action === 'dispose') instance.dispose(); + else if (action === 'enable') instance.enable(); + else if (action === 'disable') instance.disable(); + else if (action === 'hide') instance.hide(); + else if (action === 'show') instance.show(); + }); + }; + } + + if (!$.fn.dropdown && window.bootstrap.Dropdown) { + $.fn.dropdown = function (action) { + return this.each(function () { + const instance = window.bootstrap.Dropdown.getOrCreateInstance(this); + if (action === 'show') instance.show(); + else if (action === 'hide') instance.hide(); + else if (action === 'toggle') instance.toggle(); + }); + if (!$.fn.dropdown && window.bootstrap.Dropdown) { + $.fn.dropdown = function (action) { + return this.each(function () { + const instance = window.bootstrap.Dropdown.getOrCreateInstance(this); + if (action === 'show') instance.show(); + else if (action === 'hide') instance.hide(); + else if (action === 'toggle') instance.toggle(); + }); + }; + } +})(window); + +/* Bootstrap tooltips */ +$(function () { $('[data-bs-toggle="tooltip"]').tooltip() }); +/* Bootstrap dropdown */ +$(function () { $('.dropdown-toggle').dropdown(); }); /* Hide alerts automatically */ $(".alert").fadeTo(2000, 500).slideUp(500, function(){ diff --git a/rodatraden/templates/rodatraden/base.html b/rodatraden/templates/rodatraden/base.html index 9628a9c..090ecb1 100644 --- a/rodatraden/templates/rodatraden/base.html +++ b/rodatraden/templates/rodatraden/base.html @@ -1,11 +1,11 @@ {# Load the tag library #} -{% load bootstrap4 %} +{% load django_bootstrap5 %} {# Load CSS and JavaScript #} {% bootstrap_css %} -{% bootstrap_javascript jquery='full' %} +{% bootstrap_javascript %} {# Display django.contrib.messages as Bootstrap alerts #} {% bootstrap_messages %} @@ -47,7 +47,7 @@ -
+
{% block menu %} {% endblock %}
@@ -80,8 +80,11 @@

+ - + diff --git a/rodatraden/templates/rodatraden/block/block_confirm_delete.html b/rodatraden/templates/rodatraden/block/block_confirm_delete.html index 6bc22cb..67c6615 100644 --- a/rodatraden/templates/rodatraden/block/block_confirm_delete.html +++ b/rodatraden/templates/rodatraden/block/block_confirm_delete.html @@ -5,9 +5,7 @@