Răsfoiți Sursa

Remove Bootstrap version 3 (#27420)

* Remove bootstrap-3 entrypoint

* Rename bootstrap-5 entrypoint to bootstrap

* Restore entrypoints

* Remove `bootstrap-5` and `bootstrap-3` entrypoints and a single `bootstrap.ts` file is now the default entrypoint

* Update `bootstrap-5` component imports to `bootstrap`

* Update `bootstrap-5` CSS imports to `bootstrap`

* Remove the `isBootstrap5` utility

* Remove `bootstrapVersion`

* Remove `ol-bootstrapVersion`

* Remove getCssThemeModifier

* Update path and rename

* Source format

* Remove Bootstrap v3 and Bootstrap v5 alias npm packages

* Remove bootstrap argument

* Remove unused files

* Update with the latest split tests

* Remove remaining bootstrap5PageStatus

* Update path

GitOrigin-RevId: 7acda2f80114d2de8699e1f06729a2a29218e284
Rebeka Dekany 1 an în urmă
părinte
comite
c9a1ecd060
26 a modificat fișierele cu 68 adăugiri și 216 ștergeri
  1. 5 27
      package-lock.json
  2. 0 26
      services/web/app/src/infrastructure/ExpressLocals.js
  3. 2 2
      services/web/app/views/_mixins/bootstrap_js.pug
  4. 1 10
      services/web/app/views/layout-base.pug
  5. 4 13
      services/web/app/views/layout-marketing.pug
  6. 4 13
      services/web/app/views/layout-website-redesign.pug
  7. 1 4
      services/web/app/views/layout/fat-footer-base.pug
  8. 1 1
      services/web/app/views/layout/layout-no-js.pug
  9. 1 3
      services/web/app/views/subscriptions/team/invite_logged_out.pug
  10. 1 1
      services/web/cypress/support/component.ts
  11. 0 4
      services/web/frontend/js/bootstrap-5.ts
  12. 2 3
      services/web/frontend/js/bootstrap.ts
  13. 0 21
      services/web/frontend/js/features/bookmarkable-tab/index.js
  14. 1 2
      services/web/frontend/js/features/bookmarkable-tab/index.ts
  15. 4 28
      services/web/frontend/js/features/form-helpers/hydrate-form.js
  16. 2 6
      services/web/frontend/js/features/form-helpers/input-validator.js
  17. 0 2
      services/web/frontend/js/features/tooltip/index-bs3.ts
  18. 1 1
      services/web/frontend/js/features/tooltip/index.ts
  19. 0 5
      services/web/frontend/js/features/utils/bootstrap-5.ts
  20. 2 4
      services/web/frontend/js/features/utils/disableElement.js
  21. 0 1
      services/web/frontend/js/utils/meta.ts
  22. 33 33
      services/web/frontend/stylesheets/base/bootstrap.scss
  23. 1 1
      services/web/frontend/stylesheets/components/nav.scss
  24. 1 3
      services/web/package.json
  25. 0 0
      services/web/test/frontend/helpers/bootstrap.ts
  26. 1 2
      services/web/webpack.config.js

+ 5 - 27
package-lock.json

@@ -12026,16 +12026,6 @@
         "@popperjs/core": "^2.9.2"
       }
     },
-    "node_modules/@types/bootstrap-5": {
-      "name": "@types/bootstrap",
-      "version": "5.2.10",
-      "resolved": "https://registry.npmjs.org/@types/bootstrap/-/bootstrap-5.2.10.tgz",
-      "integrity": "sha512-F2X+cd6551tep0MvVZ6nM8v7XgGN/twpdNDjqS1TUM7YFNEtQYWk+dKAnH+T1gr6QgCoGMPl487xw/9hXooa2g==",
-      "dev": true,
-      "dependencies": {
-        "@popperjs/core": "^2.9.2"
-      }
-    },
     "node_modules/@types/bunyan": {
       "version": "1.8.8",
       "resolved": "https://registry.npmjs.org/@types/bunyan/-/bunyan-1.8.8.tgz",
@@ -16591,19 +16581,9 @@
       "dev": true
     },
     "node_modules/bootstrap": {
-      "version": "3.4.1",
-      "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-3.4.1.tgz",
-      "integrity": "sha512-yN5oZVmRCwe5aKwzRj6736nSmKDX7pLYwsXiCj/EYmo16hODaBiT4En5btW/jhBF/seV+XMx3aYwukYC3A49DA==",
-      "dev": true,
-      "engines": {
-        "node": ">=6"
-      }
-    },
-    "node_modules/bootstrap-5": {
-      "name": "bootstrap",
-      "version": "5.3.6",
-      "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.6.tgz",
-      "integrity": "sha512-jX0GAcRzvdwISuvArXn3m7KZscWWFAf1MKBcnzaN02qWMb3jpMoUX4/qgeiGzqyIb4ojulRzs89UCUmGcFSzTA==",
+      "version": "5.3.7",
+      "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.7.tgz",
+      "integrity": "sha512-7KgiD8UHjfcPBHEpDNg+zGz8L3LqR3GVwqZiBRFX04a1BCArZOz1r2kjly2HQ0WokqTO0v1nF+QAt8dsW4lKlw==",
       "dev": true,
       "funding": [
         {
@@ -43614,7 +43594,7 @@
         "@overleaf/o-error": "*",
         "aws-sdk": "^2.1174.0",
         "body-parser": "^1.20.3",
-        "bootstrap": "^5.3.3",
+        "bootstrap": "5.3.3",
         "compression": "^1.7.1",
         "cookie-parser": "^1.4.6",
         "cross-env": "^7.0.3",
@@ -45171,7 +45151,6 @@
         "@testing-library/react": "^16.3.0",
         "@testing-library/user-event": "^14.4.3",
         "@types/bootstrap": "^5.2.10",
-        "@types/bootstrap-5": "npm:@types/bootstrap@^5.2.10",
         "@types/chai": "^4.3.0",
         "@types/dateformat": "^5.0.2",
         "@types/diff": "^5.0.9",
@@ -45206,8 +45185,7 @@
         "babel-plugin-macros": "^3.1.0",
         "babel-plugin-module-resolver": "^5.0.2",
         "backbone": "^1.6.0",
-        "bootstrap": "^3.4.1",
-        "bootstrap-5": "npm:bootstrap@^5.3.6",
+        "bootstrap": "^5.3.7",
         "c8": "^7.2.0",
         "chai": "^4.3.6",
         "chai-as-promised": "^7.1.1",

+ 0 - 26
services/web/app/src/infrastructure/ExpressLocals.js

@@ -186,32 +186,6 @@ module.exports = function (webRouter, privateApiRouter, publicApiRouter) {
 
     res.locals.moment = moment
 
-    res.locals.isIEEE = brandId => brandId === IEEE_BRAND_ID
-
-    res.locals.getCssThemeModifier = function (
-      userSettings,
-      brandVariation,
-      enableIeeeBranding
-    ) {
-      // Themes only exist in OL v2
-      if (Settings.overleaf != null) {
-        // The IEEE theme is no longer applied in the editor, which sets
-        // enableIeeeBranding to false, but is used in the IEEE portal. If
-        // this is an IEEE-branded page and IEEE branding is disabled in this
-        // page, always use the default theme (i.e. no light theme in the
-        // IEEE-branded editor)
-        if (res.locals.isIEEE(brandVariation?.brand_id)) {
-          return enableIeeeBranding ? 'ieee-' : ''
-        } else if (userSettings && userSettings.overallTheme != null) {
-          if (!['', 'light-'].includes(userSettings.overallTheme)) {
-            return ''
-          }
-          return userSettings.overallTheme
-        }
-      }
-      return ''
-    }
-
     res.locals.buildStylesheetPath = function (cssFileName) {
       return staticFilesBase + webpackManifest[cssFileName]
     }

+ 2 - 2
services/web/app/views/_mixins/bootstrap_js.pug

@@ -1,3 +1,3 @@
-mixin bootstrap-js(bootstrapVersion)
-	each file in entrypointScripts(bootstrapVersion === 5 ? 'bootstrap-5' : 'bootstrap-3')
+mixin bootstrap-js
+	each file in entrypointScripts('bootstrap')
 		script(type='text/javascript' nonce=scriptNonce src=file)

+ 1 - 10
services/web/app/views/layout-base.pug

@@ -6,8 +6,6 @@ html(
 	class=fixedSizeDocument ? 'fixed-size-document' : undefined
 )
 	- metadata = metadata || {}
-	- let bootstrap5PageStatus = 'enabled' // One of 'disabled' and 'enabled'
-	- let bootstrap5PageSplitTest = '' // Limits Bootstrap 5 usage on this page to users with an assignment of "enabled" for the specified split test. If left empty and bootstrap5PageStatus is "enabled", the page always uses Bootstrap 5.
 	- let isWebsiteRedesign = false
 	- let isApplicationPage = false
 	- let enableIeeeBranding = true
@@ -21,14 +19,8 @@ html(
 	head
 		include ./_metadata.pug
 
-		- const bootstrapVersion = bootstrap5PageStatus !== 'disabled' && (bootstrap5Override || bootstrap5PageSplitTest === '' || splitTestVariants[bootstrap5PageSplitTest] === 'enabled') ? 5 : 3
-
 		//- Stylesheet
-		link(
-			rel='stylesheet'
-			href=buildCssPath(getCssThemeModifier(userSettings, brandVariation, enableIeeeBranding), bootstrapVersion)
-			id='main-stylesheet'
-		)
+		link(rel='stylesheet' href=buildCssPath() id='main-stylesheet')
 		block css
 			each file in entrypointStyles(entrypoint)
 				link(rel='stylesheet' href=file)
@@ -103,7 +95,6 @@ html(
 			)
 			each restriction in userRestrictions || []
 				meta(name='ol-cannot-' + restriction data-type='boolean' content)
-			meta(name='ol-bootstrapVersion' data-type='json' content=bootstrapVersion)
 
 		block head-scripts
 

+ 4 - 13
services/web/app/views/layout-marketing.pug

@@ -8,29 +8,20 @@ block entrypointVar
 
 block body
 	if typeof suppressNavbar === 'undefined'
-		if bootstrapVersion === 5
-			include layout/navbar-marketing-bootstrap-5
-		else
-			include layout/navbar-marketing
+		include layout/navbar-marketing-bootstrap-5
 
 	block content
 
 	if typeof suppressFooter === 'undefined'
 		if showThinFooter
-			if bootstrapVersion === 5
-				include layout/thin-footer-bootstrap-5
-			else
-				include layout/thin-footer
+			include layout/thin-footer-bootstrap-5
 		else
 			include layout/fat-footer
 
 	if typeof suppressPugCookieBanner == 'undefined'
 		include _cookie_banner
 
-	if bootstrapVersion === 5
-		!= moduleIncludes('contactModal-marketing-bootstrap-5', locals)
-	else
-		!= moduleIncludes('contactModal-marketing', locals)
+	!= moduleIncludes('contactModal-marketing-bootstrap-5', locals)
 
 block prepend foot-scripts
-	+bootstrap-js(bootstrapVersion)
+	+bootstrap-js

+ 4 - 13
services/web/app/views/layout-website-redesign.pug

@@ -11,19 +11,13 @@ block append meta
 
 block body
 	if typeof suppressNavbar == 'undefined'
-		if bootstrapVersion === 5
-			include layout/navbar-marketing-bootstrap-5
-		else
-			include layout/navbar-website-redesign
+		include layout/navbar-marketing-bootstrap-5
 
 	block content
 
 	if typeof suppressFooter == 'undefined'
 		if showThinFooter
-			if bootstrapVersion === 5
-				include layout/thin-footer-bootstrap-5
-			else
-				include layout/thin-footer
+			include layout/thin-footer-bootstrap-5
 		else
 			include layout/fat-footer-website-redesign
 
@@ -31,10 +25,7 @@ block body
 		include _cookie_banner
 
 	block contactModal
-		if bootstrapVersion === 5
-			!= moduleIncludes('contactModal-marketing-bootstrap-5', locals)
-		else
-			!= moduleIncludes('contactModal-marketing', locals)
+		!= moduleIncludes('contactModal-marketing-bootstrap-5', locals)
 
 block prepend foot-scripts
-	+bootstrap-js(bootstrapVersion)
+	+bootstrap-js

+ 1 - 4
services/web/app/views/layout/fat-footer-base.pug

@@ -5,10 +5,7 @@
 			a(href='/legal') #{translate('privacy_and_terms')}
 			a(href='https://www.digital-science.com/security-certifications/') #{translate('compliance')}
 		ul.fat-footer-base-item.list-unstyled.fat-footer-base-language
-			if bootstrapVersion === 5
-				include language-picker-bootstrap-5
-			else
-				include language-picker
+			include language-picker-bootstrap-5
 	.fat-footer-base-section.fat-footer-base-social
 		.fat-footer-base-item
 			a.fat-footer-social.x-logo(href='https://x.com/overleaf')

+ 1 - 1
services/web/app/views/layout/layout-no-js.pug

@@ -15,6 +15,6 @@ html(lang='en')
 		link(rel='icon' href='/favicon.ico')
 
 		if buildCssPath
-			link(rel='stylesheet' href=buildCssPath('', 5))
+			link(rel='stylesheet' href=buildCssPath())
 
 block body

+ 1 - 3
services/web/app/views/subscriptions/team/invite_logged_out.pug

@@ -4,12 +4,10 @@ block append meta
 	meta(name='ol-user' data-type='json' content=user)
 
 block content
-	- var colClass = bootstrapVersion === 5 ? 'col-lg-8 m-auto' : 'col-md-8 col-md-offset-2'
-
 	main#main-content.content.content-alt.team-invite
 		.container
 			.row
-				div(class=colClass)
+				.col-lg-8.m-auto
 					.card.text-center
 						.card-body
 							.page-header

+ 1 - 1
services/web/cypress/support/component.ts

@@ -5,7 +5,7 @@ import './shared/commands'
 import './shared/exceptions'
 import './ct/commands'
 import './ct/codemirror'
-import '../../test/frontend/helpers/bootstrap-5'
+import '../../test/frontend/helpers/bootstrap'
 
 beforeEach(function () {
   cy.wrap(localesPromise).then(resetMeta)

+ 0 - 4
services/web/frontend/js/bootstrap-5.ts

@@ -1,4 +0,0 @@
-import './features/bookmarkable-tab/index-bs5'
-import './features/tooltip/index-bs5'
-import './features/navbar/index'
-import 'bootstrap-5'

+ 2 - 3
services/web/frontend/js/bootstrap-3.ts → services/web/frontend/js/bootstrap.ts

@@ -1,5 +1,4 @@
-import 'bootstrap'
-import './features/contact-form'
 import './features/bookmarkable-tab/index'
-import './features/tooltip/index-bs3'
+import './features/tooltip/index'
 import './features/navbar/index'
+import 'bootstrap'

+ 0 - 21
services/web/frontend/js/features/bookmarkable-tab/index.js

@@ -1,21 +0,0 @@
-function bookmarkableTab(tabEl) {
-  tabEl.addEventListener('click', () => {
-    window.location.hash = tabEl.getAttribute('href')
-  })
-}
-
-function handleHashChange() {
-  const hash = window.location.hash
-  if (!hash) return
-
-  // Find the bookmarkable tab that links to the hash
-  const $tabEl = $(`[data-ol-bookmarkable-tab][href="${hash}"]`)
-  if (!$tabEl) return
-
-  // Select the tab via Bootstrap
-  $tabEl.tab('show')
-}
-
-document.querySelectorAll('[data-ol-bookmarkable-tab]').forEach(bookmarkableTab)
-window.addEventListener('hashchange', handleHashChange)
-handleHashChange()

+ 1 - 2
services/web/frontend/js/features/bookmarkable-tab/index-bs5.ts → services/web/frontend/js/features/bookmarkable-tab/index.ts

@@ -1,4 +1,4 @@
-import { Tab } from 'bootstrap-5'
+import { Tab } from 'bootstrap'
 
 function bookmarkableTab(tabEl: HTMLElement) {
   tabEl.addEventListener('click', () => {
@@ -17,7 +17,6 @@ function handleHashChange() {
 
   if (!tabEl) return
 
-  // Select the tab via Bootstrap 5
   const tab = new Tab(tabEl)
   tab.show()
 }

+ 4 - 28
services/web/frontend/js/features/form-helpers/hydrate-form.js

@@ -3,7 +3,6 @@ import { FetchError, postJSON } from '../../infrastructure/fetch-json'
 import { canSkipCaptcha, validateCaptchaV2 } from './captcha'
 import inputValidator from './input-validator'
 import { disableElement, enableElement } from '../utils/disableElement'
-import { isBootstrap5 } from '@/features/utils/bootstrap-5'
 import { materialIcon as createMaterialIcon } from '@/features/utils/material-icon'
 
 // Form helper(s) to handle:
@@ -136,7 +135,7 @@ function hideFormElements(formEl) {
 }
 
 /**
- * Creates a notification element from a message object, with BS5 classes.
+ * Creates a notification element from a message object.
  *
  * @param {Object} message
  * @param {'error' | 'success' | 'warning' | 'info'} message.type
@@ -145,7 +144,7 @@ function hideFormElements(formEl) {
  * @param {string[]} message.hints
  * @returns {HTMLDivElement}
  */
-function createNotificationFromMessageBS5(message) {
+function createNotificationFromMessage(message) {
   const messageEl = document.createElement('div')
   messageEl.className = classNames('mb-3 notification', {
     'notification-type-error': message.type === 'error',
@@ -216,32 +215,9 @@ function showMessages(formEl, messageBag) {
       customErrorElements.forEach(el => {
         el.hidden = false
       })
-    } else if (isBootstrap5()) {
-      const notification = createNotificationFromMessageBS5(message)
-      messagesEl.append(notification)
     } else {
-      // No custom error element for key on page, append a new error message
-      const messageEl = document.createElement('div')
-      messageEl.className = classNames('alert mb-2', {
-        'alert-danger': message.type === 'error',
-        'alert-success': message.type !== 'error',
-      })
-      messageEl.textContent = message.text || `Error: ${message.key}`
-      messageEl.setAttribute('aria-live', 'assertive')
-      messageEl.setAttribute(
-        'role',
-        message.type === 'error' ? 'alert' : 'status'
-      )
-      if (message.hints && message.hints.length) {
-        const listEl = document.createElement('ul')
-        message.hints.forEach(hint => {
-          const listItemEl = document.createElement('li')
-          listItemEl.textContent = hint
-          listEl.append(listItemEl)
-        })
-        messageEl.append(listEl)
-      }
-      messagesEl.append(messageEl)
+      const notification = createNotificationFromMessage(message)
+      messagesEl.append(notification)
     }
     if (message.key) {
       // Hide the form elements on specific message types

+ 2 - 6
services/web/frontend/js/features/form-helpers/input-validator.js

@@ -1,4 +1,3 @@
-import { isBootstrap5 } from '@/features/utils/bootstrap-5'
 import { materialIcon } from '@/features/utils/material-icon'
 
 export default function inputValidator(inputEl) {
@@ -13,11 +12,8 @@ export default function inputValidator(inputEl) {
 
   const messageTextNode = document.createTextNode('')
 
-  // In Bootstrap 5, add an icon
-  if (isBootstrap5()) {
-    const iconEl = materialIcon('error')
-    messageInnerEl.append(iconEl)
-  }
+  const iconEl = materialIcon('error')
+  messageInnerEl.append(iconEl)
   messageInnerEl.append(messageTextNode)
 
   inputEl.insertAdjacentElement('afterend', messageEl)

+ 0 - 2
services/web/frontend/js/features/tooltip/index-bs3.ts

@@ -1,2 +0,0 @@
-$('[data-ol-lang-selector-tooltip]').tooltip({ trigger: 'hover' })
-$('[data-toggle="tooltip"]').tooltip()

+ 1 - 1
services/web/frontend/js/features/tooltip/index-bs5.ts → services/web/frontend/js/features/tooltip/index.ts

@@ -1,4 +1,4 @@
-import { Tooltip } from 'bootstrap-5'
+import { Tooltip } from 'bootstrap'
 
 function getElementWidth(el: Element) {
   const elComputedStyle = window.getComputedStyle(el)

+ 0 - 5
services/web/frontend/js/features/utils/bootstrap-5.ts

@@ -1,5 +0,0 @@
-import getMeta from '@/utils/meta'
-
-// The reason this is a function is to ensure that the meta tag is read before
-// any isBootstrap5 check is performed
-export const isBootstrap5 = () => getMeta('ol-bootstrapVersion') === 5

+ 2 - 4
services/web/frontend/js/features/utils/disableElement.js

@@ -1,7 +1,5 @@
-import { isBootstrap5 } from './bootstrap-5'
-
 export function disableElement(el) {
-  if (isBootstrap5() && el.tagName.toLowerCase() === 'a') {
+  if (el.tagName.toLowerCase() === 'a') {
     el.classList.add('disabled')
   } else {
     el.disabled = true
@@ -10,7 +8,7 @@ export function disableElement(el) {
 }
 
 export function enableElement(el) {
-  if (isBootstrap5() && el.tagName.toLowerCase() === 'a') {
+  if (el.tagName.toLowerCase() === 'a') {
     el.classList.remove('disabled')
   } else {
     el.disabled = false

+ 0 - 1
services/web/frontend/js/utils/meta.ts

@@ -75,7 +75,6 @@ export interface Meta {
   'ol-allowedImageNames': AllowedImageName[]
   'ol-anonymous': boolean
   'ol-baseAssetPath': string
-  'ol-bootstrapVersion': 3 | 5
   'ol-brandVariation': Record<string, any>
 
   // dynamic keys based on permissions

+ 33 - 33
services/web/frontend/stylesheets/base/bootstrap.scss

@@ -1,9 +1,9 @@
 // Only include the parts of Bootstrap we need to minimize bundle size
 
 // Include functions first (enabling manipulation of colors, SVGs, calc, etc. in the variable overrides)
-@import 'bootstrap-5/scss/functions';
+@import 'bootstrap/scss/functions';
 
-// Overrides for Bootstrap 5 Sass variables. These have to be included before
+// Overrides for Bootstrap Sass variables. These have to be included before
 // Bootstrap itself because Bootstrap uses them to create the CSS variables it
 // uses, and in calculations to determine, for example, what color text to use
 // on a button based on contrast.
@@ -11,44 +11,44 @@
 @import '../abstracts/all';
 
 // Include remainder of required Bootstrap stylesheets (including any separate color mode stylesheets)
-@import 'bootstrap-5/scss/variables';
-@import 'bootstrap-5/scss/variables-dark';
+@import 'bootstrap/scss/variables';
+@import 'bootstrap/scss/variables-dark';
 
 // Include remainder of required parts
-@import 'bootstrap-5/scss/maps';
-@import 'bootstrap-5/scss/mixins';
-@import 'bootstrap-5/scss/utilities';
+@import 'bootstrap/scss/maps';
+@import 'bootstrap/scss/mixins';
+@import 'bootstrap/scss/utilities';
 
 // Layout & components
-@import 'bootstrap-5/scss/root';
-@import 'bootstrap-5/scss/reboot';
-@import 'bootstrap-5/scss/transitions';
-@import 'bootstrap-5/scss/type';
-@import 'bootstrap-5/scss/images';
-@import 'bootstrap-5/scss/containers';
-@import 'bootstrap-5/scss/grid';
-@import 'bootstrap-5/scss/tables';
-@import 'bootstrap-5/scss/forms';
-@import 'bootstrap-5/scss/buttons';
-@import 'bootstrap-5/scss/dropdown';
-@import 'bootstrap-5/scss/button-group';
-@import 'bootstrap-5/scss/badge';
-@import 'bootstrap-5/scss/list-group';
-@import 'bootstrap-5/scss/modal';
-@import 'bootstrap-5/scss/tooltip';
-@import 'bootstrap-5/scss/popover';
-@import 'bootstrap-5/scss/spinners';
-@import 'bootstrap-5/scss/card';
-@import 'bootstrap-5/scss/close';
-@import 'bootstrap-5/scss/nav';
-@import 'bootstrap-5/scss/navbar';
-@import 'bootstrap-5/scss/offcanvas';
+@import 'bootstrap/scss/root';
+@import 'bootstrap/scss/reboot';
+@import 'bootstrap/scss/transitions';
+@import 'bootstrap/scss/type';
+@import 'bootstrap/scss/images';
+@import 'bootstrap/scss/containers';
+@import 'bootstrap/scss/grid';
+@import 'bootstrap/scss/tables';
+@import 'bootstrap/scss/forms';
+@import 'bootstrap/scss/buttons';
+@import 'bootstrap/scss/dropdown';
+@import 'bootstrap/scss/button-group';
+@import 'bootstrap/scss/badge';
+@import 'bootstrap/scss/list-group';
+@import 'bootstrap/scss/modal';
+@import 'bootstrap/scss/tooltip';
+@import 'bootstrap/scss/popover';
+@import 'bootstrap/scss/spinners';
+@import 'bootstrap/scss/card';
+@import 'bootstrap/scss/close';
+@import 'bootstrap/scss/nav';
+@import 'bootstrap/scss/navbar';
+@import 'bootstrap/scss/offcanvas';
 
 // Helpers
-@import 'bootstrap-5/scss/helpers';
+@import 'bootstrap/scss/helpers';
 
 // Utilities
-@import 'bootstrap-5/scss/utilities/api';
+@import 'bootstrap/scss/utilities/api';
 
 // Mixins
-@import 'bootstrap-5/scss/mixins/breakpoints';
+@import 'bootstrap/scss/mixins/breakpoints';

+ 1 - 1
services/web/frontend/stylesheets/components/nav.scss

@@ -1,4 +1,4 @@
-@import 'bootstrap-5/scss/functions';
+@import 'bootstrap/scss/functions';
 
 :root {
   // Basics

+ 1 - 3
services/web/package.json

@@ -232,7 +232,6 @@
     "@testing-library/react": "^16.3.0",
     "@testing-library/user-event": "^14.4.3",
     "@types/bootstrap": "^5.2.10",
-    "@types/bootstrap-5": "npm:@types/bootstrap@^5.2.10",
     "@types/chai": "^4.3.0",
     "@types/dateformat": "^5.0.2",
     "@types/diff": "^5.0.9",
@@ -267,8 +266,7 @@
     "babel-plugin-macros": "^3.1.0",
     "babel-plugin-module-resolver": "^5.0.2",
     "backbone": "^1.6.0",
-    "bootstrap": "^3.4.1",
-    "bootstrap-5": "npm:bootstrap@^5.3.6",
+    "bootstrap": "^5.3.7",
     "c8": "^7.2.0",
     "chai": "^4.3.6",
     "chai-as-promised": "^7.1.1",

+ 0 - 0
services/web/test/frontend/helpers/bootstrap-5.ts → services/web/test/frontend/helpers/bootstrap.ts


+ 1 - 2
services/web/webpack.config.js

@@ -16,8 +16,7 @@ invalidateBabelCacheIfNeeded()
 
 // Generate a hash of entry points, including modules
 const entryPoints = {
-  'bootstrap-3': './frontend/js/bootstrap-3.ts',
-  'bootstrap-5': './frontend/js/bootstrap-5.ts',
+  bootstrap: './frontend/js/bootstrap.ts',
   devToolbar: './frontend/js/dev-toolbar.ts',
   'ide-detached': './frontend/js/ide-detached.ts',
   marketing: './frontend/js/marketing.ts',