diff --git a/Build/Sources/TypeScript/backend/localization.ts b/Build/Sources/TypeScript/backend/localization.ts index 4333cc736aa9929a3b9cf51e8128412a08e40d78..a627a8b836745fefee1eaa2fc00974cd0ee47dd3 100644 --- a/Build/Sources/TypeScript/backend/localization.ts +++ b/Build/Sources/TypeScript/backend/localization.ts @@ -17,7 +17,8 @@ import { AjaxResponse } from '@typo3/core/ajax/ajax-response'; import { SeverityEnum } from './enum/severity'; import AjaxRequest from '@typo3/core/ajax/ajax-request'; import Icons from './icons'; -import Wizard from './wizard'; +import Modal, { ModalElement } from './modal'; +import MultiStepWizard, { MultiStepWizardSettings } from './multi-step-wizard'; import '@typo3/backend/element/icon-element'; type LanguageRecord = { @@ -44,9 +45,6 @@ type SummaryRecord = { class Localization { private readonly triggerButton: string = '.t3js-localize'; - private localizationMode: string = null; - private sourceLanguage: number = null; - private records: Array<any> = []; constructor() { DocumentService.ready().then((): void => { @@ -54,279 +52,284 @@ class Localization { }); } - private initialize(): void { - Icons.getIcon('actions-localize', Icons.sizes.large).then((localizeIconMarkup: string): void => { - Icons.getIcon('actions-edit-copy', Icons.sizes.large).then((copyIconMarkup: string): void => { - $(this.triggerButton).removeClass('disabled'); - - $(document).on('click', this.triggerButton, (e: JQueryEventObject): void => { - e.preventDefault(); - - const $triggerButton = $(e.currentTarget); - const actions: Array<string> = []; - const availableLocalizationModes: Array<string> = []; - let slideStep1: string = ''; - - if ($triggerButton.data('allowTranslate')) { - actions.push( - '<div class="row">' - + '<div class="col-sm-3">' - + '<label class="btn btn-default d-block t3js-localization-option" data-helptext=".t3js-helptext-translate">' - + localizeIconMarkup - + '<input type="radio" name="mode" id="mode_translate" value="localize" style="display: none">' - + '<br>' + TYPO3.lang['localize.wizard.button.translate'] + '</label>' - + '</div>' - + '<div class="col-sm-9">' - + '<p class="t3js-helptext t3js-helptext-translate text-body-secondary">' + TYPO3.lang['localize.educate.translate'] + '</p>' - + '</div>' - + '</div>', - ); - availableLocalizationModes.push('localize'); + private async initialize(): Promise<void> { + const localizeIconMarkup = await Icons.getIcon('actions-localize', Icons.sizes.large); + const copyIconMarkup = await Icons.getIcon('actions-edit-copy', Icons.sizes.large); + + $(this.triggerButton).removeClass('disabled'); + + $(document).on('click', this.triggerButton, async (e: JQueryEventObject): Promise<void> => { + e.preventDefault(); + + const $triggerButton = $(e.currentTarget); + const actions: Array<string> = []; + const availableLocalizationModes: Array<string> = []; + let slideStep1: string = ''; + + if ($triggerButton.data('allowTranslate') === 0 && $triggerButton.data('allowCopy') === 0) { + Modal.confirm( + TYPO3.lang['window.localization.mixed_mode.title'], + TYPO3.lang['window.localization.mixed_mode.message'], + SeverityEnum.warning, + [ + { + text: TYPO3?.lang?.['button.ok'] || 'OK', + btnClass: 'btn-warning', + name: 'ok', + trigger: (e: Event, modal: ModalElement): void => modal.hideModal() + } + ] + ); + return; + } + + const availableLanguages: LanguageRecord[] = await (await this.loadAvailableLanguages( + parseInt($triggerButton.data('pageId'), 10), + parseInt($triggerButton.data('languageId'), 10), + )).resolve(); + + if ($triggerButton.data('allowTranslate')) { + actions.push( + '<div class="row">' + + '<div class="col-sm-3">' + + '<label class="btn btn-default d-block t3js-localization-option" data-helptext=".t3js-helptext-translate">' + + localizeIconMarkup + + '<input type="radio" name="mode" id="mode_translate" value="localize" style="display: none">' + + '<br>' + TYPO3.lang['localize.wizard.button.translate'] + '</label>' + + '</div>' + + '<div class="col-sm-9">' + + '<p class="t3js-helptext t3js-helptext-translate text-body-secondary">' + TYPO3.lang['localize.educate.translate'] + '</p>' + + '</div>' + + '</div>', + ); + availableLocalizationModes.push('localize'); + } + + if ($triggerButton.data('allowCopy')) { + actions.push( + '<div class="row">' + + '<div class="col-sm-3">' + + '<label class="btn btn-default d-block t3js-localization-option" data-helptext=".t3js-helptext-copy">' + + copyIconMarkup + + '<input type="radio" name="mode" id="mode_copy" value="copyFromLanguage" style="display: none">' + + '<br>' + TYPO3.lang['localize.wizard.button.copy'] + '</label>' + + '</div>' + + '<div class="col-sm-9">' + + '<p class="t3js-helptext t3js-helptext-copy text-body-secondary">' + TYPO3.lang['localize.educate.copy'] + '</p>' + + '</div>' + + '</div>', + ); + availableLocalizationModes.push('copyFromLanguage'); + } + + if (availableLocalizationModes.length === 1) { + MultiStepWizard.set('localizationMode', availableLocalizationModes[0]); + } else { + slideStep1 += '<div data-bs-toggle="buttons">' + actions.join('') + '</div>'; + MultiStepWizard.addSlide( + 'localize-choose-action', + TYPO3.lang['localize.wizard.header_page'] + .replace('{0}', $triggerButton.data('page')) + .replace('{1}', $triggerButton.data('languageName')), + slideStep1, + SeverityEnum.notice, + TYPO3.lang['localize.wizard.step.selectMode'], + ($slide: JQuery, settings: MultiStepWizardSettings): void => { + if (settings.localizationMode !== undefined) { + MultiStepWizard.unlockNextStep(); + } } + ); + } + + if (availableLanguages.length === 1) { + MultiStepWizard.set('sourceLanguage', availableLanguages[0].uid); + } else { + MultiStepWizard.addSlide( + 'localize-choose-language', + TYPO3.lang['localize.view.chooseLanguage'], + '', + SeverityEnum.notice, + TYPO3.lang['localize.wizard.step.chooseLanguage'], + async ($slide: JQuery, settings: MultiStepWizardSettings): Promise<void> => { + if (settings.sourceLanguage !== undefined) { + MultiStepWizard.unlockNextStep(); + } - if ($triggerButton.data('allowCopy')) { - actions.push( - '<div class="row">' - + '<div class="col-sm-3">' - + '<label class="btn btn-default d-block t3js-localization-option" data-helptext=".t3js-helptext-copy">' - + copyIconMarkup - + '<input type="radio" name="mode" id="mode_copy" value="copyFromLanguage" style="display: none">' - + '<br>' + TYPO3.lang['localize.wizard.button.copy'] + '</label>' - + '</div>' - + '<div class="col-sm-9">' - + '<p class="t3js-helptext t3js-helptext-copy text-body-secondary">' + TYPO3.lang['localize.educate.copy'] + '</p>' - + '</div>' - + '</div>', - ); - availableLocalizationModes.push('copyFromLanguage'); - } + $slide.html('<div class="text-center">' + (await Icons.getIcon('spinner-circle', Icons.sizes.large)) + '</div>'); + MultiStepWizard.getComponent().on('click', '.t3js-language-option', (optionEvt: JQueryEventObject): void => { + const $me = $(optionEvt.currentTarget); + const $radio = $me.prev(); - if ($triggerButton.data('allowTranslate') === 0 && $triggerButton.data('allowCopy') === 0) { - actions.push( - '<div class="row">' - + '<div class="col-sm-12">' - + '<div class="alert alert-warning">' - + '<div class="media">' - + '<div class="media-left">' - + '<span class="icon-emphasized"><typo3-backend-icon identifier="actions-exclamation" size="small"></typo3-backend-icon></span>' - + '</div>' - + '<div class="media-body">' - + '<p class="alert-message">' + TYPO3.lang['localize.educate.noTranslate'] + '</p>' - + '</div>' - + '</div>' - + '</div>' - + '</div>' - + '</div>', - ); - } + MultiStepWizard.set('sourceLanguage', $radio.val()); + MultiStepWizard.unlockNextStep(); + }); - slideStep1 += '<div data-bs-toggle="buttons">' + actions.join('') + '</div>'; - Wizard.addSlide( - 'localize-choose-action', - TYPO3.lang['localize.wizard.header_page'] - .replace('{0}', $triggerButton.data('page')) - .replace('{1}', $triggerButton.data('languageName')), - slideStep1, - SeverityEnum.notice, - (): void => { - if (availableLocalizationModes.length === 1) { - // In case only one mode is available, select the mode and continue - this.localizationMode = availableLocalizationModes[0]; - Wizard.unlockNextStep().get(0).click(); - } - } - ); - Wizard.addSlide( - 'localize-choose-language', - TYPO3.lang['localize.view.chooseLanguage'], - '', - SeverityEnum.notice, - ($slide: JQuery): void => { - Icons.getIcon('spinner-circle', Icons.sizes.large).then((markup: string): void => { - $slide.html('<div class="text-center">' + markup + '</div>'); - - this.loadAvailableLanguages( - parseInt($triggerButton.data('pageId'), 10), - parseInt($triggerButton.data('languageId'), 10), - ).then(async (response: AjaxResponse): Promise<void> => { - const result: Array<LanguageRecord> = await response.resolve(); - if (result.length === 1) { - // We only have one result, auto select the record and continue - this.sourceLanguage = result[0].uid; - Wizard.unlockNextStep().get(0).click(); - return; - } - - Wizard.getComponent().on('click', '.t3js-language-option', (optionEvt: JQueryEventObject): void => { - const $me = $(optionEvt.currentTarget); - const $radio = $me.prev(); - - this.sourceLanguage = $radio.val(); - Wizard.unlockNextStep(); - }); - - const $languageButtons = $('<div />', { class: 'row' }); - - for (const languageObject of result) { - const id: string = 'language' + languageObject.uid; - const $input: JQuery = $('<input />', { - type: 'radio', - name: 'language', - id: id, - value: languageObject.uid, - style: 'display: none;', - class: 'btn-check' - }); - const $label: JQuery = $('<label />', { class: 'btn btn-default d-block t3js-language-option option', 'for': id }) - .text(' ' + languageObject.title) - .prepend(languageObject.flagIcon); - - $languageButtons.append( - $('<div />', { class: 'col-sm-4' }) - .append($input) - .append($label), - ); - } - $slide.empty().append($languageButtons); - }); - }); - }, - ); - Wizard.addSlide( - 'localize-summary', - TYPO3.lang['localize.view.summary'], - '', - SeverityEnum.notice, ($slide: JQuery): void => { - Icons.getIcon('spinner-circle', Icons.sizes.large).then((markup: string): void => { - $slide.html('<div class="text-center">' + markup + '</div>'); + const $languageButtons = $('<div />', { class: 'row' }); + + for (const languageObject of availableLanguages) { + const id: string = 'language' + languageObject.uid; + const $input: JQuery = $('<input />', { + type: 'radio', + name: 'language', + id: id, + value: languageObject.uid, + style: 'display: none;', + class: 'btn-check' }); - this.getSummary( - parseInt($triggerButton.data('pageId'), 10), - parseInt($triggerButton.data('languageId'), 10), - ).then(async (response: AjaxResponse): Promise<void> => { - const result: SummaryRecord = await response.resolve(); - $slide.empty(); - this.records = []; - - const columns = result.columns.columns; - const columnList = result.columns.columnList; - - columnList.forEach((colPos: number): void => { - if (typeof result.records[colPos] === 'undefined') { - return; - } - - const column = columns[colPos]; - const $row = $('<div />', { class: 'row' }); - - result.records[colPos].forEach((record: SummaryColPosRecord): void => { - const label = ' (' + record.uid + ') ' + record.title; - this.records.push(record.uid); - - $row.append( - $('<div />', { 'class': 'col-sm-6' }).append( - $('<div />', { 'class': 'input-group' }).append( - $('<span />', { 'class': 'input-group-text' }).append( - $('<input />', { - type: 'checkbox', - 'class': 't3js-localization-toggle-record', - id: 'record-uid-' + record.uid, - checked: 'checked', - 'data-uid': record.uid, - 'aria-label': label, - }), - ), - $('<label />', { - 'class': 'form-control', - for: 'record-uid-' + record.uid, - }).text(label).prepend(record.icon), - ), - ), - ); - }); - - $slide.append( - $('<fieldset />', { - 'class': 'localization-fieldset', - }).append( - $('<label />').text(column).prepend( - $('<input />', { - 'class': 't3js-localization-toggle-column', - type: 'checkbox', - checked: 'checked', - }), - ), - $row, + const $label: JQuery = $('<label />', { + class: 'btn btn-default d-block t3js-language-option option', + 'for': id + }) + .text(' ' + languageObject.title) + .prepend(languageObject.flagIcon); + + $languageButtons.append( + $('<div />', { class: 'col-sm-4' }) + .append($input) + .append($label), + ); + } + $slide.empty().append($languageButtons); + }, + ); + } + MultiStepWizard.addSlide( + 'localize-summary', + TYPO3.lang['localize.view.summary'], + '', + SeverityEnum.notice, + TYPO3.lang['localize.wizard.step.selectRecords'], + async ($slide: JQuery, settings: MultiStepWizardSettings): Promise<void> => { + $slide.empty().html('<div class="text-center">' + (await Icons.getIcon('spinner-circle', Icons.sizes.large)) + '</div>'); + + const result: SummaryRecord = await (await this.getSummary( + parseInt($triggerButton.data('pageId'), 10), + parseInt($triggerButton.data('languageId'), 10), + settings.sourceLanguage + )).resolve(); + + $slide.empty(); + + MultiStepWizard.set('records', []); + + const columns = result.columns.columns; + const columnList = result.columns.columnList; + + columnList.forEach((colPos: number): void => { + if (typeof result.records[colPos] === 'undefined') { + return; + } + + const column = columns[colPos]; + const $row = $('<div />', { class: 'row' }); + + result.records[colPos].forEach((record: SummaryColPosRecord): void => { + const label = ' (' + record.uid + ') ' + record.title; + settings.records.push(record.uid); + + $row.append( + $('<div />', { 'class': 'col-sm-6' }).append( + $('<div />', { 'class': 'input-group' }).append( + $('<span />', { 'class': 'input-group-text' }).append( + $('<input />', { + type: 'checkbox', + 'class': 't3js-localization-toggle-record', + id: 'record-uid-' + record.uid, + checked: 'checked', + 'data-uid': record.uid, + 'aria-label': label, + }), ), - ); - }); - - Wizard.unlockNextStep(); - - Wizard.getComponent().on('change', '.t3js-localization-toggle-record', (cmpEvt: JQueryEventObject): void => { - const $me = $(cmpEvt.currentTarget); - const uid = $me.data('uid'); - const $parent = $me.closest('fieldset'); - const $columnCheckbox = $parent.find('.t3js-localization-toggle-column'); - - if ($me.is(':checked')) { - this.records.push(uid); - } else { - const index = this.records.indexOf(uid); - if (index > -1) { - this.records.splice(index, 1); - } - } - - const $allChildren = $parent.find('.t3js-localization-toggle-record'); - const $checkedChildren = $parent.find('.t3js-localization-toggle-record:checked'); - - $columnCheckbox.prop('checked', $checkedChildren.length > 0); - $columnCheckbox.prop('__indeterminate', $checkedChildren.length > 0 && $checkedChildren.length < $allChildren.length); - - if (this.records.length > 0) { - Wizard.unlockNextStep(); - } else { - Wizard.lockNextStep(); - } - }).on('change', '.t3js-localization-toggle-column', (toggleEvt: JQueryEventObject): void => { - const $me = $(toggleEvt.currentTarget); - const $children = $me.closest('fieldset').find('.t3js-localization-toggle-record'); - - $children.prop('checked', $me.is(':checked')); - $children.trigger('change'); - }); - }); - }, - ); - - Wizard.addFinalProcessingSlide((): void => { - this.localizeRecords( - parseInt($triggerButton.data('pageId'), 10), - parseInt($triggerButton.data('languageId'), 10), - this.records, - ).then((): void => { - Wizard.dismiss(); - document.location.reload(); + $('<label />', { + 'class': 'form-control', + for: 'record-uid-' + record.uid, + }).text(label).prepend(record.icon), + ), + ), + ); }); - }).then((): void => { - Wizard.show(); - Wizard.getComponent().on('click', '.t3js-localization-option', (optionEvt: JQueryEventObject): void => { - const $me = $(optionEvt.currentTarget); - const $radio = $me.find('input[type="radio"]'); - - if ($me.data('helptext')) { - const $container = $(optionEvt.delegateTarget); - $container.find('.t3js-localization-option').removeClass('active'); - $container.find('.t3js-helptext').addClass('text-body-secondary'); - $me.addClass('active'); - $container.find($me.data('helptext')).removeClass('text-body-secondary'); + $slide.append( + $('<fieldset />', { + 'class': 'localization-fieldset', + }).append( + $('<label />').text(column).prepend( + $('<input />', { + 'class': 't3js-localization-toggle-column', + type: 'checkbox', + checked: 'checked', + }), + ), + $row, + ), + ); + }); + + MultiStepWizard.unlockNextStep(); + + MultiStepWizard.getComponent().on('change', '.t3js-localization-toggle-record', (cmpEvt: JQueryEventObject): void => { + const $me = $(cmpEvt.currentTarget); + const uid = $me.data('uid'); + const $parent = $me.closest('fieldset'); + const $columnCheckbox = $parent.find('.t3js-localization-toggle-column'); + + if ($me.is(':checked')) { + settings.records.push(uid); + } else { + const index = settings.records.indexOf(uid); + if (index > -1) { + settings.records.splice(index, 1); } - this.localizationMode = $radio.val(); - Wizard.unlockNextStep(); - }); + } + + const $allChildren = $parent.find('.t3js-localization-toggle-record'); + const $checkedChildren = $parent.find('.t3js-localization-toggle-record:checked'); + + $columnCheckbox.prop('checked', $checkedChildren.length > 0); + $columnCheckbox.prop('__indeterminate', $checkedChildren.length > 0 && $checkedChildren.length < $allChildren.length); + + if (settings.records.length > 0) { + MultiStepWizard.unlockNextStep(); + } else { + MultiStepWizard.lockNextStep(); + } + }).on('change', '.t3js-localization-toggle-column', (toggleEvt: JQueryEventObject): void => { + const $me = $(toggleEvt.currentTarget); + const $children = $me.closest('fieldset').find('.t3js-localization-toggle-record'); + + $children.prop('checked', $me.is(':checked')); + $children.trigger('change'); }); + }, + ); + + MultiStepWizard.addFinalProcessingSlide(async ($slide: JQuery, settings: MultiStepWizardSettings): Promise<void> => { + await this.localizeRecords( + parseInt($triggerButton.data('pageId'), 10), + parseInt($triggerButton.data('languageId'), 10), + settings.sourceLanguage, + settings.localizationMode, + settings.records, + ); + MultiStepWizard.dismiss(); + document.location.reload(); + }).then((): void => { + MultiStepWizard.show(); + + MultiStepWizard.getComponent().on('click', '.t3js-localization-option', (optionEvt: JQueryEventObject): void => { + const $me = $(optionEvt.currentTarget); + const $radio = $me.find('input[type="radio"]'); + + if ($me.data('helptext')) { + const $container = $(optionEvt.delegateTarget); + $container.find('.t3js-localization-option').removeClass('active'); + $container.find('.t3js-helptext').addClass('text-body-secondary'); + $me.addClass('active'); + $container.find($me.data('helptext')).removeClass('text-body-secondary'); + } + MultiStepWizard.set('localizationMode', $radio.val()); + MultiStepWizard.unlockNextStep(); }); }); }); @@ -348,33 +351,24 @@ class Localization { /** * Get summary for record processing - * - * @param {number} pageId - * @param {number} languageId - * @returns {Promise<AjaxResponse>} */ - private getSummary(pageId: number, languageId: number): Promise<AjaxResponse> { + private getSummary(pageId: number, languageId: number, sourceLanguage: number): Promise<AjaxResponse> { return new AjaxRequest(TYPO3.settings.ajaxUrls.records_localize_summary).withQueryArguments({ pageId: pageId, destLanguageId: languageId, - languageId: this.sourceLanguage, + languageId: sourceLanguage, }).get(); } /** * Localize records - * - * @param {number} pageId - * @param {number} languageId - * @param {Array<number>} uidList - * @returns {Promise<AjaxResponse>} */ - private localizeRecords(pageId: number, languageId: number, uidList: Array<number>): Promise<AjaxResponse> { + private localizeRecords(pageId: number, languageId: number, sourceLanguage: number, localizationMode: string, uidList: Array<number>): Promise<AjaxResponse> { return new AjaxRequest(TYPO3.settings.ajaxUrls.records_localize).withQueryArguments({ pageId: pageId, - srcLanguageId: this.sourceLanguage, + srcLanguageId: sourceLanguage, destLanguageId: languageId, - action: this.localizationMode, + action: localizationMode, uidList: uidList, }).get(); } diff --git a/Build/Sources/TypeScript/backend/multi-step-wizard.ts b/Build/Sources/TypeScript/backend/multi-step-wizard.ts index 2ddb31dca28b0745702ee6fb158f773bedfd1f1f..209637d1d63fe9ba53bd62765b88780e9c5c3561 100644 --- a/Build/Sources/TypeScript/backend/multi-step-wizard.ts +++ b/Build/Sources/TypeScript/backend/multi-step-wizard.ts @@ -20,12 +20,12 @@ import Icons from './icons'; type SlideCallback = ($slide: JQuery, settings: MultiStepWizardSettings, identifier: string) => void; -interface MultiStepWizardSettings { +export interface MultiStepWizardSettings { [key: string]: any; } interface MultiStepWizardSetup { - slides: Array<any>; + slides: Slide[]; settings: MultiStepWizardSettings; forceSelection: boolean; $carousel: JQuery; @@ -108,23 +108,22 @@ class MultiStepWizard { * @param {Function} callback * @returns {Promise<string>} */ - public addFinalProcessingSlide(callback?: SlideCallback): Promise<void> { + public async addFinalProcessingSlide(callback?: SlideCallback): Promise<void> { if (!callback) { callback = (): void => { this.dismiss(); }; } - return Icons.getIcon('spinner-circle', Icons.sizes.default, null, null).then((markup: string) => { - const $processingSlide = $('<div />', { class: 'text-center' }).append(markup); - this.addSlide( - 'final-processing-slide', top.TYPO3.lang['wizard.processing.title'], - $processingSlide[0].outerHTML, - Severity.info, - null, - callback, - ); - }); + const spinnerIcon = await Icons.getIcon('spinner-circle', Icons.sizes.large, null, null); + const $processingSlide = $('<div />', { class: 'text-center' }).append(spinnerIcon); + this.addSlide( + 'final-processing-slide', top.TYPO3.lang['wizard.processing.title'], + $processingSlide[0].outerHTML, + Severity.info, + null, + callback, + ); } /** @@ -166,6 +165,12 @@ class MultiStepWizard { }); this.getComponent().on('wizard-visible', (): void => { + if (this.setup.forceSelection) { + // @todo: This is a hack as modal buttons cannot be initially disabled. + this.lockPrevStep(); + this.lockNextStep(); + } + this.runSlideCallback(firstSlide, this.setup.$carousel.find('.carousel-item').first()); }).on('wizard-dismissed', (): void => { this.setup = $.extend(true, {}, this.originalSetup); @@ -282,11 +287,11 @@ class MultiStepWizard { const currentIndex = this.setup.$carousel.data('currentIndex'); const slide = this.setup.slides[currentIndex]; - this.runSlideCallback(slide, $(evt.relatedTarget)); - if (this.setup.forceSelection) { this.lockNextStep(); } + + this.runSlideCallback(slide, $(evt.relatedTarget)); }); // Custom event, closes the wizard @@ -331,9 +336,15 @@ class MultiStepWizard { const nextSlideNumber = this.setup.$carousel.data('currentSlide') + 1; const currentIndex = this.setup.$carousel.data('currentIndex'); const nextIndex = currentIndex + 1; + const $slideContent = $modal.find('.carousel-item:eq(' + nextIndex + ')'); + // Flush content when sliding + $slideContent.empty().append(this.setup.slides[nextIndex].content); $modalTitle.text(this.setup.slides[nextIndex].title); + // Always unlock previous step + this.unlockPrevStep(); + this.setup.$carousel.data('currentSlide', nextSlideNumber); this.setup.$carousel.data('currentIndex', nextIndex); @@ -368,12 +379,22 @@ class MultiStepWizard { const nextSlideNumber = this.setup.$carousel.data('currentSlide') - 1; const currentIndex = this.setup.$carousel.data('currentIndex'); const nextIndex = currentIndex - 1; + const $slideContent = $modal.find('.carousel-item:eq(' + nextIndex + ')'); + + // Flush content when sliding + $slideContent.empty().append(this.setup.slides[nextIndex].content); + $modalTitle.text(this.setup.slides[nextIndex].title); + + // Always unlock previous step if there is any + if (nextIndex > 0) { + this.unlockPrevStep(); + } else { + this.lockPrevStep(); + } this.setup.$carousel.data('currentSlide', nextSlideNumber); this.setup.$carousel.data('currentIndex', nextIndex); - $modalTitle.text(this.setup.slides[nextIndex].title); - $modalFooter.find('.progress-bar.last-step') .width(this.setup.$carousel.data('initialStep') + '%') .text(this.getProgressBarTitle(this.setup.$carousel.data('slideCount') - 1)); diff --git a/typo3/sysext/backend/Resources/Private/Language/locallang_layout.xlf b/typo3/sysext/backend/Resources/Private/Language/locallang_layout.xlf index 75b9c93db0710d5aec7bdceb14d4b06419146dff..733028994f31b679fff18a135512494f225812cf 100644 --- a/typo3/sysext/backend/Resources/Private/Language/locallang_layout.xlf +++ b/typo3/sysext/backend/Resources/Private/Language/locallang_layout.xlf @@ -366,6 +366,18 @@ <trans-unit id="localize.wizard.button.process" resname="localize.wizard.button.process"> <source>Start processing</source> </trans-unit> + <trans-unit id="localize.wizard.step.selectMode" resname="localize.wizard.step.selectMode"> + <source>Localization mode</source> + </trans-unit> + <trans-unit id="localize.wizard.step.chooseLanguage" resname="localize.wizard.step.chooseLanguage"> + <source>Source Language</source> + </trans-unit> + <trans-unit id="localize.wizard.step.chooseLanguage" resname="localize.wizard.step.chooseLanguage"> + <source>Source Language</source> + </trans-unit> + <trans-unit id="localize.wizard.step.selectRecords" resname="localize.wizard.step.selectRecords"> + <source>Records</source> + </trans-unit> <trans-unit id="localize.view.chooseLanguage" resname="localize.view.chooseLanguage"> <source>Choose the language from which you want to localize the content</source> </trans-unit> @@ -390,10 +402,12 @@ <strong>Use this when you want to have freedom in designing your translated website.</strong> </source> </trans-unit> - <trans-unit id="localize.educate.noTranslate" resname="localize.educate.noTranslate" xml:space="preserve"> - <source>Using the translation wizard is not possible, because either translation is disabled or the translated page is in "mixed mode". Being in "mixed mode" means that both translated records that have a source language and "free mode" records without a parent, are present.<br> - We highlighted the problematic records for you in this case. - </source> + <trans-unit id="window.localization.mixed_mode.title" resname="window.localization.mixed_mode.title"> + <source>Localization not possible</source> + </trans-unit> + <trans-unit id="window.localization.mixed_mode.message" resname="window.localization.mixed_mode.message"> + <source>Using the translation wizard is not possible, because either translation is disabled or the translated page is in "mixed mode". Being in "mixed mode" means that both translated records that have a source language and "free mode" records without a parent, are present. + We highlighted the problematic records for you in this case.</source> </trans-unit> <trans-unit id="error.invalidBackendLayout" resname="error.invalidBackendLayout"> <source>The selected page layout is mis-configured, no columns are specified to be editable. Please update your backend layout to have at least one parameter "colPos" set.</source> diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/localization.js b/typo3/sysext/backend/Resources/Public/JavaScript/localization.js index 5a2e9fb79a566a57925edcd65e3f6ee29c55f1d4..96be7d8b0171a1638099153955fafe3aee161582 100644 --- a/typo3/sysext/backend/Resources/Public/JavaScript/localization.js +++ b/typo3/sysext/backend/Resources/Public/JavaScript/localization.js @@ -10,4 +10,4 @@ * * The TYPO3 project - inspiring people to share! */ -import DocumentService from"@typo3/core/document-service.js";import $ from"jquery";import{SeverityEnum}from"@typo3/backend/enum/severity.js";import AjaxRequest from"@typo3/core/ajax/ajax-request.js";import Icons from"@typo3/backend/icons.js";import Wizard from"@typo3/backend/wizard.js";import"@typo3/backend/element/icon-element.js";class Localization{constructor(){this.triggerButton=".t3js-localize",this.localizationMode=null,this.sourceLanguage=null,this.records=[],DocumentService.ready().then((()=>{this.initialize()}))}initialize(){Icons.getIcon("actions-localize",Icons.sizes.large).then((e=>{Icons.getIcon("actions-edit-copy",Icons.sizes.large).then((a=>{$(this.triggerButton).removeClass("disabled"),$(document).on("click",this.triggerButton,(t=>{t.preventDefault();const o=$(t.currentTarget),i=[],l=[];let s="";o.data("allowTranslate")&&(i.push('<div class="row"><div class="col-sm-3"><label class="btn btn-default d-block t3js-localization-option" data-helptext=".t3js-helptext-translate">'+e+'<input type="radio" name="mode" id="mode_translate" value="localize" style="display: none"><br>'+TYPO3.lang["localize.wizard.button.translate"]+'</label></div><div class="col-sm-9"><p class="t3js-helptext t3js-helptext-translate text-body-secondary">'+TYPO3.lang["localize.educate.translate"]+"</p></div></div>"),l.push("localize")),o.data("allowCopy")&&(i.push('<div class="row"><div class="col-sm-3"><label class="btn btn-default d-block t3js-localization-option" data-helptext=".t3js-helptext-copy">'+a+'<input type="radio" name="mode" id="mode_copy" value="copyFromLanguage" style="display: none"><br>'+TYPO3.lang["localize.wizard.button.copy"]+'</label></div><div class="col-sm-9"><p class="t3js-helptext t3js-helptext-copy text-body-secondary">'+TYPO3.lang["localize.educate.copy"]+"</p></div></div>"),l.push("copyFromLanguage")),0===o.data("allowTranslate")&&0===o.data("allowCopy")&&i.push('<div class="row"><div class="col-sm-12"><div class="alert alert-warning"><div class="media"><div class="media-left"><span class="icon-emphasized"><typo3-backend-icon identifier="actions-exclamation" size="small"></typo3-backend-icon></span></div><div class="media-body"><p class="alert-message">'+TYPO3.lang["localize.educate.noTranslate"]+"</p></div></div></div></div></div>"),s+='<div data-bs-toggle="buttons">'+i.join("")+"</div>",Wizard.addSlide("localize-choose-action",TYPO3.lang["localize.wizard.header_page"].replace("{0}",o.data("page")).replace("{1}",o.data("languageName")),s,SeverityEnum.notice,(()=>{1===l.length&&(this.localizationMode=l[0],Wizard.unlockNextStep().get(0).click())})),Wizard.addSlide("localize-choose-language",TYPO3.lang["localize.view.chooseLanguage"],"",SeverityEnum.notice,(e=>{Icons.getIcon("spinner-circle",Icons.sizes.large).then((a=>{e.html('<div class="text-center">'+a+"</div>"),this.loadAvailableLanguages(parseInt(o.data("pageId"),10),parseInt(o.data("languageId"),10)).then((async a=>{const t=await a.resolve();if(1===t.length)return this.sourceLanguage=t[0].uid,void Wizard.unlockNextStep().get(0).click();Wizard.getComponent().on("click",".t3js-language-option",(e=>{const a=$(e.currentTarget).prev();this.sourceLanguage=a.val(),Wizard.unlockNextStep()}));const o=$("<div />",{class:"row"});for(const e of t){const a="language"+e.uid,t=$("<input />",{type:"radio",name:"language",id:a,value:e.uid,style:"display: none;",class:"btn-check"}),i=$("<label />",{class:"btn btn-default d-block t3js-language-option option",for:a}).text(" "+e.title).prepend(e.flagIcon);o.append($("<div />",{class:"col-sm-4"}).append(t).append(i))}e.empty().append(o)}))}))})),Wizard.addSlide("localize-summary",TYPO3.lang["localize.view.summary"],"",SeverityEnum.notice,(e=>{Icons.getIcon("spinner-circle",Icons.sizes.large).then((a=>{e.html('<div class="text-center">'+a+"</div>")})),this.getSummary(parseInt(o.data("pageId"),10),parseInt(o.data("languageId"),10)).then((async a=>{const t=await a.resolve();e.empty(),this.records=[];const o=t.columns.columns;t.columns.columnList.forEach((a=>{if(void 0===t.records[a])return;const i=o[a],l=$("<div />",{class:"row"});t.records[a].forEach((e=>{const a=" ("+e.uid+") "+e.title;this.records.push(e.uid),l.append($("<div />",{class:"col-sm-6"}).append($("<div />",{class:"input-group"}).append($("<span />",{class:"input-group-text"}).append($("<input />",{type:"checkbox",class:"t3js-localization-toggle-record",id:"record-uid-"+e.uid,checked:"checked","data-uid":e.uid,"aria-label":a})),$("<label />",{class:"form-control",for:"record-uid-"+e.uid}).text(a).prepend(e.icon))))})),e.append($("<fieldset />",{class:"localization-fieldset"}).append($("<label />").text(i).prepend($("<input />",{class:"t3js-localization-toggle-column",type:"checkbox",checked:"checked"})),l))})),Wizard.unlockNextStep(),Wizard.getComponent().on("change",".t3js-localization-toggle-record",(e=>{const a=$(e.currentTarget),t=a.data("uid"),o=a.closest("fieldset"),i=o.find(".t3js-localization-toggle-column");if(a.is(":checked"))this.records.push(t);else{const e=this.records.indexOf(t);e>-1&&this.records.splice(e,1)}const l=o.find(".t3js-localization-toggle-record"),s=o.find(".t3js-localization-toggle-record:checked");i.prop("checked",s.length>0),i.prop("__indeterminate",s.length>0&&s.length<l.length),this.records.length>0?Wizard.unlockNextStep():Wizard.lockNextStep()})).on("change",".t3js-localization-toggle-column",(e=>{const a=$(e.currentTarget),t=a.closest("fieldset").find(".t3js-localization-toggle-record");t.prop("checked",a.is(":checked")),t.trigger("change")}))}))})),Wizard.addFinalProcessingSlide((()=>{this.localizeRecords(parseInt(o.data("pageId"),10),parseInt(o.data("languageId"),10),this.records).then((()=>{Wizard.dismiss(),document.location.reload()}))})).then((()=>{Wizard.show(),Wizard.getComponent().on("click",".t3js-localization-option",(e=>{const a=$(e.currentTarget),t=a.find('input[type="radio"]');if(a.data("helptext")){const t=$(e.delegateTarget);t.find(".t3js-localization-option").removeClass("active"),t.find(".t3js-helptext").addClass("text-body-secondary"),a.addClass("active"),t.find(a.data("helptext")).removeClass("text-body-secondary")}this.localizationMode=t.val(),Wizard.unlockNextStep()}))}))}))}))}))}loadAvailableLanguages(e,a){return new AjaxRequest(TYPO3.settings.ajaxUrls.page_languages).withQueryArguments({pageId:e,languageId:a}).get()}getSummary(e,a){return new AjaxRequest(TYPO3.settings.ajaxUrls.records_localize_summary).withQueryArguments({pageId:e,destLanguageId:a,languageId:this.sourceLanguage}).get()}localizeRecords(e,a,t){return new AjaxRequest(TYPO3.settings.ajaxUrls.records_localize).withQueryArguments({pageId:e,srcLanguageId:this.sourceLanguage,destLanguageId:a,action:this.localizationMode,uidList:t}).get()}}export default new Localization; \ No newline at end of file +import DocumentService from"@typo3/core/document-service.js";import $ from"jquery";import{SeverityEnum}from"@typo3/backend/enum/severity.js";import AjaxRequest from"@typo3/core/ajax/ajax-request.js";import Icons from"@typo3/backend/icons.js";import Modal from"@typo3/backend/modal.js";import MultiStepWizard from"@typo3/backend/multi-step-wizard.js";import"@typo3/backend/element/icon-element.js";class Localization{constructor(){this.triggerButton=".t3js-localize",DocumentService.ready().then((()=>{this.initialize()}))}async initialize(){const e=await Icons.getIcon("actions-localize",Icons.sizes.large),t=await Icons.getIcon("actions-edit-copy",Icons.sizes.large);$(this.triggerButton).removeClass("disabled"),$(document).on("click",this.triggerButton,(async a=>{a.preventDefault();const o=$(a.currentTarget),l=[],i=[];let n="";if(0===o.data("allowTranslate")&&0===o.data("allowCopy"))return void Modal.confirm(TYPO3.lang["window.localization.mixed_mode.title"],TYPO3.lang["window.localization.mixed_mode.message"],SeverityEnum.warning,[{text:TYPO3?.lang?.["button.ok"]||"OK",btnClass:"btn-warning",name:"ok",trigger:(e,t)=>t.hideModal()}]);const s=await(await this.loadAvailableLanguages(parseInt(o.data("pageId"),10),parseInt(o.data("languageId"),10))).resolve();o.data("allowTranslate")&&(l.push('<div class="row"><div class="col-sm-3"><label class="btn btn-default d-block t3js-localization-option" data-helptext=".t3js-helptext-translate">'+e+'<input type="radio" name="mode" id="mode_translate" value="localize" style="display: none"><br>'+TYPO3.lang["localize.wizard.button.translate"]+'</label></div><div class="col-sm-9"><p class="t3js-helptext t3js-helptext-translate text-body-secondary">'+TYPO3.lang["localize.educate.translate"]+"</p></div></div>"),i.push("localize")),o.data("allowCopy")&&(l.push('<div class="row"><div class="col-sm-3"><label class="btn btn-default d-block t3js-localization-option" data-helptext=".t3js-helptext-copy">'+t+'<input type="radio" name="mode" id="mode_copy" value="copyFromLanguage" style="display: none"><br>'+TYPO3.lang["localize.wizard.button.copy"]+'</label></div><div class="col-sm-9"><p class="t3js-helptext t3js-helptext-copy text-body-secondary">'+TYPO3.lang["localize.educate.copy"]+"</p></div></div>"),i.push("copyFromLanguage")),1===i.length?MultiStepWizard.set("localizationMode",i[0]):(n+='<div data-bs-toggle="buttons">'+l.join("")+"</div>",MultiStepWizard.addSlide("localize-choose-action",TYPO3.lang["localize.wizard.header_page"].replace("{0}",o.data("page")).replace("{1}",o.data("languageName")),n,SeverityEnum.notice,TYPO3.lang["localize.wizard.step.selectMode"],((e,t)=>{void 0!==t.localizationMode&&MultiStepWizard.unlockNextStep()}))),1===s.length?MultiStepWizard.set("sourceLanguage",s[0].uid):MultiStepWizard.addSlide("localize-choose-language",TYPO3.lang["localize.view.chooseLanguage"],"",SeverityEnum.notice,TYPO3.lang["localize.wizard.step.chooseLanguage"],(async(e,t)=>{void 0!==t.sourceLanguage&&MultiStepWizard.unlockNextStep(),e.html('<div class="text-center">'+await Icons.getIcon("spinner-circle",Icons.sizes.large)+"</div>"),MultiStepWizard.getComponent().on("click",".t3js-language-option",(e=>{const t=$(e.currentTarget).prev();MultiStepWizard.set("sourceLanguage",t.val()),MultiStepWizard.unlockNextStep()}));const a=$("<div />",{class:"row"});for(const e of s){const t="language"+e.uid,o=$("<input />",{type:"radio",name:"language",id:t,value:e.uid,style:"display: none;",class:"btn-check"}),l=$("<label />",{class:"btn btn-default d-block t3js-language-option option",for:t}).text(" "+e.title).prepend(e.flagIcon);a.append($("<div />",{class:"col-sm-4"}).append(o).append(l))}e.empty().append(a)})),MultiStepWizard.addSlide("localize-summary",TYPO3.lang["localize.view.summary"],"",SeverityEnum.notice,TYPO3.lang["localize.wizard.step.selectRecords"],(async(e,t)=>{e.empty().html('<div class="text-center">'+await Icons.getIcon("spinner-circle",Icons.sizes.large)+"</div>");const a=await(await this.getSummary(parseInt(o.data("pageId"),10),parseInt(o.data("languageId"),10),t.sourceLanguage)).resolve();e.empty(),MultiStepWizard.set("records",[]);const l=a.columns.columns;a.columns.columnList.forEach((o=>{if(void 0===a.records[o])return;const i=l[o],n=$("<div />",{class:"row"});a.records[o].forEach((e=>{const a=" ("+e.uid+") "+e.title;t.records.push(e.uid),n.append($("<div />",{class:"col-sm-6"}).append($("<div />",{class:"input-group"}).append($("<span />",{class:"input-group-text"}).append($("<input />",{type:"checkbox",class:"t3js-localization-toggle-record",id:"record-uid-"+e.uid,checked:"checked","data-uid":e.uid,"aria-label":a})),$("<label />",{class:"form-control",for:"record-uid-"+e.uid}).text(a).prepend(e.icon))))})),e.append($("<fieldset />",{class:"localization-fieldset"}).append($("<label />").text(i).prepend($("<input />",{class:"t3js-localization-toggle-column",type:"checkbox",checked:"checked"})),n))})),MultiStepWizard.unlockNextStep(),MultiStepWizard.getComponent().on("change",".t3js-localization-toggle-record",(e=>{const a=$(e.currentTarget),o=a.data("uid"),l=a.closest("fieldset"),i=l.find(".t3js-localization-toggle-column");if(a.is(":checked"))t.records.push(o);else{const e=t.records.indexOf(o);e>-1&&t.records.splice(e,1)}const n=l.find(".t3js-localization-toggle-record"),s=l.find(".t3js-localization-toggle-record:checked");i.prop("checked",s.length>0),i.prop("__indeterminate",s.length>0&&s.length<n.length),t.records.length>0?MultiStepWizard.unlockNextStep():MultiStepWizard.lockNextStep()})).on("change",".t3js-localization-toggle-column",(e=>{const t=$(e.currentTarget),a=t.closest("fieldset").find(".t3js-localization-toggle-record");a.prop("checked",t.is(":checked")),a.trigger("change")}))})),MultiStepWizard.addFinalProcessingSlide((async(e,t)=>{await this.localizeRecords(parseInt(o.data("pageId"),10),parseInt(o.data("languageId"),10),t.sourceLanguage,t.localizationMode,t.records),MultiStepWizard.dismiss(),document.location.reload()})).then((()=>{MultiStepWizard.show(),MultiStepWizard.getComponent().on("click",".t3js-localization-option",(e=>{const t=$(e.currentTarget),a=t.find('input[type="radio"]');if(t.data("helptext")){const a=$(e.delegateTarget);a.find(".t3js-localization-option").removeClass("active"),a.find(".t3js-helptext").addClass("text-body-secondary"),t.addClass("active"),a.find(t.data("helptext")).removeClass("text-body-secondary")}MultiStepWizard.set("localizationMode",a.val()),MultiStepWizard.unlockNextStep()}))}))}))}loadAvailableLanguages(e,t){return new AjaxRequest(TYPO3.settings.ajaxUrls.page_languages).withQueryArguments({pageId:e,languageId:t}).get()}getSummary(e,t,a){return new AjaxRequest(TYPO3.settings.ajaxUrls.records_localize_summary).withQueryArguments({pageId:e,destLanguageId:t,languageId:a}).get()}localizeRecords(e,t,a,o,l){return new AjaxRequest(TYPO3.settings.ajaxUrls.records_localize).withQueryArguments({pageId:e,srcLanguageId:a,destLanguageId:t,action:o,uidList:l}).get()}}export default new Localization; \ No newline at end of file diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/multi-step-wizard.js b/typo3/sysext/backend/Resources/Public/JavaScript/multi-step-wizard.js index af3e70a780db02c9fb582b858dd5f9dd62e1ef53..562fb5cf4e248ceb093b1292a349dc9cfd5c7385 100644 --- a/typo3/sysext/backend/Resources/Public/JavaScript/multi-step-wizard.js +++ b/typo3/sysext/backend/Resources/Public/JavaScript/multi-step-wizard.js @@ -10,4 +10,4 @@ * * The TYPO3 project - inspiring people to share! */ -import{SeverityEnum}from"@typo3/backend/enum/severity.js";import $ from"jquery";import{Carousel}from"bootstrap";import Modal from"@typo3/backend/modal.js";import Severity from"@typo3/backend/severity.js";import Icons from"@typo3/backend/icons.js";class MultiStepWizard{constructor(){this.setup={slides:[],settings:{},forceSelection:!0,$carousel:null,carousel:null},this.originalSetup=$.extend(!0,{},this.setup)}set(t,e){return this.setup.settings[t]=e,this}addSlide(t,e,s="",i=SeverityEnum.info,r,a){const l={identifier:t,title:e,content:s,severity:i,progressBarTitle:r,callback:a};return this.setup.slides.push(l),this}addFinalProcessingSlide(t){return t||(t=()=>{this.dismiss()}),Icons.getIcon("spinner-circle",Icons.sizes.default,null,null).then((e=>{const s=$("<div />",{class:"text-center"}).append(e);this.addSlide("final-processing-slide",top.TYPO3.lang["wizard.processing.title"],s[0].outerHTML,Severity.info,null,t)}))}show(){const t=this.generateSlides(),e=this.setup.slides[0];Modal.advanced({title:e.title,content:t,severity:e.severity,staticBackdrop:!0,buttons:[{text:top.TYPO3.lang["wizard.button.cancel"],active:!0,btnClass:"btn-default float-start",name:"cancel",trigger:()=>{this.getComponent().trigger("wizard-dismiss")}},{text:top.TYPO3.lang["wizard.button.prev"],btnClass:"btn-"+Severity.getCssClass(e.severity),name:"prev"},{text:top.TYPO3.lang["wizard.button.next"],btnClass:"btn-"+Severity.getCssClass(e.severity),name:"next"}],additionalCssClasses:["modal-multi-step-wizard"],callback:t=>{this.setup.carousel=new Carousel(t.querySelector(".carousel")),this.addButtonContainer(),this.addProgressBar(),this.initializeEvents()}}),this.getComponent().on("wizard-visible",(()=>{this.runSlideCallback(e,this.setup.$carousel.find(".carousel-item").first())})).on("wizard-dismissed",(()=>{this.setup=$.extend(!0,{},this.originalSetup)}))}getComponent(){return null===this.setup.$carousel&&this.generateSlides(),this.setup.$carousel}dismiss(){Modal.dismiss()}lockNextStep(){const t=this.setup.$carousel.closest(".modal").find('button[name="next"]');return t.prop("disabled",!0),t}unlockNextStep(){const t=this.setup.$carousel.closest(".modal").find('button[name="next"]');return t.prop("disabled",!1),t}lockPrevStep(){const t=this.setup.$carousel.closest(".modal").find('button[name="prev"]');return t.prop("disabled",!0),t}unlockPrevStep(){const t=this.setup.$carousel.closest(".modal").find('button[name="prev"]');return t.prop("disabled",!1),t}triggerStepButton(t){const e=this.setup.$carousel.closest(".modal").find('button[name="'+t+'"]');return e.length>0&&!0!==e.prop("disabled")&&e.get(0).click(),e}blurCancelStep(){const t=this.setup.$carousel.closest(".modal").find('button[name="cancel"]');return t.trigger("blur"),t}initializeEvents(){const t=this.setup.$carousel.closest(".modal");this.initializeSlideNextEvent(t),this.initializeSlidePrevEvent(t),this.setup.$carousel.get(0).addEventListener("slide.bs.carousel",(e=>{"left"===e.direction?this.nextSlideChanges(t):this.prevSlideChanges(t)})),this.setup.$carousel.get(0).addEventListener("slid.bs.carousel",(t=>{const e=this.setup.$carousel.data("currentIndex"),s=this.setup.slides[e];this.runSlideCallback(s,$(t.relatedTarget)),this.setup.forceSelection&&this.lockNextStep()}));const e=this.getComponent();e.on("wizard-dismiss",this.dismiss),Modal.currentModal.addEventListener("typo3-modal-hidden",(()=>{e.trigger("wizard-dismissed")})),Modal.currentModal.addEventListener("typo3-modal-shown",(()=>{e.trigger("wizard-visible")}))}initializeSlideNextEvent(t){t.find(".modal-footer").find('button[name="next"]').off().on("click",(()=>{this.setup.carousel.next()}))}initializeSlidePrevEvent(t){t.find(".modal-footer").find('button[name="prev"]').off().on("click",(()=>{this.setup.carousel.prev()}))}nextSlideChanges(t){this.initializeSlideNextEvent(t);const e=t.find(".modal-title"),s=t.find(".modal-footer"),i=this.setup.$carousel.data("currentSlide")+1,r=this.setup.$carousel.data("currentIndex"),a=r+1;e.text(this.setup.slides[a].title),this.setup.$carousel.data("currentSlide",i),this.setup.$carousel.data("currentIndex",a);const l=s.find(".progress-bar");l.eq(r).width("0%"),l.eq(a).width(this.setup.$carousel.data("initialStep")*i+"%").removeClass("inactive"),this.updateCurrentSeverity(t,r,a)}prevSlideChanges(t){this.initializeSlidePrevEvent(t);const e=t.find(".modal-title"),s=t.find(".modal-footer"),i=s.find('button[name="next"]'),r=this.setup.$carousel.data("currentSlide")-1,a=this.setup.$carousel.data("currentIndex"),l=a-1;this.setup.$carousel.data("currentSlide",r),this.setup.$carousel.data("currentIndex",l),e.text(this.setup.slides[l].title),s.find(".progress-bar.last-step").width(this.setup.$carousel.data("initialStep")+"%").text(this.getProgressBarTitle(this.setup.$carousel.data("slideCount")-1)),i.text(top.TYPO3.lang["wizard.button.next"]);const n=s.find(".progress-bar");n.eq(a).width(this.setup.$carousel.data("initialStep")+"%").addClass("inactive"),n.eq(l).width(this.setup.$carousel.data("initialStep")*r+"%").removeClass("inactive"),this.updateCurrentSeverity(t,a,l)}updateCurrentSeverity(t,e,s){t.find(".modal-footer").find('button[name="next"]').removeClass("btn-"+Severity.getCssClass(this.setup.slides[e].severity)).addClass("btn-"+Severity.getCssClass(this.setup.slides[s].severity)),t.removeClass("modal-severity-"+Severity.getCssClass(this.setup.slides[e].severity)).addClass("modal-severity-"+Severity.getCssClass(this.setup.slides[s].severity))}getProgressBarTitle(t){let e;return e=null===this.setup.slides[t].progressBarTitle?0===t?top.TYPO3.lang["wizard.progressStep.start"]:t>=this.setup.$carousel.data("slideCount")-1?top.TYPO3.lang["wizard.progressStep.finish"]:top.TYPO3.lang["wizard.progressStep"]+String(t+1):this.setup.slides[t].progressBarTitle,e}runSlideCallback(t,e){"function"==typeof t.callback&&t.callback(e,this.setup.settings,t.identifier)}addProgressBar(){const t=this.setup.$carousel.find(".carousel-item").length,e=Math.max(1,t),s=Math.round(100/e),i=this.setup.$carousel.closest(".modal").find(".modal-footer");if(this.setup.$carousel.data("initialStep",s).data("slideCount",e).data("realSlideCount",t).data("currentIndex",0).data("currentSlide",1),e>1){i.prepend($("<div />",{class:"progress"}));for(let t=0;t<this.setup.slides.length;++t){let e;e=0===t?"progress-bar first-step":t===this.setup.$carousel.data("slideCount")-1?"progress-bar last-step inactive":"progress-bar step inactive",i.find(".progress").append($("<div />",{role:"progressbar",class:e,"aria-valuemin":0,"aria-valuenow":s,"aria-valuemax":100}).width(s+"%").text(this.getProgressBarTitle(t)))}}}addButtonContainer(){this.setup.$carousel.closest(".modal").find(".modal-footer .btn").wrapAll('<div class="modal-btn-group" />')}generateSlides(){if(null!==this.setup.$carousel)return this.setup.$carousel;let t='<div class="carousel slide" data-bs-ride="false"><div class="carousel-inner" role="listbox">';for(let e=0;e<this.setup.slides.length;++e){const s=this.setup.slides[e];let i=s.content;"object"==typeof i&&(i=i.html()),t+='<div class="carousel-item" data-bs-slide="'+s.identifier+'" data-step="'+e+'">'+i+"</div>"}return t+="</div></div>",this.setup.$carousel=$(t),this.setup.$carousel.find(".carousel-item").first().addClass("active"),this.setup.$carousel}}let multistepWizardObject;try{window.opener&&window.opener.TYPO3&&window.opener.TYPO3.MultiStepWizard&&(multistepWizardObject=window.opener.TYPO3.MultiStepWizard),parent&&parent.window.TYPO3&&parent.window.TYPO3.MultiStepWizard&&(multistepWizardObject=parent.window.TYPO3.MultiStepWizard),top&&top.TYPO3&&top.TYPO3.MultiStepWizard&&(multistepWizardObject=top.TYPO3.MultiStepWizard)}catch(t){}multistepWizardObject||(multistepWizardObject=new MultiStepWizard,"undefined"!=typeof TYPO3&&(TYPO3.MultiStepWizard=multistepWizardObject));export default multistepWizardObject; \ No newline at end of file +import{SeverityEnum}from"@typo3/backend/enum/severity.js";import $ from"jquery";import{Carousel}from"bootstrap";import Modal from"@typo3/backend/modal.js";import Severity from"@typo3/backend/severity.js";import Icons from"@typo3/backend/icons.js";class MultiStepWizard{constructor(){this.setup={slides:[],settings:{},forceSelection:!0,$carousel:null,carousel:null},this.originalSetup=$.extend(!0,{},this.setup)}set(t,e){return this.setup.settings[t]=e,this}addSlide(t,e,s="",i=SeverityEnum.info,r,a){const l={identifier:t,title:e,content:s,severity:i,progressBarTitle:r,callback:a};return this.setup.slides.push(l),this}async addFinalProcessingSlide(t){t||(t=()=>{this.dismiss()});const e=await Icons.getIcon("spinner-circle",Icons.sizes.large,null,null),s=$("<div />",{class:"text-center"}).append(e);this.addSlide("final-processing-slide",top.TYPO3.lang["wizard.processing.title"],s[0].outerHTML,Severity.info,null,t)}show(){const t=this.generateSlides(),e=this.setup.slides[0];Modal.advanced({title:e.title,content:t,severity:e.severity,staticBackdrop:!0,buttons:[{text:top.TYPO3.lang["wizard.button.cancel"],active:!0,btnClass:"btn-default float-start",name:"cancel",trigger:()=>{this.getComponent().trigger("wizard-dismiss")}},{text:top.TYPO3.lang["wizard.button.prev"],btnClass:"btn-"+Severity.getCssClass(e.severity),name:"prev"},{text:top.TYPO3.lang["wizard.button.next"],btnClass:"btn-"+Severity.getCssClass(e.severity),name:"next"}],additionalCssClasses:["modal-multi-step-wizard"],callback:t=>{this.setup.carousel=new Carousel(t.querySelector(".carousel")),this.addButtonContainer(),this.addProgressBar(),this.initializeEvents()}}),this.getComponent().on("wizard-visible",(()=>{this.setup.forceSelection&&(this.lockPrevStep(),this.lockNextStep()),this.runSlideCallback(e,this.setup.$carousel.find(".carousel-item").first())})).on("wizard-dismissed",(()=>{this.setup=$.extend(!0,{},this.originalSetup)}))}getComponent(){return null===this.setup.$carousel&&this.generateSlides(),this.setup.$carousel}dismiss(){Modal.dismiss()}lockNextStep(){const t=this.setup.$carousel.closest(".modal").find('button[name="next"]');return t.prop("disabled",!0),t}unlockNextStep(){const t=this.setup.$carousel.closest(".modal").find('button[name="next"]');return t.prop("disabled",!1),t}lockPrevStep(){const t=this.setup.$carousel.closest(".modal").find('button[name="prev"]');return t.prop("disabled",!0),t}unlockPrevStep(){const t=this.setup.$carousel.closest(".modal").find('button[name="prev"]');return t.prop("disabled",!1),t}triggerStepButton(t){const e=this.setup.$carousel.closest(".modal").find('button[name="'+t+'"]');return e.length>0&&!0!==e.prop("disabled")&&e.get(0).click(),e}blurCancelStep(){const t=this.setup.$carousel.closest(".modal").find('button[name="cancel"]');return t.trigger("blur"),t}initializeEvents(){const t=this.setup.$carousel.closest(".modal");this.initializeSlideNextEvent(t),this.initializeSlidePrevEvent(t),this.setup.$carousel.get(0).addEventListener("slide.bs.carousel",(e=>{"left"===e.direction?this.nextSlideChanges(t):this.prevSlideChanges(t)})),this.setup.$carousel.get(0).addEventListener("slid.bs.carousel",(t=>{const e=this.setup.$carousel.data("currentIndex"),s=this.setup.slides[e];this.setup.forceSelection&&this.lockNextStep(),this.runSlideCallback(s,$(t.relatedTarget))}));const e=this.getComponent();e.on("wizard-dismiss",this.dismiss),Modal.currentModal.addEventListener("typo3-modal-hidden",(()=>{e.trigger("wizard-dismissed")})),Modal.currentModal.addEventListener("typo3-modal-shown",(()=>{e.trigger("wizard-visible")}))}initializeSlideNextEvent(t){t.find(".modal-footer").find('button[name="next"]').off().on("click",(()=>{this.setup.carousel.next()}))}initializeSlidePrevEvent(t){t.find(".modal-footer").find('button[name="prev"]').off().on("click",(()=>{this.setup.carousel.prev()}))}nextSlideChanges(t){this.initializeSlideNextEvent(t);const e=t.find(".modal-title"),s=t.find(".modal-footer"),i=this.setup.$carousel.data("currentSlide")+1,r=this.setup.$carousel.data("currentIndex"),a=r+1;t.find(".carousel-item:eq("+a+")").empty().append(this.setup.slides[a].content),e.text(this.setup.slides[a].title),this.unlockPrevStep(),this.setup.$carousel.data("currentSlide",i),this.setup.$carousel.data("currentIndex",a);const l=s.find(".progress-bar");l.eq(r).width("0%"),l.eq(a).width(this.setup.$carousel.data("initialStep")*i+"%").removeClass("inactive"),this.updateCurrentSeverity(t,r,a)}prevSlideChanges(t){this.initializeSlidePrevEvent(t);const e=t.find(".modal-title"),s=t.find(".modal-footer"),i=s.find('button[name="next"]'),r=this.setup.$carousel.data("currentSlide")-1,a=this.setup.$carousel.data("currentIndex"),l=a-1;t.find(".carousel-item:eq("+l+")").empty().append(this.setup.slides[l].content),e.text(this.setup.slides[l].title),l>0?this.unlockPrevStep():this.lockPrevStep(),this.setup.$carousel.data("currentSlide",r),this.setup.$carousel.data("currentIndex",l),s.find(".progress-bar.last-step").width(this.setup.$carousel.data("initialStep")+"%").text(this.getProgressBarTitle(this.setup.$carousel.data("slideCount")-1)),i.text(top.TYPO3.lang["wizard.button.next"]);const n=s.find(".progress-bar");n.eq(a).width(this.setup.$carousel.data("initialStep")+"%").addClass("inactive"),n.eq(l).width(this.setup.$carousel.data("initialStep")*r+"%").removeClass("inactive"),this.updateCurrentSeverity(t,a,l)}updateCurrentSeverity(t,e,s){t.find(".modal-footer").find('button[name="next"]').removeClass("btn-"+Severity.getCssClass(this.setup.slides[e].severity)).addClass("btn-"+Severity.getCssClass(this.setup.slides[s].severity)),t.removeClass("modal-severity-"+Severity.getCssClass(this.setup.slides[e].severity)).addClass("modal-severity-"+Severity.getCssClass(this.setup.slides[s].severity))}getProgressBarTitle(t){let e;return e=null===this.setup.slides[t].progressBarTitle?0===t?top.TYPO3.lang["wizard.progressStep.start"]:t>=this.setup.$carousel.data("slideCount")-1?top.TYPO3.lang["wizard.progressStep.finish"]:top.TYPO3.lang["wizard.progressStep"]+String(t+1):this.setup.slides[t].progressBarTitle,e}runSlideCallback(t,e){"function"==typeof t.callback&&t.callback(e,this.setup.settings,t.identifier)}addProgressBar(){const t=this.setup.$carousel.find(".carousel-item").length,e=Math.max(1,t),s=Math.round(100/e),i=this.setup.$carousel.closest(".modal").find(".modal-footer");if(this.setup.$carousel.data("initialStep",s).data("slideCount",e).data("realSlideCount",t).data("currentIndex",0).data("currentSlide",1),e>1){i.prepend($("<div />",{class:"progress"}));for(let t=0;t<this.setup.slides.length;++t){let e;e=0===t?"progress-bar first-step":t===this.setup.$carousel.data("slideCount")-1?"progress-bar last-step inactive":"progress-bar step inactive",i.find(".progress").append($("<div />",{role:"progressbar",class:e,"aria-valuemin":0,"aria-valuenow":s,"aria-valuemax":100}).width(s+"%").text(this.getProgressBarTitle(t)))}}}addButtonContainer(){this.setup.$carousel.closest(".modal").find(".modal-footer .btn").wrapAll('<div class="modal-btn-group" />')}generateSlides(){if(null!==this.setup.$carousel)return this.setup.$carousel;let t='<div class="carousel slide" data-bs-ride="false"><div class="carousel-inner" role="listbox">';for(let e=0;e<this.setup.slides.length;++e){const s=this.setup.slides[e];let i=s.content;"object"==typeof i&&(i=i.html()),t+='<div class="carousel-item" data-bs-slide="'+s.identifier+'" data-step="'+e+'">'+i+"</div>"}return t+="</div></div>",this.setup.$carousel=$(t),this.setup.$carousel.find(".carousel-item").first().addClass("active"),this.setup.$carousel}}let multistepWizardObject;try{window.opener&&window.opener.TYPO3&&window.opener.TYPO3.MultiStepWizard&&(multistepWizardObject=window.opener.TYPO3.MultiStepWizard),parent&&parent.window.TYPO3&&parent.window.TYPO3.MultiStepWizard&&(multistepWizardObject=parent.window.TYPO3.MultiStepWizard),top&&top.TYPO3&&top.TYPO3.MultiStepWizard&&(multistepWizardObject=top.TYPO3.MultiStepWizard)}catch(t){}multistepWizardObject||(multistepWizardObject=new MultiStepWizard,"undefined"!=typeof TYPO3&&(TYPO3.MultiStepWizard=multistepWizardObject));export default multistepWizardObject; \ No newline at end of file