diff --git a/core/lib/Drupal/Core/Ajax/CloseDialogCommand.php b/core/lib/Drupal/Core/Ajax/CloseDialogCommand.php index 1feeb0c8c0..d1ffb9c470 100644 --- a/core/lib/Drupal/Core/Ajax/CloseDialogCommand.php +++ b/core/lib/Drupal/Core/Ajax/CloseDialogCommand.php @@ -32,7 +32,7 @@ class CloseDialogCommand implements CommandInterface { * (optional) Whether to persist the dialog in the DOM or not. */ public function __construct($selector = NULL, $persist = FALSE) { - $this->selector = $selector ? $selector : '#drupal-modal'; + $this->selector = $selector ?: '#drupal-modal'; $this->persist = $persist; } diff --git a/core/lib/Drupal/Core/Ajax/CloseModalDialogCommand.php b/core/lib/Drupal/Core/Ajax/CloseModalDialogCommand.php index a9c3fe702e..afb48448de 100644 --- a/core/lib/Drupal/Core/Ajax/CloseModalDialogCommand.php +++ b/core/lib/Drupal/Core/Ajax/CloseModalDialogCommand.php @@ -14,9 +14,12 @@ class CloseModalDialogCommand extends CloseDialogCommand { * * @param bool $persist * (optional) Whether to persist the dialog in the DOM or not. + * @param null|string $selector + * (optional) Selector to scope the modal. Only modals of the same scope + * will be removed after opening a subsequent modal. */ - public function __construct($persist = FALSE) { - $this->selector = '#drupal-modal'; + public function __construct($persist = FALSE, $selector = NULL) { + $this->selector = $selector ?: '#drupal-modal'; $this->persist = $persist; } diff --git a/core/lib/Drupal/Core/Ajax/OpenModalDialogCommand.php b/core/lib/Drupal/Core/Ajax/OpenModalDialogCommand.php index a55b0eaf60..0aa684c5fb 100644 --- a/core/lib/Drupal/Core/Ajax/OpenModalDialogCommand.php +++ b/core/lib/Drupal/Core/Ajax/OpenModalDialogCommand.php @@ -29,10 +29,13 @@ class OpenModalDialogCommand extends OpenDialogCommand { * (optional) Custom settings that will be passed to the Drupal behaviors * on the content of the dialog. If left empty, the settings will be * populated automatically from the current request. + * @param null|string $selector + * (optional) Selector to scope the modal. Only modals of the same scope + * will be removed after opening a subsequent modal. */ - public function __construct($title, $content, array $dialog_options = [], $settings = NULL) { + public function __construct($title, $content, array $dialog_options = [], $settings = NULL, $selector = NULL) { $dialog_options['modal'] = TRUE; - parent::__construct('#drupal-modal', $title, $content, $dialog_options, $settings); + parent::__construct($selector ?: '#drupal-modal', $title, $content, $dialog_options, $settings); } } diff --git a/core/misc/dialog/dialog.js b/core/misc/dialog/dialog.js index 3ec4218282..4b95008bad 100644 --- a/core/misc/dialog/dialog.js +++ b/core/misc/dialog/dialog.js @@ -84,14 +84,15 @@ } function closeDialog(value) { - $(window).trigger('dialog:beforeclose', [dialog, $element]); + const settings = $element.dialog('instance'); + $(window).trigger('dialog:beforeclose', [dialog, $element, settings]); // Unlocks the body when the dialog closes. bodyScrollLock.clearBodyLocks(); $element.dialog('close'); dialog.returnValue = value; dialog.open = false; - $(window).trigger('dialog:afterclose', [dialog, $element]); + $(window).trigger('dialog:afterclose', [dialog, $element, settings]); } dialog.show = () => { diff --git a/core/modules/ckeditor5/ckeditor5.ckeditor5.yml b/core/modules/ckeditor5/ckeditor5.ckeditor5.yml index a553162fc3..c88c4f4eb3 100644 --- a/core/modules/ckeditor5/ckeditor5.ckeditor5.yml +++ b/core/modules/ckeditor5/ckeditor5.ckeditor5.yml @@ -801,6 +801,7 @@ media_library_mediaLibrary: dialogSettings: height: 75% dialogClass: media-library-widget-modal + selector: '#modal-media-library' drupal: label: Media Library elements: false diff --git a/core/modules/ckeditor5/js/ckeditor5.js b/core/modules/ckeditor5/js/ckeditor5.js index e204023d7a..001ba11fb3 100644 --- a/core/modules/ckeditor5/js/ckeditor5.js +++ b/core/modules/ckeditor5/js/ckeditor5.js @@ -588,11 +588,11 @@ */ Drupal.ckeditor5 = { /** - * Variable storing the current dialog's save callback. + * Set storing the dialog save callbacks keyed by dialog selector. * - * @type {?function} + * @type {Map} */ - saveCallback: null, + saveCallback: new Map(), /** * Open a dialog for a Drupal-based plugin. @@ -631,7 +631,10 @@ ckeditorAjaxDialog.execute(); // Store the save callback to be executed when this dialog is closed. - Drupal.ckeditor5.saveCallback = saveCallback; + Drupal.ckeditor5.saveCallback.set( + dialogSettings.selector || '#drupal-modal', + saveCallback, + ); }, }; @@ -669,16 +672,17 @@ }); // Respond to dialogs that are saved, sending data back to CKEditor. - $(window).on('editor:dialogsave', (e, values) => { - if (Drupal.ckeditor5.saveCallback) { - Drupal.ckeditor5.saveCallback(values); + $(window).on('editor:dialogsave', (e, values, selector) => { + if (Drupal.ckeditor5.saveCallback.has(selector)) { + Drupal.ckeditor5.saveCallback.get(selector)(values); } }); // Respond to dialogs that are closed, removing the current save handler. - $(window).on('dialog:afterclose', () => { - if (Drupal.ckeditor5.saveCallback) { - Drupal.ckeditor5.saveCallback = null; + $(window).on('dialog:afterclose', (e, dialog, element, dialogSettings) => { + const selector = dialogSettings.options.selector || '#drupal-modal'; + if (Drupal.ckeditor5.saveCallback.has(selector)) { + Drupal.ckeditor5.saveCallback.delete(selector); } }); })(Drupal, Drupal.debounce, CKEditor5, jQuery, once); diff --git a/core/modules/editor/js/editor.dialog.js b/core/modules/editor/js/editor.dialog.js index 5cd37960da..b0131d2063 100644 --- a/core/modules/editor/js/editor.dialog.js +++ b/core/modules/editor/js/editor.dialog.js @@ -29,6 +29,9 @@ response, status, ) { - $(window).trigger('editor:dialogsave', [response.values]); + $(window).trigger('editor:dialogsave', [ + response.values, + response.selector, + ]); }; })(jQuery, Drupal); diff --git a/core/modules/media_library/src/Form/AddFormBase.php b/core/modules/media_library/src/Form/AddFormBase.php index 05f2596c03..9f56b05d5d 100644 --- a/core/modules/media_library/src/Form/AddFormBase.php +++ b/core/modules/media_library/src/Form/AddFormBase.php @@ -778,7 +778,7 @@ public function updateWidget(array &$form, FormStateInterface $form_state) { return $this->openerResolver->get($state) ->getSelectionResponse($state, $media_ids) - ->addCommand(new CloseDialogCommand()); + ->addCommand(new CloseDialogCommand('#modal-media-library', FALSE)); } /** diff --git a/core/modules/media_library/src/MediaLibraryEditorOpener.php b/core/modules/media_library/src/MediaLibraryEditorOpener.php index ab5674c875..f8b1428d86 100644 --- a/core/modules/media_library/src/MediaLibraryEditorOpener.php +++ b/core/modules/media_library/src/MediaLibraryEditorOpener.php @@ -70,7 +70,7 @@ public function getSelectionResponse(MediaLibraryState $state, array $selected_i 'data-entity-uuid' => $selected_media->uuid(), ], ]; - $response->addCommand(new EditorDialogSave($values)); + $response->addCommand(new EditorDialogSave($values, '#modal-media-library')); return $response; } diff --git a/core/modules/media_library/src/MediaLibraryUiBuilder.php b/core/modules/media_library/src/MediaLibraryUiBuilder.php index 47143fcc25..20cb4b70a6 100644 --- a/core/modules/media_library/src/MediaLibraryUiBuilder.php +++ b/core/modules/media_library/src/MediaLibraryUiBuilder.php @@ -118,6 +118,7 @@ public function buildUi(MediaLibraryState $state = NULL) { } else { return [ + '#modal_selector' => '#modal-media-library', '#theme' => 'media_library_wrapper', '#attributes' => [ 'id' => 'media-library-wrapper', diff --git a/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php b/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php index 260e76d478..973592b87e 100644 --- a/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php +++ b/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php @@ -825,7 +825,7 @@ public static function openMediaLibrary(array $form, FormStateInterface $form_st $library_ui = \Drupal::service('media_library.ui_builder')->buildUi($triggering_element['#media_library_state']); $dialog_options = MediaLibraryUiBuilder::dialogOptions(); return (new AjaxResponse()) - ->addCommand(new OpenModalDialogCommand($dialog_options['title'], $library_ui, $dialog_options)); + ->addCommand(new OpenModalDialogCommand($dialog_options['title'], $library_ui, $dialog_options, NULL, '#modal-media-library')); } /** diff --git a/core/modules/media_library/src/Plugin/views/field/MediaLibrarySelectForm.php b/core/modules/media_library/src/Plugin/views/field/MediaLibrarySelectForm.php index 4f5875e1dd..f7e5295ffc 100644 --- a/core/modules/media_library/src/Plugin/views/field/MediaLibrarySelectForm.php +++ b/core/modules/media_library/src/Plugin/views/field/MediaLibrarySelectForm.php @@ -3,7 +3,7 @@ namespace Drupal\media_library\Plugin\views\field; use Drupal\Core\Ajax\AjaxResponse; -use Drupal\Core\Ajax\CloseDialogCommand; +use Drupal\Core\Ajax\CloseModalDialogCommand; use Drupal\Core\Ajax\MessageCommand; use Drupal\Core\Form\FormBuilderInterface; use Drupal\Core\Form\FormStateInterface; @@ -151,7 +151,7 @@ public static function updateWidget(array &$form, FormStateInterface $form_state return \Drupal::service('media_library.opener_resolver') ->get($state) ->getSelectionResponse($state, $selected_ids) - ->addCommand(new CloseDialogCommand()); + ->addCommand(new CloseModalDialogCommand(FALSE, '#modal-media-library')); } /** diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/WidgetUploadTest.php b/core/modules/media_library/tests/src/FunctionalJavascript/WidgetUploadTest.php index 223c98b0a2..e9900eb16a 100644 --- a/core/modules/media_library/tests/src/FunctionalJavascript/WidgetUploadTest.php +++ b/core/modules/media_library/tests/src/FunctionalJavascript/WidgetUploadTest.php @@ -745,4 +745,78 @@ public function testWidgetUploadAdvancedUi() { $assert_session->fieldValueEquals('Alternative text', $filenames[0], $media_item_one); } + /** + * Tests if the widget is working correctly when opened from a modal. + */ + public function testWidgetInModalContext() { + $page = $this->getSession()->getPage(); + + foreach ($this->getTestFiles('image') as $image) { + $extension = pathinfo($image->filename, PATHINFO_EXTENSION); + if ($extension === 'png') { + $png_image = $image; + } + elseif ($extension === 'jpg') { + $jpg_image = $image; + } + } + + if (!isset($png_image) || !isset($jpg_image)) { + $this->fail('Expected test files not present.'); + } + + // Create a user that can create media for all media types. + $user = $this->drupalCreateUser([ + 'access administration pages', + 'access content', + 'create basic_page content', + 'create media', + 'view media', + ]); + $this->drupalLogin($user); + $this->drupalGet('/media-library-test-modal'); + $assert_session = $this->assertSession(); + $this->click('.modal-basic_page'); + $assert_session->waitForElement('css', '.ui-dialog-content'); + + $file_storage = $this->container->get('entity_type.manager')->getStorage('file'); + /** @var \Drupal\Core\File\FileSystemInterface $file_system */ + $file_system = $this->container->get('file_system'); + + // Add to the twin media field. + // Add to the twin media field. + $this->openMediaLibraryForField('field_twin_media'); + + // Assert the upload form is now visible for default tab type_three. + $assert_session->elementExists('css', '.js-media-library-add-form'); + $assert_session->fieldExists('Add files'); + + // Assert we can upload a file to the default tab type_three. + $assert_session->elementNotExists('css', '.js-media-library-add-form[data-input]'); + $this->addMediaFileToField('Add files', $this->container->get('file_system')->realpath($png_image->uri)); + $this->assertMediaAdded(); + $assert_session->elementExists('css', '.js-media-library-add-form[data-input]'); + // We do not have pre-selected items, so the container should not be added + // to the form. + $assert_session->pageTextNotContains('Additional selected media'); + // Files are temporary until the form is saved. + $files = $file_storage->loadMultiple(); + $file = array_pop($files); + $this->assertSame('public://type-three-dir', $file_system->dirname($file->getFileUri())); + $this->assertTrue($file->isTemporary()); + // Assert the revision_log_message field is not shown. + $upload_form = $assert_session->elementExists('css', '.js-media-library-add-form'); + $assert_session->fieldNotExists('Revision log message', $upload_form); + // Assert the name field contains the filename and the alt text is required. + $assert_session->fieldValueEquals('Name', $png_image->filename); + $page->fillField('Alternative text', $this->randomString()); + $buttons = $this->assertElementExistsAfterWait('css', '.media-library-widget-modal .ui-dialog-buttonpane'); + + $buttons->pressButton('Save'); + $this->waitForText('Insert selected'); + $buttons->pressButton('Insert selected'); + $assert_session->waitForElementVisible('css', '#field_twin_media-media-library-wrapper .js-media-library-selection .js-media-library-item'); + + } + }