diff --git a/core/modules/media_library/js/media_library.widget.es6.js b/core/modules/media_library/js/media_library.widget.es6.js
index b42ef705..80bacbd1 100644
--- a/core/modules/media_library/js/media_library.widget.es6.js
+++ b/core/modules/media_library/js/media_library.widget.es6.js
@@ -68,6 +68,25 @@
},
};
+ /**
+ * Allow users to edit media library items inside a modal.
+ */
+ Drupal.behaviors.MediaLibraryWidgetEditItem = {
+ attach: function attach() {
+ $('.media-library-widget .edit-media')
+ .once('media-library-edit')
+ .on('click', function () {
+ // Remove any "selected-media" classes.
+ $(this)
+ .closest('.media-library-selection')
+ .find('.selected-media')
+ .removeClass('selected-media');
+ // Mark the media item as selected to render it properly when submitting an ajax media edit request.
+ $(this).parent().find('article').addClass('selected-media');
+ });
+ },
+ };
+
/**
* Disable the open button when the user is not allowed to add more items.
*
diff --git a/core/modules/media_library/js/media_library.widget.js b/core/modules/media_library/js/media_library.widget.js
index 0fe2475e..3b321297 100644
--- a/core/modules/media_library/js/media_library.widget.js
+++ b/core/modules/media_library/js/media_library.widget.js
@@ -35,6 +35,14 @@
$('.js-media-library-item-weight', context).once('media-library-toggle').parent().hide();
}
};
+ Drupal.behaviors.MediaLibraryWidgetEditItem = {
+ attach: function attach() {
+ $('.media-library-widget .edit-media').once('media-library-edit').on('click', function () {
+ $(this).closest('.media-library-selection').find('.selected-media').removeClass('selected-media');
+ $(this).parent().find('article').addClass('selected-media');
+ });
+ }
+ };
Drupal.behaviors.MediaLibraryWidgetDisableButton = {
attach: function attach(context) {
$('.js-media-library-open-button[data-disabled-focus="true"]', context).once('media-library-disable').each(function () {
diff --git a/core/modules/media_library/media_library.module b/core/modules/media_library/media_library.module
index 1b6beb87..6bc4b269 100644
--- a/core/modules/media_library/media_library.module
+++ b/core/modules/media_library/media_library.module
@@ -5,11 +5,16 @@
* Contains hook implementations for the media_library module.
*/
+use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\Ajax\CloseModalDialogCommand;
+use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
+use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
@@ -28,6 +33,7 @@ use Drupal\views\Plugin\views\cache\CachePluginBase;
use Drupal\views\ViewExecutable;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Component\Serialization\Json;
+use Drupal\Core\Messenger\MessengerInterface;
/**
* Implements hook_help().
@@ -224,6 +230,78 @@ function media_library_views_post_render(ViewExecutable $view, &$output, CachePl
}
}
+/**
+ * Implements hook_form_FORM_ID_alter() for media_form forms.
+ */
+function media_library_form_media_form_alter(array &$form, FormStateInterface $form_state) {
+ // Make the media edit form work inside modal dialogs.
+ $form_object = $form_state->getFormObject();
+ if (is_object($form_object) && $form_object->getOperation() === 'edit') {
+ $wrapper_format = \Drupal::request()->query->get(MainContentViewSubscriber::WRAPPER_FORMAT);
+ if ($wrapper_format === 'drupal_modal' || ($wrapper_format === 'drupal_ajax')) {
+ foreach (Element::children($form['actions']) as $key) {
+ if ($key === 'submit') {
+ // The default name is 'op', but we change it on purpose so that it is
+ // easier to detect our modified AJAX call above.
+ $form['actions'][$key]['#name'] = 'media_library_ajax_submit';
+ $form['actions'][$key]['#ajax'] = [
+ 'callback' => 'media_library_media_form_ajax_submit',
+ ];
+
+ // Prevent the form that opened the modal dialog to refocus to the
+ // 'Save' button at the bottom of the form.
+ $form['actions'][$key]['#attributes']['data-disable-refocus'] = 'true';
+
+ // Reattach dialog AJAX library.
+ $form['#attached']['library'][] = 'core/drupal.dialog.ajax';
+
+ // The data-drupal-selector needs to be the same between the various
+ // AJAX requests. A bug in \Drupal\Core\Form\FormBuilder prevents that
+ // from happening unless $form['#id'] is also the same. Normally, #id
+ // is set to a unique HTML ID via Html::getUniqueId(), but here we
+ // bypass that in order to work around the data-drupal-selector bug.
+ // This is okay so long as we assume that this form only ever occurs
+ // once on a page.
+ // @todo: Remove once https://www.drupal.org/node/2897377 is fixed.
+ $form['#id'] = Html::getId($form_state->getBuildInfo()['form_id']);
+ }
+ else {
+ // We just want to allow the save action for now. Just hide other
+ // actions like the delete action.
+ $form['actions'][$key]['#access'] = FALSE;
+ }
+ }
+ }
+ }
+}
+
+/**
+ * AJAX callback for media edit form when form is shown inside modal dialog.
+ *
+ * @ingroup form
+ */
+function media_library_media_form_ajax_submit(array &$form, FormStateInterface $form_state) {
+ $response = new AjaxResponse();
+ if (!$form_state->hasAnyErrors()) {
+ $media = $form_state->getFormObject()->getEntity();
+ $render_array = \Drupal::entityTypeManager()->getViewBuilder('media')->view($media, 'media_library');
+ $html = \Drupal::service('renderer')->render($render_array);
+ $response->addCommand(new CloseModalDialogCommand());
+ $response->addCommand(new ReplaceCommand('.selected-media', $html));
+
+ // Remove status messages when editing medias.
+ \Drupal::messenger()->deleteByType(MessengerInterface::TYPE_STATUS);
+ }
+ else {
+ $form['status_messages'] = [
+ '#type' => 'status_messages',
+ '#weight' => -1000,
+ ];
+ $response->addCommand(new ReplaceCommand('[data-drupal-selector="' . $form['#attributes']['data-drupal-selector'] . '"]', $form));
+ }
+ return $response;
+}
+
/**
* Implements hook_preprocess_media().
*/
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 1d890c97..60f72e0c 100644
--- a/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php
+++ b/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php
@@ -362,6 +362,52 @@ class MediaLibraryWidget extends WidgetBase implements TrustedCallbackInterface
];
}
+ $cardinality_unlimited = ($element['#cardinality'] === FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
+ $remaining = $element['#cardinality'] - count($referenced_entities);
+
+ // Inform the user of how many items are remaining.
+ if (!$cardinality_unlimited) {
+ if ($remaining) {
+ $cardinality_message = $this->formatPlural($remaining, 'One media item remaining.', '@count media items remaining.');
+ }
+ else {
+ $cardinality_message = $this->t('The maximum number of media items have been selected.');
+ }
+
+ // Add a line break between the field message and the cardinality message.
+ if (!empty($element['#description'])) {
+ $element['#description'] .= '
';
+ }
+ $element['#description'] .= $cardinality_message;
+ }
+
+ // Create a new media library URL with the correct state parameters.
+ $selected_type_id = reset($allowed_media_type_ids);
+ $remaining = $cardinality_unlimited ? FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED : $remaining;
+ // This particular media library opener needs some extra metadata for its
+ // \Drupal\media_library\MediaLibraryOpenerInterface::getSelectionResponse()
+ // to be able to target the element whose 'data-media-library-widget-value'
+ // attribute is the same as $field_widget_id. The entity ID, entity type ID,
+ // bundle, field name are used for access checking.
+ $entity = $items->getEntity();
+ $opener_parameters = [
+ 'field_widget_id' => $field_widget_id,
+ 'entity_type_id' => $entity->getEntityTypeId(),
+ 'bundle' => $entity->bundle(),
+ 'field_name' => $field_name,
+ ];
+ // Only add the entity ID when we actually have one. The entity ID needs to
+ // be a string to ensure that the media library state generates its
+ // tamper-proof hash in a consistent way.
+ if (!$entity->isNew()) {
+ $opener_parameters['entity_id'] = (string) $entity->id();
+
+ if ($entity->getEntityType()->isRevisionable()) {
+ $opener_parameters['revision_id'] = (string) $entity->getRevisionId();
+ }
+ }
+ $state = MediaLibraryState::create('media_library.opener.field_widget', $allowed_media_type_ids, $selected_type_id, $remaining, $opener_parameters);
+
$element['selection'] = [
'#type' => 'container',
'#theme_wrappers' => [
@@ -396,6 +442,7 @@ class MediaLibraryWidget extends WidgetBase implements TrustedCallbackInterface
'#type' => 'submit',
'#name' => $field_name . '-' . $delta . '-media-library-remove-button' . $id_suffix,
'#value' => $this->t('Remove'),
+ '#weight' => -10,
'#media_id' => $media_item->id(),
'#attributes' => [
'aria-label' => $this->t('Remove @label', ['@label' => $media_item->label()]),
@@ -412,6 +459,19 @@ class MediaLibraryWidget extends WidgetBase implements TrustedCallbackInterface
// Prevent errors in other widgets from preventing removal.
'#limit_validation_errors' => $limit_validation_errors,
],
+ 'edit_button' => [
+ '#type' => 'button',
+ '#value' => $this->t('Edit'),
+ '#weight' => -5,
+ '#access' => $media_item->access('update'),
+ '#attributes' => [
+ 'type' => 'button',
+ 'class' => ['edit-media', 'use-ajax'],
+ 'href' => $media_item->toUrl('edit-form', ['query' => $state->all()])->toString(),
+ 'data-dialog-type' => 'modal',
+ 'data-dialog-options' => '{"width":"80%"}',
+ ],
+ ],
// @todo Make the view mode configurable in https://www.drupal.org/project/drupal/issues/2971209
'rendered_entity' => $view_builder->view($media_item, 'media_library'),
'target_id' => [
@@ -434,52 +494,6 @@ class MediaLibraryWidget extends WidgetBase implements TrustedCallbackInterface
];
}
- $cardinality_unlimited = ($element['#cardinality'] === FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
- $remaining = $element['#cardinality'] - count($referenced_entities);
-
- // Inform the user of how many items are remaining.
- if (!$cardinality_unlimited) {
- if ($remaining) {
- $cardinality_message = $this->formatPlural($remaining, 'One media item remaining.', '@count media items remaining.');
- }
- else {
- $cardinality_message = $this->t('The maximum number of media items have been selected.');
- }
-
- // Add a line break between the field message and the cardinality message.
- if (!empty($element['#description'])) {
- $element['#description'] .= '
';
- }
- $element['#description'] .= $cardinality_message;
- }
-
- // Create a new media library URL with the correct state parameters.
- $selected_type_id = reset($allowed_media_type_ids);
- $remaining = $cardinality_unlimited ? FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED : $remaining;
- // This particular media library opener needs some extra metadata for its
- // \Drupal\media_library\MediaLibraryOpenerInterface::getSelectionResponse()
- // to be able to target the element whose 'data-media-library-widget-value'
- // attribute is the same as $field_widget_id. The entity ID, entity type ID,
- // bundle, field name are used for access checking.
- $entity = $items->getEntity();
- $opener_parameters = [
- 'field_widget_id' => $field_widget_id,
- 'entity_type_id' => $entity->getEntityTypeId(),
- 'bundle' => $entity->bundle(),
- 'field_name' => $field_name,
- ];
- // Only add the entity ID when we actually have one. The entity ID needs to
- // be a string to ensure that the media library state generates its
- // tamper-proof hash in a consistent way.
- if (!$entity->isNew()) {
- $opener_parameters['entity_id'] = (string) $entity->id();
-
- if ($entity->getEntityType()->isRevisionable()) {
- $opener_parameters['revision_id'] = (string) $entity->getRevisionId();
- }
- }
- $state = MediaLibraryState::create('media_library.opener.field_widget', $allowed_media_type_ids, $selected_type_id, $remaining, $opener_parameters);
-
// Add a button that will load the Media library in a modal using AJAX.
$element['open_button'] = [
'#type' => 'button',