From 90e9415c5839278c60987f3aba02877b5d4f639f Mon Sep 17 00:00:00 2001 From: Juliet Shin Date: Thu, 21 May 2026 17:04:34 -0700 Subject: [PATCH 1/4] Initial changes --- Dockerfile | 2 + Dockerfile.dev | 2 + .../stylesheets/blocks/_modal_search.scss | 11 +- app/controllers/contributors_controller.rb | 3 +- app/javascript/application.js | 1 + app/javascript/src/utils/accordion.js | 3 +- .../src/utils/bootstrapLegacyCompat.js | 214 ++ app/javascript/src/utils/popoverHelper.js | 2 +- app/javascript/src/utils/tooltipHelper.js | 2 +- app/views/phases/_guidances_notes.html.erb | 15 +- config/webpack/webpack.config.js | 6 + package.json | 63 +- yarn.lock | 2183 +++++++---------- 13 files changed, 1200 insertions(+), 1307 deletions(-) create mode 100644 app/javascript/src/utils/bootstrapLegacyCompat.js diff --git a/Dockerfile b/Dockerfile index 9e7aa81fee..4e32c759ff 100644 --- a/Dockerfile +++ b/Dockerfile @@ -46,6 +46,8 @@ RUN apt-get -qqy update \ # Always run Node in Production for the ECS hosted environments ENV NODE_ENV=production +ENV PUPPETEER_SKIP_DOWNLOAD=true +ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium RUN echo Using RAILS_ENV: ${RAILS_ENV}, NODE_ENV: ${NODE_ENV} diff --git a/Dockerfile.dev b/Dockerfile.dev index 6ae0ca3738..1d4279f55b 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -46,6 +46,8 @@ WORKDIR $INSTALL_PATH # Cleanup from build ENV RAILS_ENV development +ENV PUPPETEER_SKIP_DOWNLOAD=true +ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium RUN rm -rf node_modules vendor .cache # Make sure Git isn't going to try to use SSH (for klaro dependency) diff --git a/app/assets/stylesheets/blocks/_modal_search.scss b/app/assets/stylesheets/blocks/_modal_search.scss index 151f1be60a..ced0d1ff86 100644 --- a/app/assets/stylesheets/blocks/_modal_search.scss +++ b/app/assets/stylesheets/blocks/_modal_search.scss @@ -80,4 +80,13 @@ .modal-search-result .modal-search-result-unselector { border: 1px solid var(--color-red); color: var(--color-red); -} \ No newline at end of file +} + +// .modal.modal-search { +// z-index: 1055 !important; +// } + +// Fix Bootstrap 3 .fade not working with Bootstrap 5 .show class +.fade.show { opacity: 1 !important; } +.modal-backdrop { z-index: 1050; } +.modal { z-index: 1055; } \ No newline at end of file diff --git a/app/controllers/contributors_controller.rb b/app/controllers/contributors_controller.rb index 6bb3d39673..8e58c86ddd 100644 --- a/app/controllers/contributors_controller.rb +++ b/app/controllers/contributors_controller.rb @@ -67,8 +67,7 @@ def update args = process_orcid_for_update(hash: args) if @contributor.update(args) - redirect_to edit_plan_contributor_path(@plan, @contributor), - notice: success_message(@contributor, _('saved')) + redirect_to plan_contributors_path(@plan), notice: success_message(@contributor, _('saved')) else flash.now[:alert] = failure_message(@contributor, _('save')) render :edit diff --git a/app/javascript/application.js b/app/javascript/application.js index 67fc84ffea..81292355c2 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -25,6 +25,7 @@ import 'bootstrap-select'; // const imagePath = (name) => images(name, true) // Utilities import './src/utils/accordion'; +import './src/utils/bootstrapLegacyCompat'; import './src/utils/autoComplete'; import './src/utils/externalLink'; import './src/utils/modalSearch'; diff --git a/app/javascript/src/utils/accordion.js b/app/javascript/src/utils/accordion.js index 253893bbd9..08322579e8 100644 --- a/app/javascript/src/utils/accordion.js +++ b/app/javascript/src/utils/accordion.js @@ -46,7 +46,8 @@ $(() => { // Expands or collapses the panel according to the // direction passed (e.g. show --> expands, hide --> collapses) if (direction === 'show') { - if (!panelCollapse.find('.panel-body').attr('data-loaded') || !panelCollapse.hasClass('in')) { + if (!panelCollapse.find('.panel-body').attr('data-loaded') + || (!panelCollapse.hasClass('in') && !panelCollapse.hasClass('show'))) { panelCollapse.prev()[0].click(); } } else { diff --git a/app/javascript/src/utils/bootstrapLegacyCompat.js b/app/javascript/src/utils/bootstrapLegacyCompat.js new file mode 100644 index 0000000000..a835b56fe3 --- /dev/null +++ b/app/javascript/src/utils/bootstrapLegacyCompat.js @@ -0,0 +1,214 @@ +import { Collapse, Dropdown, Modal, Tab, Tooltip, Popover } from 'bootstrap'; + + +$(() => { + // Bridge Bootstrap 3 collapse data attributes to Bootstrap 5 equivalents. + $('[data-toggle="collapse"]').each((_i, trigger) => { + const triggerEl = $(trigger); + const targetSelector = triggerEl.attr('data-target') || triggerEl.attr('href'); + const parentSelector = triggerEl.attr('data-parent'); + + if (!triggerEl.attr('data-bs-toggle')) { + triggerEl.attr('data-bs-toggle', 'collapse'); + } + + if (targetSelector && !triggerEl.attr('data-bs-target')) { + triggerEl.attr('data-bs-target', targetSelector); + } + + if (targetSelector && parentSelector) { + const normalizedParent = + parentSelector.startsWith('#') || parentSelector.startsWith('.') + ? parentSelector + : `#${parentSelector}`; + const targetEl = $(targetSelector); + if (targetEl.length > 0 && !targetEl.attr('data-bs-parent')) { + targetEl.attr('data-bs-parent', normalizedParent); + } + } + }); + + // Bridge Bootstrap 3 dropdown data attributes to Bootstrap 5 equivalents. + $('[data-toggle="dropdown"]').each((_i, trigger) => { + const triggerEl = $(trigger); + if (!triggerEl.attr('data-bs-toggle')) { + triggerEl.attr('data-bs-toggle', 'dropdown'); + } + }); + + + // Keep legacy jQuery API calls working (e.g. panelCollapse.collapse('hide')). + if ($.fn && !$.fn.collapse) { + $.fn.collapse = function collapse(action = 'toggle') { + return this.each((_i, el) => { + const instance = Collapse.getOrCreateInstance(el, { toggle: false }); + if (action === 'show') { + instance.show(); + } else if (action === 'hide') { + instance.hide(); + } else { + instance.toggle(); + } + }); + }; + } + + // Keep legacy jQuery tab API calls working (e.g. $(...).tab('show')). + if ($.fn && !$.fn.tab) { + $.fn.tab = function tab(action = 'show') { + return this.each((_i, el) => { + const instance = Tab.getOrCreateInstance(el); + if (action === 'show') { + instance.show(); + } + }); + }; + } + + if ($.fn && !$.fn.modal) { + $.fn.modal = function modal(action = 'show', ...args) { + return this.each((_i, el) => { + const options = typeof action === 'object' ? action : {}; + const instance = Modal.getOrCreateInstance(el, options); + + if (typeof action === 'string') { + if (action === 'show') { + instance.show(); + } else if (action === 'hide') { + instance.hide(); + } else if (action === 'toggle') { + instance.toggle(); + } else if (action === 'dispose') { + instance.dispose(); + } else if (action === 'handleUpdate') { + instance.handleUpdate(); + } + } + }); + }; + } + + if ($.fn && !$.fn.dropdown) { + $.fn.dropdown = function dropdown(action = 'toggle') { + return this.each((_i, el) => { + const instance = Dropdown.getOrCreateInstance(el); + if (action === 'toggle') instance.toggle(); + else if (action === 'show') instance.show(); + else if (action === 'hide') instance.hide(); + else if (action === 'dispose') instance.dispose(); + }); + }; + } + + + // Initialize all tooltips (BS5 requires explicit init, BS3 did it automatically) + $('[data-toggle="tooltip"], [data-bs-toggle="tooltip"]').each((_i, el) => { + const triggerEl = $(el); + if (!triggerEl.attr('data-bs-toggle')) { + triggerEl.attr('data-bs-toggle', 'tooltip'); + } + new Tooltip(el); + }); + + // Initialize all popovers + $('[data-toggle="popover"], [data-bs-toggle="popover"]').each((_i, el) => { + const triggerEl = $(el); + if (!triggerEl.attr('data-bs-toggle')) { + triggerEl.attr('data-bs-toggle', 'popover'); + } + new Popover(el); + }); + + // Bridge Bootstrap 3 tab/pill attributes to Bootstrap 5 equivalents. + $('[data-toggle="tab"], [data-toggle="pill"]').each((_i, trigger) => { + const triggerEl = $(trigger); + const mode = triggerEl.attr('data-toggle'); + const targetSelector = triggerEl.attr('data-target') || triggerEl.attr('href'); + + if (mode && !triggerEl.attr('data-bs-toggle')) { + triggerEl.attr('data-bs-toggle', mode); + } + + if (targetSelector && !triggerEl.attr('data-bs-target')) { + triggerEl.attr('data-bs-target', targetSelector); + } + }); + + $('[data-toggle="modal"]').each((_i, trigger) => { + const triggerEl = $(trigger); + const targetSelector = triggerEl.attr('data-target') || triggerEl.attr('href'); + + if (!triggerEl.attr('data-bs-toggle')) { + triggerEl.attr('data-bs-toggle', 'modal'); + } + + if (targetSelector && !triggerEl.attr('data-bs-target')) { + triggerEl.attr('data-bs-target', targetSelector); + } + }); + + $('body').on('click', '[data-toggle="modal"]', (e) => { + const triggerEl = $(e.currentTarget); + + // Bootstrap 5 already handles this natively via data-bs-toggle — skip to avoid double-firing + if (triggerEl.attr('data-bs-toggle') === 'modal') return; + + const targetSelector = + triggerEl.attr('data-target') || + triggerEl.attr('href'); + + if (!targetSelector) return; + const target = document.querySelector(targetSelector); + if (!target) return; + + e.preventDefault(); + Modal.getOrCreateInstance(target).show(); + }); + + + $('body').on('click', '[data-toggle="collapse"]', (e) => { + const triggerEl = $(e.currentTarget); + const targetSelector = triggerEl.attr('data-bs-target') + || triggerEl.attr('data-target') + || triggerEl.attr('href'); + + if (!targetSelector || !targetSelector.startsWith('#')) { + return; + } + + const target = document.querySelector(targetSelector); + if (!target) { + return; + } + + e.preventDefault(); + const parentSelector = triggerEl.attr('data-parent'); + const options = { toggle: false }; + if (parentSelector) { + options.parent = + parentSelector.startsWith('#') || parentSelector.startsWith('.') + ? parentSelector + : `#${parentSelector}`; + } + + Collapse.getOrCreateInstance(target, options).toggle(); + }); + + $('body').on('click', '[data-toggle="tab"], [data-toggle="pill"]', (e) => { + const trigger = e.currentTarget; + e.preventDefault(); + Tab.getOrCreateInstance(trigger).show(); + }); + + // Keep legacy BS3 li.active selectors in sync with BS5 nav-link activation. + $('body').on('shown.bs.tab', '[data-toggle="tab"], [data-toggle="pill"], [data-bs-toggle="tab"], [data-bs-toggle="pill"]', (e) => { + const currentLink = $(e.target); + const navList = currentLink.closest('ul.nav, ul.nav-tabs, ul.nav-pills'); + if (navList.length === 0) { + return; + } + + navList.find('li').removeClass('active'); + currentLink.closest('li').addClass('active'); + }); +}); diff --git a/app/javascript/src/utils/popoverHelper.js b/app/javascript/src/utils/popoverHelper.js index 68770bb450..78ae20b63c 100644 --- a/app/javascript/src/utils/popoverHelper.js +++ b/app/javascript/src/utils/popoverHelper.js @@ -1,4 +1,4 @@ -import 'bootstrap/js/popover'; +import 'bootstrap/js/dist/popover'; $(() => { $('[data-toggle="popover"]').popover({ diff --git a/app/javascript/src/utils/tooltipHelper.js b/app/javascript/src/utils/tooltipHelper.js index f67edd2192..361a26b723 100644 --- a/app/javascript/src/utils/tooltipHelper.js +++ b/app/javascript/src/utils/tooltipHelper.js @@ -1,4 +1,4 @@ -import 'bootstrap/js/tooltip'; +import 'bootstrap/js/dist/tooltip'; $(() => { // When using a tooltip on a tinymce textarea, add the HTML attributes for the tooltips to diff --git a/app/views/phases/_guidances_notes.html.erb b/app/views/phases/_guidances_notes.html.erb index 8c6937e929..ffbaaf2e75 100644 --- a/app/views/phases/_guidances_notes.html.erb +++ b/app/views/phases/_guidances_notes.html.erb @@ -1,19 +1,19 @@ <%# locals: { plan, template, question, answer, guidance_presenter } %> <% guidances_active = guidance_presenter.any?(question: question) %> <% active_nav = nil %> -
+