Commit 27881b60 authored by Benjamin Franzke's avatar Benjamin Franzke Committed by Benni Mack
Browse files

[TASK] Update bootstrap javascript to 5.0.0-beta1

Bootstrap v5 – introduced in #92616 – was added with CCS from beta1 but
JavaScript from alpha2. bootstrap.bundle.js was manually wrapped
into a AMD closure, and because bootstrap 5.0.0-beta1 contains alot of
changes regarding data tags, it couldn't be updated in the initial
patch.

Bootstrap is now bundled using rollup using the ES6 sources in order
to allow for automatic updates through `grunt build`.

popperjs – previously bundled into bootstrap distributed files –
is now added as dependency. The bootstap ES6 sources, that we now use
through rollup, do not bundle this external dependency (for good reasons).

Dependency added with:

   yarn add @popperjs/core

Further adaptions contained in this change to ensure beta1 compatibility:

a) Carousel "item" to "carousel-item" class migration
b) $.fn.modal(options) does no longer imply $.fn.modal('show')
c) Fix panels, both JS and CSS (card-group can't be used here)
d) All bootstrap data- tags are migrated to data-bs-.
   (see https://github.com/twbs/bootstrap/pull/31827)
   Migrated with

   # renderes a sed substition with the help of a nested sed from all the
   # data-bs attributes that where changed in the twbs/bootstrap commit
   git grep -l data- | xargs sed -i $( \
        curl -s \
        https://patch-diff.githubusercontent.com/raw/twbs/bootstrap/pull/31827.patch | \
        sed 's/data-bs-[a-z-]*/\n&\n/g' | grep "data-bs-[a-z-]" | \
        sort | uniq | \
        sed 's/data-bs-\(.*\)\([^a-z-]\|$\)/ -e s\/data-\1\\\([^a-z-]\\\)\/data-bs-\1\\1\/g -e s\/data('"'"'\1'"'"')\/data('"'"'bs-\1'"'"')\/g/g' \
   )

   # Revert false positives from the above auto-replacement
   git checkout -- typo3/sysext/core/Documentation/Changelog/ \
        typo3/sysext/backend/Classes/Form/Container/FlexFormContainerContainer.php \
        Build/Sources/TypeScript/backend/Resources/Public/TypeScript/LiveSearch.ts \
        Build/Sources/TypeScript/backend/Resources/Public/TypeScript/FormEngineFlexForm.ts \
        Build/Sources/TypeScript/install/Resources/Public/TypeScript/Module/Settings/ExtensionConfiguration.ts \
        Build/Sources/Sass/typo3/_element_panel.scss

   (cd Build && grunt build)

Resolves: #93126
Resolves: #93123
Resolves: #93132
Related: #92616
Releases: master
Change-Id: Ie194d0f87d2c60df7b9e8a6de4893cfaaea55356
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/67215

Tested-by: default avatarTYPO3com <noreply@typo3.com>
Tested-by: default avatarMartin Kutschker <mkutschker-typo3@yahoo.com>
Tested-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
Tested-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
Reviewed-by: default avatarMartin Kutschker <mkutschker-typo3@yahoo.com>
Reviewed-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
parent 89f2e9f8
......@@ -514,6 +514,37 @@ module.exports = function (grunt) {
]
}
},
'bootstrap': {
options: {
preserveModules: false,
plugins: () => [
{
name: 'terser',
renderChunk: code => require('terser').minify(code, grunt.config.get('terser.options'))
},
{
name: 'externals',
resolveId: (source) => {
if (source === 'jquery') {
return {id: 'jquery', external: true}
}
if (source === 'bootstrap') {
return {id: 'node_modules/bootstrap/dist/js/bootstrap.esm.js'}
}
if (source === '@popperjs/core') {
return {id: 'node_modules/@popperjs/core/dist/esm/index.js'}
}
return null
}
}
]
},
files: {
'<%= paths.core %>Public/JavaScript/Contrib/bootstrap/bootstrap.js': [
'Sources/JavaScript/core/Resources/Public/JavaScript/Contrib/bootstrap.js'
]
}
}
},
npmcopy: {
options: {
......
// bootstrap needs a jQuery global to initialize $.fn
import './jquery-global.js';
export * from 'bootstrap';
......@@ -305,7 +305,9 @@ label {
@extend .card !optional;
&-group {
@extend .card-group !optional;
// .card-group is not a replacement for panel-group, therefore we build our own
display: flex;
flex-flow: column;
}
&-heading {
......@@ -315,10 +317,9 @@ label {
&-title {
@extend .card-title !optional;
}
&-title {
@extend .card-title !optional;
margin-top: 0;
margin-bottom: 0;
}
&-body {
......
......@@ -11,7 +11,7 @@
// <div class="panel panel-default">
// <div class="panel-heading">
// <div class="panel-heading-right">
// <a href="#panelContentId" class="panel-heading-collapse" role="button" data-toggle="collapse" aria-expanded="true">
// <a href="#panelContentId" class="panel-heading-collapse" role="button" data-bs-toggle="collapse" aria-expanded="true">
// <span class="t3js-icon icon icon-size-small icon-state-default icon-actions-view-list-collapse" data-identifier="actions-view-list-collapse">
// ... IconAPI ...
// </span>
......
......@@ -204,7 +204,7 @@
}
// Collapsibles clickable on full length
a[data-toggle="collapse"] {
a[data-bs-toggle="collapse"] {
display: block;
}
......
......@@ -86,7 +86,7 @@
border-left: 2px solid $color-orange;
position: relative;
&[data-toggle=collapse] {
&[data-bs-toggle=collapse] {
background-color: #333;
}
}
......
......@@ -7,7 +7,7 @@ $option-margin-bottom: $padding-small-horizontal;
}
}
[data-slide="localize-summary"] {
[data-bs-slide="localize-summary"] {
.input-group {
margin-bottom: $option-margin-bottom;
......
......@@ -187,10 +187,10 @@ class AjaxDataHandler {
// Update tooltip title
$anchorElement.tooltip('hide').one('hidden.bs.tooltip', (): void => {
const nextTitle = $anchorElement.data('toggleTitle');
// Bootstrap Tooltip internally uses only .attr('data-original-title')
// Bootstrap Tooltip internally uses only .attr('data-bs-original-title')
$anchorElement
.data('toggleTitle', $anchorElement.attr('data-original-title'))
.attr('data-original-title', nextTitle);
.data('toggleTitle', $anchorElement.attr('data-bs-original-title'))
.attr('data-bs-original-title', nextTitle);
});
const $iconElement = $anchorElement.find(Identifiers.icon);
......
......@@ -69,10 +69,10 @@ class ContextHelp {
const $element = $(this.selector);
$element
.attr('data-loaded', 'false')
.attr('data-html', 'true')
.attr('data-original-title', title)
.attr('data-placement', this.placement)
.attr('data-trigger', this.trigger);
.attr('data-bs-html', 'true')
.attr('data-bs-original-title', title)
.attr('data-bs-placement', this.placement)
.attr('data-bs-trigger', this.trigger);
Popover.popover($element);
$(document).on('show.bs.popover', this.selector, (e: Event): void => {
......
......@@ -78,7 +78,7 @@ class DebugConsole {
$('<li />', {role: 'presentation', class: 'nav-item', 'data-identifier': tabIdentifier}).append(
$('<a />', {
'aria-controls': tabIdentifier,
'data-toggle': 'tab',
'data-bs-toggle': 'tab',
class: 'nav-link',
href: '#' + tabIdentifier,
role: 'tab',
......
......@@ -29,7 +29,7 @@ import Severity = require('../../Severity');
import Utility = require('../../Utility');
enum Selectors {
toggleSelector = '[data-toggle="formengine-inline"]',
toggleSelector = '[data-bs-toggle="formengine-inline"]',
controlSectionSelector = '.t3js-formengine-irre-control',
createNewRecordButtonSelector = '.t3js-create-new-button',
createNewRecordBySelectorSelector = '.t3js-create-new-selector',
......
......@@ -322,7 +322,7 @@ class ImageManipulation {
* Assign EventListener to aspectRatioTrigger
*/
this.aspectRatioTrigger.off('click').on('click', (e: JQueryEventObject): void => {
const ratioId: string = $(e.currentTarget).attr('data-option');
const ratioId: string = $(e.currentTarget).attr('data-bs-option');
const temp: CropVariant = $.extend(true, {}, this.currentCropVariant);
const ratio: Ratio = temp.allowedAspectRatios[ratioId];
this.setAspectRatio(ratio);
......@@ -448,7 +448,7 @@ class ImageManipulation {
if (this.currentCropVariant.selectedRatio) {
// set data explicitly or setAspectRatio up-scales the crop
this.currentModal.find(`[data-option='${this.currentCropVariant.selectedRatio}']`).addClass('active');
this.currentModal.find(`[data-bs-option='${this.currentCropVariant.selectedRatio}']`).addClass('active');
}
}
......@@ -507,8 +507,8 @@ class ImageManipulation {
private update(cropVariant: CropVariant): void {
const temp: CropVariant = $.extend(true, {}, cropVariant);
const selectedRatio: Ratio = cropVariant.allowedAspectRatios[cropVariant.selectedRatio];
this.currentModal.find('[data-option]').removeClass('active');
this.currentModal.find(`[data-option="${cropVariant.selectedRatio}"]`).addClass('active');
this.currentModal.find('[data-bs-option]').removeClass('active');
this.currentModal.find(`[data-bs-option="${cropVariant.selectedRatio}"]`).addClass('active');
/**
* Setting the aspect ratio cause a redraw of the crop area so we need to manually reset it to last data
*/
......
......@@ -116,7 +116,7 @@ class Localization {
);
}
slideStep1 += '<div data-toggle="buttons">' + actions.join('<hr>') + '</div>';
slideStep1 += '<div data-bs-toggle="buttons">' + actions.join('<hr>') + '</div>';
Wizard.addSlide(
'localize-choose-action',
TYPO3.lang['localize.wizard.header_page']
......@@ -155,7 +155,7 @@ class Localization {
Wizard.unlockNextStep();
});
const $languageButtons = $('<div />', {class: 'row', 'data-toggle': 'buttons'});
const $languageButtons = $('<div />', {class: 'row', 'data-bs-toggle': 'buttons'});
for (const languageObject of result) {
$languageButtons.append(
......
......@@ -105,6 +105,7 @@ class LoginRefresh {
public showTimeoutModal(): void {
this.isTimingOut = true;
this.$timeoutModal.modal(this.options.modalConfig);
this.$timeoutModal.modal('show');
this.fillProgressbar(this.$timeoutModal);
}
......@@ -121,6 +122,7 @@ class LoginRefresh {
*/
public showBackendLockedModal(): void {
this.$backendLockedModal.modal(this.options.modalConfig);
this.$backendLockedModal.modal('show');
}
/**
......@@ -136,9 +138,12 @@ class LoginRefresh {
public showLoginForm(): void {
// log off for sure
new AjaxRequest(TYPO3.settings.ajaxUrls.logout).get().then((): void => {
TYPO3.configuration.showRefreshLoginPopup
? this.showLoginPopup()
: this.$loginForm.modal(this.options.modalConfig);
if (TYPO3.configuration.showRefreshLoginPopup) {
this.showLoginPopup();
} else {
this.$loginForm.modal(this.options.modalConfig);
this.$loginForm.modal('show');
}
});
}
......
......@@ -378,7 +378,7 @@ class Modal {
$(theDocument).on('click', '.t3js-modal-trigger', (evt: JQueryEventObject): void => {
evt.preventDefault();
const $element = $(evt.currentTarget);
const content = $element.data('content') || 'Are you sure?';
const content = $element.data('bs-content') || 'Are you sure?';
const severity = typeof SeverityEnum[$element.data('severity')] !== 'undefined'
? SeverityEnum[$element.data('severity')]
: SeverityEnum.info;
......@@ -536,7 +536,7 @@ class Modal {
configuration.callback(currentModal);
}
return currentModal.modal();
return currentModal.modal('show');
}
}
......
......@@ -518,7 +518,7 @@ class MultiStepWizard {
return this.setup.$carousel;
}
let slides = '<div class="carousel slide" data-ride="carousel" data-interval="false">'
let slides = '<div class="carousel slide" data-bs-ride="carousel" data-bs-interval="false">'
+ '<div class="carousel-inner" role="listbox">';
for (let i = 0; i < this.setup.slides.length; ++i) {
......@@ -528,7 +528,7 @@ class MultiStepWizard {
if (typeof slideContent === 'object') {
slideContent = slideContent.html();
}
slides += '<div class="item" data-slide="' + currentSlide.identifier + '" data-step="' + i + '">' + slideContent + '</div>';
slides += '<div class="item" data-bs-slide="' + currentSlide.identifier + '" data-step="' + i + '">' + slideContent + '</div>';
}
slides += '</div></div>';
......
......@@ -12,6 +12,8 @@
*/
import $ from 'jquery';
// @todo Importing bootstrap here, to have jQuery.fn.alert applied
import 'bootstrap';
import {AbstractAction} from './ActionButton/AbstractAction';
import {SeverityEnum} from './Enum/Severity';
import Severity = require('./Severity');
......@@ -139,7 +141,7 @@ class Notification {
const $box = $(
'<div id="' + notificationId + '" class="alert alert-' + className + ' alert-dismissible fade" role="alert">' +
'<button type="button" class="close" data-dismiss="alert">' +
'<button type="button" class="close" data-bs-dismiss="alert">' +
'<span aria-hidden="true"><i class="fa fa-times-circle"></i></span>' +
'<span class="sr-only">Close</span>' +
'</button>' +
......
......@@ -12,8 +12,6 @@
*/
import $ from 'jquery';
// @todo Importing bootstrap here, to have jQuery.fn.popup applied
import 'bootstrap';
import {Popover as BootstrapPopover} from 'bootstrap';
/**
......@@ -28,7 +26,7 @@ class Popover {
*
* @return {string}
*/
private readonly DEFAULT_SELECTOR: string = '[data-toggle="popover"]';
private readonly DEFAULT_SELECTOR: string = '[data-bs-toggle="popover"]';
constructor() {
this.initialize();
......@@ -39,7 +37,10 @@ class Popover {
*/
public initialize(selector?: string): void {
selector = selector || this.DEFAULT_SELECTOR;
$(selector).popover();
$(selector).each((i, el) => {
const popover = new BootstrapPopover(el)
$(el).data('typo3.bs.popover', popover);
});
}
// noinspection JSMethodCanBeStatic
......@@ -48,8 +49,11 @@ class Popover {
*
* @param {JQuery} $element
*/
public popover($element: JQuery): void {
$element.popover();
public popover($element: JQuery) {
$element.each((i, el) => {
const popover = new BootstrapPopover(el)
$(el).data('typo3.bs.popover', popover);
});
}
// noinspection JSMethodCanBeStatic
......@@ -62,12 +66,15 @@ class Popover {
public setOptions($element: JQuery, options?: BootstrapPopover.Options): void {
options = options || <BootstrapPopover.Options>{};
const title: string|(() => void) = options.title || $element.data('title') || '';
const content: string|(() => void) = options.content || $element.data('content') || '';
const content: string|(() => void) = options.content || $element.data('bs-content') || '';
$element
.attr('data-original-title', (title as string))
.attr('data-content', (content as string))
.attr('data-placement', 'auto')
.popover(options);
.attr('data-bs-original-title', (title as string))
.attr('data-bs-content', (content as string))
.attr('data-bs-placement', 'auto')
$.each(options, (key, value) => {
this.setOption($element, key, value);
});
}
// noinspection JSMethodCanBeStatic
......@@ -79,7 +86,16 @@ class Popover {
* @param {String} value
*/
public setOption($element: JQuery, key: string, value: string): void {
$element.data('bs.popover').options[key] = value;
if (key === 'content') {
$element.attr('data-bs-content', value);
} else {
$element.each((i, el) => {
const popover = $(el).data('typo3.bs.popover');
if (popover) {
popover.config[key] = value;
}
});
}
}
// noinspection JSMethodCanBeStatic
......@@ -89,7 +105,12 @@ class Popover {
* @param {JQuery} $element
*/
public show($element: JQuery): void {
$element.popover('show');
$element.each((i, el) => {
const popover = $(el).data('typo3.bs.popover');
if (popover) {
popover.show();
}
});
}
// noinspection JSMethodCanBeStatic
......@@ -99,7 +120,12 @@ class Popover {
* @param {JQuery} $element
*/
public hide($element: JQuery): void {
$element.popover('hide');
$element.each((i, el) => {
const popover = $(el).data('typo3.bs.popover');
if (popover) {
popover.hide();
}
});
}
// noinspection JSMethodCanBeStatic
......@@ -109,7 +135,12 @@ class Popover {
* @param {Object} $element
*/
public destroy($element: JQuery): void {
$element.popover('destroy');
$element.each((i, el) => {
const popover = $(el).data('typo3.bs.popover');
if (popover) {
popover.dispose();
}
});
}
// noinspection JSMethodCanBeStatic
......@@ -119,7 +150,12 @@ class Popover {
* @param {Object} $element
*/
public toggle($element: JQuery): void {
$element.popover('toggle');
$element.each((i, el) => {
const popover = $(el).data('typo3.bs.popover');
if (popover) {
popover.toggle();
}
});
}
}
......
......@@ -22,7 +22,7 @@ import $ from 'jquery';
class Tooltip {
constructor() {
$((): void => {
this.initialize('[data-toggle="tooltip"]');
this.initialize('[data-bs-toggle="tooltip"]');
});
}
......@@ -40,7 +40,7 @@ class Tooltip {
*/
public show($element: JQuery, title: string): void {
$element
.attr('data-placement', 'auto')
.attr('data-bs-placement', 'auto')
.attr('data-title', title)
.tooltip('show');
}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment