Skip to content
Snippets Groups Projects
Commit e0ae9636 authored by Jess's avatar Jess
Browse files

Issue #3082690 by phenaproxima, bnjmnm, oknate, JeroenT, effulgentsia, xjm,...

Issue #3082690 by phenaproxima, bnjmnm, oknate, JeroenT, effulgentsia, xjm, lauriii, webchick, seanB, andrewmacpherson, Wim Leers, DyanneNova, Gábor Hojtsy, cboyden, peterx, rainbreaw, jan.stoeckler, shaal, annagaz, FeyP, chr.fritsch, marcoscano, samuel.mortenson, Berdir, webflo: Mark Media Library as a stable core module
parent eb99b6b5
No related branches found
No related tags found
6 merge requests!7452Issue #1797438. HTML5 validation is preventing form submit and not fully...,!1012Issue #3226887: Hreflang on non-canonical content pages,!789Issue #3210310: Adjust Database API to remove deprecated Drupal 9 code in Drupal 10,!596Issue #3046532: deleting an entity reference field, used in a contextual view, makes the whole site unrecoverable,!496Issue #2463967: Use .user.ini file for PHP settings,!144Issue #2666286: Clean up menu_ui to conform to Drupal coding standards
Showing
with 318 additions and 336 deletions
......@@ -283,6 +283,10 @@ Media
- Christian Fritsch 'chr.fritsch' https://www.drupal.org/u/chr.fritsch
- Adam Globus-Hoenich 'phenaproxima' https://www.drupal.org/u/phenaproxima
Media Library
- Sean Blommaert 'seanB' https://www.drupal.org/u/seanb
- Adam Globus-Hoenich 'phenaproxima' https://www.drupal.org/u/phenaproxima
Menu
- Daniel Wehner 'dawehner' https://www.drupal.org/u/dawehner
- Peter Wolanin 'pwolanin' https://www.drupal.org/u/pwolanin
......
......@@ -73,7 +73,7 @@ display:
type: default
options:
grouping: { }
row_class: 'media-library-item media-library-item--grid js-media-library-item js-click-to-select'
row_class: ''
default_row_class: true
row:
type: fields
......@@ -120,7 +120,7 @@ display:
preserve_tags: ''
html: false
element_type: ''
element_class: js-click-to-select-checkbox media-library-item__click-to-select-checkbox
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: false
......@@ -173,7 +173,7 @@ display:
preserve_tags: ''
html: false
element_type: ''
element_class: media-library-item__content
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: false
......@@ -468,7 +468,7 @@ display:
relationships: { }
display_extenders: { }
use_ajax: true
css_class: 'media-library-view js-media-library-view'
css_class: ''
cache_metadata:
max-age: 0
contexts:
......@@ -525,7 +525,7 @@ display:
preserve_tags: ''
html: false
element_type: ''
element_class: js-click-to-select-checkbox media-library-item__click-to-select-checkbox
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: false
......@@ -627,7 +627,7 @@ display:
trim_whitespace: false
alt: 'Edit {{ name }}'
rel: ''
link_class: media-library-item__edit
link_class: ''
prefix: ''
suffix: ''
target: ''
......@@ -680,7 +680,7 @@ display:
trim_whitespace: false
alt: 'Delete {{ name }}'
rel: ''
link_class: media-library-item__remove
link_class: ''
prefix: ''
suffix: ''
target: ''
......@@ -749,7 +749,7 @@ display:
preserve_tags: ''
html: false
element_type: ''
element_class: media-library-item__content
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: false
......@@ -786,10 +786,10 @@ display:
display_extenders: { }
path: admin/content/media-widget
fields:
rendered_entity:
id: rendered_entity
media_library_select_form:
id: media_library_select_form
table: media
field: rendered_entity
field: media_library_select_form
relationship: none
group_type: group
admin_label: ''
......@@ -823,7 +823,7 @@ display:
preserve_tags: ''
html: false
element_type: ''
element_class: media-library-item__content
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: false
......@@ -834,13 +834,12 @@ display:
hide_empty: false
empty_zero: false
hide_alter_empty: true
view_mode: media_library
entity_type: media
plugin_id: rendered_entity
media_library_select_form:
id: media_library_select_form
plugin_id: media_library_select_form
rendered_entity:
id: rendered_entity
table: media
field: media_library_select_form
field: rendered_entity
relationship: none
group_type: group
admin_label: ''
......@@ -879,14 +878,15 @@ display:
element_label_class: ''
element_label_colon: false
element_wrapper_type: ''
element_wrapper_class: js-click-to-select-checkbox media-library-item__click-to-select-checkbox
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
view_mode: media_library
entity_type: media
plugin_id: media_library_select_form
plugin_id: rendered_entity
defaults:
fields: false
access: false
......@@ -1086,7 +1086,7 @@ display:
label: 'Table'
plugin_id: display_link
empty: true
css_class: 'media-library-view js-media-library-view media-library-view--widget'
css_class: ''
rendering_language: '***LANGUAGE_language_interface***'
cache_metadata:
max-age: -1
......@@ -1131,7 +1131,7 @@ display:
relationship: none
entity_type: media
plugin_id: media_library_select_form
element_wrapper_class: js-click-to-select-checkbox media-library-item__click-to-select-checkbox
element_wrapper_class: ''
element_class: ''
thumbnail__target_id:
id: thumbnail__target_id
......@@ -1374,7 +1374,7 @@ display:
label: 'Table'
plugin_id: display_link
empty: true
css_class: 'media-library-view js-media-library-view media-library-view--widget'
css_class: ''
rendering_language: '***LANGUAGE_language_interface***'
cache_metadata:
max-age: -1
......
/**
* @file media_library.module.css
*/
/**
* By default, the dialog is too narrow to be usable.
* @see Drupal.ckeditor.openDialog()
*/
.ui-dialog--narrow.media-library-widget-modal {
max-width: 75%;
}
.media-library-wrapper {
display: flex;
}
.media-library-menu {
margin: 0;
padding: 0;
}
/* @todo Use a class instead of the li element.
https://www.drupal.org/project/drupal/issues/3029227 */
.media-library-menu li {
padding: 0;
list-style: none;
}
.media-library-views-form > .form-actions {
flex-basis: 100%;
}
.media-library-views-form,
.media-library-selection,
.media-library-add-form__selected-media .details-wrapper,
.media-library-views-form__bulk_form,
.media-library-view .form--inline {
display: flex;
flex-wrap: wrap;
}
.media-library-views-form__header {
flex-basis: 100%;
}
.media-library-item {
position: relative;
}
.media-library-item__click-to-select-trigger {
overflow: hidden;
height: 100%;
cursor: pointer;
}
.media-library-view .form-actions {
align-self: flex-end;
}
.media-library-item__click-to-select-checkbox {
position: absolute;
z-index: 1;
top: 16px;
left: 16px; /* LTR */
display: block;
}
[dir="rtl"] .media-library-item__click-to-select-checkbox {
right: 16px;
left: auto;
}
.media-library-item__status {
position: absolute;
top: 40px;
left: 5px; /* LTR */
pointer-events: none;
}
[dir="rtl"] .media-library-item__status {
right: 5px;
left: auto;
}
.media-library-select-all {
flex-basis: 100%;
width: 100%;
}
.media-library-view--widget .media-library-select-all {
display: none;
}
.media-library-item--disabled {
pointer-events: none;
}
.media-library-selection .media-library-item__preview {
cursor: move;
}
.media-library-widget-modal .ui-dialog-buttonpane {
display: flex;
align-items: center;
}
.media-library-widget-modal .ui-dialog-buttonpane .form-actions {
flex: 1;
}
@media screen and (max-width: 600px) {
.media-library-view .form-actions {
flex-basis: 100%;
}
}
/* @todo Remove in https://www.drupal.org/project/drupal/issues/3064914 */
.views-live-preview .media-library-view div.views-row + div.views-row {
margin-top: 0;
}
......@@ -12,9 +12,10 @@
*/
Drupal.behaviors.MediaLibrarySelectAll = {
attach(context) {
const $view = $('.js-media-library-view', context).once(
'media-library-select-all',
);
const $view = $(
'.js-media-library-view[data-view-display-id="page"]',
context,
).once('media-library-select-all');
if ($view.length && $view.find('.js-media-library-item').length) {
const $checkbox = $(Drupal.theme('checkbox')).on(
'click',
......
......@@ -8,7 +8,7 @@
(function ($, Drupal) {
Drupal.behaviors.MediaLibrarySelectAll = {
attach: function attach(context) {
var $view = $('.js-media-library-view', context).once('media-library-select-all');
var $view = $('.js-media-library-view[data-view-display-id="page"]', context).once('media-library-select-all');
if ($view.length && $view.find('.js-media-library-item').length) {
var $checkbox = $(Drupal.theme('checkbox')).on('click', function (_ref) {
var currentTarget = _ref.currentTarget;
......
name: 'Media Library'
type: module
description: 'Enhances the media list with additional features to more easily find and use existing media items.'
package: Core (Experimental)
package: Core
version: VERSION
dependencies:
- drupal:media
......
style:
version: VERSION
css:
component:
css/media_library.module.css: {}
theme:
css/media_library.theme.css: {}
click_to_select:
version: VERSION
js:
......@@ -21,7 +13,6 @@ view:
dependencies:
- core/drupal.announce
- core/drupal.checkbox
- media_library/style
- media_library/click_to_select
widget:
......@@ -31,7 +22,6 @@ widget:
dependencies:
- core/drupal.dialog.ajax
- core/jquery.once
- media_library/style
- core/sortable
ui:
......
......@@ -24,7 +24,6 @@
use Drupal\media_library\Form\FileUploadForm;
use Drupal\media_library\Form\OEmbedForm;
use Drupal\media_library\MediaLibraryState;
use Drupal\views\Form\ViewsForm;
use Drupal\views\Plugin\views\cache\CachePluginBase;
use Drupal\views\ViewExecutable;
use Drupal\Core\StringTranslation\TranslatableMarkup;
......@@ -60,11 +59,21 @@ function media_library_help($route_name, RouteMatchInterface $route_match) {
$output .= '</dl>';
$output .= '<h3>' . t('Customize') . '</h3>';
$output .= '<ul>';
$output .= '<li>';
if (\Drupal::moduleHandler()->moduleExists('views_ui') && \Drupal::currentUser()->hasPermission('administer views')) {
$output .= t('Both the table-style and grid-style interfaces are regular views and can be customized via the <a href=":views-ui">Views UI</a>, including sorting and filtering. This is the case for both the administration page and the modal dialog.', [
':views_ui' => Url::fromRoute('entity.view.collection')->toString(),
]);
}
else {
$output .= t('Both the table-style and grid-style interfaces are regular views and can be customized via the Views UI, including sorting and filtering. This is the case for both the administration page and the modal dialog.');
}
$output .= '</li>';
$output .= '<li>' . t('Both the table-style and grid-style interfaces are regular views and can be customized via the Views UI, including sorting and filtering. This is the case for both the administration page and the modal dialog.') . '</li>';
$output .= '<li>' . t('In the grid-style interface, which fields are displayed (including which image style is used for images) can be customized by configuring the "Media library" view mode for each of your <a href=":media-types">media types</a>. The thumbnail images in the grid-style interface can be customized by configuring the "Media Library thumbnail (220×220)" image style.', [
$output .= '<li>' . t('In the grid-style interface, the fields that are displayed (including which image style is used for images) can be customized by configuring the "Media library" view mode for each of your <a href=":media-types">media types</a>. The thumbnail images in the grid-style interface can be customized by configuring the "Media Library thumbnail (220×220)" image style.', [
':media-types' => Url::fromRoute('entity.media_type.collection')->toString(),
]) . '</li>';
$output .= '<li>' . t('When adding new media items within the modal dialog, which fields are displayed can be customized by configuring the "Media library" form mode for each of your <a href=":media-types">media types</a>.', [
$output .= '<li>' . t('When adding new media items within the modal dialog, the fields that are displayed can be customized by configuring the "Media library" form mode for each of your <a href=":media-types">media types</a>.', [
':media-types' => Url::fromRoute('entity.media_type.collection')->toString(),
]) . '</li>';
$output .= '</ul>';
......@@ -91,9 +100,84 @@ function media_library_theme() {
'media__media_library' => [
'base hook' => 'media',
],
'media_library_wrapper' => [
'render element' => 'element',
],
'media_library_item' => [
'render element' => 'element',
],
];
}
/**
* Prepares variables for the media library modal dialog.
*
* Default template: media-library-wrapper.html.twig.
*
* @param array $variables
* An associative array containing:
* - element: An associative array containing the properties of the element.
* Properties used: #menu, #content.
*/
function template_preprocess_media_library_wrapper(array &$variables) {
$variables['menu'] = &$variables['element']['menu'];
$variables['content'] = &$variables['element']['content'];
}
/**
* Prepares variables for a selected media item.
*
* Default template: media-library-item.html.twig.
*
* @param array $variables
* An associative array containing:
* - element: An associative array containing the properties and children of
* the element.
*/
function template_preprocess_media_library_item(array &$variables) {
$element = &$variables['element'];
foreach (Element::children($element) as $key) {
$variables['content'][$key] = $element[$key];
}
}
/**
* Implements hook_views_pre_render().
*/
function media_library_views_pre_render(ViewExecutable $view) {
$add_classes = function (&$option, array $classes_to_add) {
$classes = $option ? preg_split('/\s+/', trim($option)) : [];
$classes = array_filter($classes);
$classes = array_merge($classes, $classes_to_add);
$option = implode(' ', array_unique($classes));
};
if ($view->id() === 'media_library') {
if ($view->current_display === 'page') {
$add_classes($view->style_plugin->options['row_class'], ['js-media-library-item', 'js-click-to-select']);
if (array_key_exists('media_bulk_form', $view->field)) {
$add_classes($view->field['media_bulk_form']->options['element_class'], ['js-click-to-select-checkbox']);
}
}
elseif (strpos($view->current_display, 'widget') === 0) {
if (array_key_exists('media_library_select_form', $view->field)) {
$add_classes($view->field['media_library_select_form']->options['element_wrapper_class'], ['js-click-to-select-checkbox']);
}
$add_classes($view->display_handler->options['css_class'], ['js-media-library-view']);
}
$add_classes($view->style_plugin->options['row_class'], ['js-media-library-item', 'js-click-to-select']);
if ($view->display_handler->options['defaults']['css_class']) {
$add_classes($view->displayHandlers->get('default')->options['css_class'], ['js-media-library-view']);
}
else {
$add_classes($view->display_handler->options['css_class'], ['js-media-library-view']);
}
}
}
/**
* Implements hook_views_post_render().
*/
......@@ -143,23 +227,30 @@ function media_library_preprocess_media(&$variables) {
$variables['url'] = $media->toUrl($rel, [
'language' => $media->language(),
]);
$variables['preview_attributes'] = new Attribute();
$variables['preview_attributes']->addClass('media-library-item__preview', 'js-media-library-item-preview');
$variables['metadata_attributes'] = new Attribute();
$variables['metadata_attributes']->addClass('media-library-item__attributes');
$variables += [
'preview_attributes' => new Attribute(),
'metadata_attributes' => new Attribute(),
];
$variables['status'] = $media->isPublished();
}
}
/**
* Implements hook_preprocess_views_view() for the 'media_library' view.
*/
function media_library_preprocess_views_view__media_library(array &$variables) {
$variables['attributes']['data-view-display-id'] = $variables['view']->current_display;
}
/**
* Implements hook_preprocess_views_view_fields().
*/
function media_library_preprocess_views_view_fields(&$variables) {
// Add classes to media rendered entity field so it can be targeted for
// styling and JavaScript mouseover and click events.
// JavaScript mouseover and click events.
if ($variables['view']->id() === 'media_library' && isset($variables['fields']['rendered_entity'])) {
if (isset($variables['fields']['rendered_entity']->wrapper_attributes)) {
$variables['fields']['rendered_entity']->wrapper_attributes->addClass('js-click-to-select-trigger media-library-item__click-to-select-trigger');
$variables['fields']['rendered_entity']->wrapper_attributes->addClass('js-click-to-select-trigger');
}
}
}
......@@ -193,41 +284,32 @@ function media_library_form_views_form_media_library_page_alter(array &$form, Fo
* Implements hook_form_alter().
*/
function media_library_form_alter(array &$form, FormStateInterface $form_state, $form_id) {
$form_object = $form_state->getFormObject();
if ($form_object instanceof ViewsForm && strpos($form_object->getBaseFormId(), 'views_form_media_library') === 0) {
$form['#attributes']['class'][] = 'media-library-views-form';
if (isset($form['header'])) {
$form['header']['#attributes']['class'][] = 'media-library-views-form__header';
$form['header']['media_bulk_form']['#attributes']['class'][] = 'media-library-views-form__bulk_form';
}
}
// Add after build to fix media library views exposed filter's submit button.
// Add a process callback to ensure that the media library view's exposed
// filters submit button is not moved to the modal dialog's button area.
if ($form_id === 'views_exposed_form' && strpos($form['#id'], 'views-exposed-form-media-library-widget') === 0) {
$form['#after_build'][] = '_media_library_views_form_media_library_after_build';
}
// Configures media_library displays when a type is submitted.
if ($form_object instanceof MediaTypeForm) {
if ($form_state->getFormObject() instanceof MediaTypeForm) {
$form['actions']['submit']['#submit'][] = '_media_library_media_type_form_submit';
}
}
/**
* After build callback for views form media library.
* Form #after_build callback for media_library view's exposed filters form.
*/
function _media_library_views_form_media_library_after_build(array $form, FormStateInterface $form_state) {
// Remove .form-actions from media library views exposed filter actions
// and replace with .media-library-view--form-actions.
//
// This prevents the views exposed filter's 'Apply filter' submit button from
// being moved into the dialog's buttons.
// Remove .form-actions from the view's exposed filter actions. This prevents
// the "Apply filters" submit button from being moved into the dialog's
// button area.
// @see \Drupal\Core\Render\Element\Actions::processActions
// @see Drupal.behaviors.dialog.prepareDialogButtons
// @todo Remove this after
// https://www.drupal.org/project/drupal/issues/3089751 is fixed.
if (($key = array_search('form-actions', $form['actions']['#attributes']['class'])) !== FALSE) {
unset($form['actions']['#attributes']['class'][$key]);
}
$form['actions']['#attributes']['class'][] = 'media-library-view--form-actions';
return $form;
}
......
......@@ -18,9 +18,8 @@
* @ingroup ajax
*
* @internal
* Media Library is an experimental module and its internal code may be
* subject to change in minor releases. External code should not instantiate
* or extend this class.
* This is an internal part of Media Library and may be subject to change in
* minor releases. External code should not instantiate or extend this class.
*/
class UpdateSelectionCommand implements CommandInterface {
......
......@@ -9,8 +9,11 @@
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\BaseFormIdInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Security\TrustedCallbackInterface;
use Drupal\Core\Url;
use Drupal\media\MediaInterface;
use Drupal\media\MediaTypeInterface;
......@@ -21,13 +24,8 @@
/**
* Provides a base class for creating media items from within the media library.
*
* @internal
* Media Library is an experimental module and its internal code may be
* subject to change in minor releases. External code should not instantiate
* or extend this class.
*/
abstract class AddFormBase extends FormBase {
abstract class AddFormBase extends FormBase implements BaseFormIdInterface, TrustedCallbackInterface {
/**
* The entity type manager.
......@@ -99,7 +97,7 @@ public static function create(ContainerInterface $container) {
/**
* {@inheritdoc}
*/
public function getFormId() {
public function getBaseFormId() {
return 'media_library_add_form';
}
......@@ -137,9 +135,8 @@ protected function getMediaType(FormStateInterface $form_state) {
public function buildForm(array $form, FormStateInterface $form_state) {
// @todo Remove the ID when we can use selectors to replace content via
// AJAX in https://www.drupal.org/project/drupal/issues/2821793.
$form['#prefix'] = '<div id="media-library-add-form-wrapper" class="media-library-add-form-wrapper">';
$form['#prefix'] = '<div id="media-library-add-form-wrapper">';
$form['#suffix'] = '</div>';
$form['#attached']['library'][] = 'media_library/style';
// The media library is loaded via AJAX, which means that the form action
// URL defaults to the current URL. However, to add media, we always need to
......@@ -157,27 +154,37 @@ public function buildForm(array $form, FormStateInterface $form_state) {
];
$form['#attributes']['class'] = [
'media-library-add-form',
'js-media-library-add-form',
];
$added_media = $this->getAddedMediaItems($form_state);
if (empty($added_media)) {
$form['#attributes']['class'][] = 'media-library-add-form--without-input';
$form = $this->buildInputElement($form, $form_state);
}
else {
$form['#attributes']['class'][] = 'media-library-add-form--with-input';
$form['#attributes']['data-input'] = 'true';
// This deserves to be themeable, but it doesn't need to be its own "real"
// template.
$form['description'] = [
'#type' => 'inline_template',
'#template' => '<p>{{ text }}</p>',
'#context' => [
'text' => $this->formatPlural(count($added_media), 'The media item has been created but has not yet been saved. Fill in any required fields and save to add it to the media library.', 'The media items have been created but have not yet been saved. Fill in any required fields and save to add them to the media library.'),
],
];
$form['media'] = [
'#type' => 'container',
'#pre_render' => [
[$this, 'preRenderAddedMedia'],
],
'#attributes' => [
'class' => [
// This needs to be focus-able by an AJAX response.
// @see ::updateFormCallback()
'js-media-library-add-form-added-media',
'media-library-add-form__added-media',
],
'aria-label' => $this->t('Added media items'),
'role' => 'list',
// Add the tabindex '-1' to allow the focus to be shifted to the added
// media wrapper when items are added. We set focus to the container
// because a media item does not necessarily have required fields and
......@@ -186,18 +193,6 @@ public function buildForm(array $form, FormStateInterface $form_state) {
'tabindex' => '-1',
],
];
$form['media']['description'] = [
'#type' => 'html_tag',
'#tag' => 'p',
'#value' => $this->formatPlural(count($added_media), 'The media item has been created but has not yet been saved. Fill in any required fields and save to add it to the media library.', 'The media items have been created but have not yet been saved. Fill in any required fields and save to add them to the media library.'),
'#attributes' => [
'class' => [
'media-library-add-form__description',
],
],
];
foreach ($added_media as $delta => $media) {
$form['media'][$delta] = $this->buildEntityFormElement($media, $form, $form_state, $delta);
}
......@@ -267,13 +262,8 @@ protected function buildEntityFormElement(MediaInterface $media, array $form, Fo
$id_suffix = $parents ? '-' . implode('-', $parents) : '';
$element = [
'#type' => 'container',
'#attributes' => [
'class' => [
'media-library-add-form__media',
],
'#wrapper_attributes' => [
'aria-label' => $media->getName(),
'role' => 'listitem',
// Add the tabindex '-1' to allow the focus to be shifted to the next
// media item when an item is removed. We set focus to the container
// because a media item does not necessarily have required fields and we
......@@ -288,20 +278,10 @@ protected function buildEntityFormElement(MediaInterface $media, array $form, Fo
'preview' => [
'#type' => 'container',
'#weight' => 10,
'#attributes' => [
'class' => [
'media-library-add-form__preview',
],
],
],
'fields' => [
'#type' => 'container',
'#weight' => 20,
'#attributes' => [
'class' => [
'media-library-add-form__fields',
],
],
// The '#parents' are set here because the entity form display needs it
// to build the entity form fields.
'#parents' => ['media', $delta, 'fields'],
......@@ -312,7 +292,6 @@ protected function buildEntityFormElement(MediaInterface $media, array $form, Fo
'#name' => 'media-' . $delta . '-remove-button' . $id_suffix,
'#weight' => 30,
'#attributes' => [
'class' => ['media-library-add-form__remove-button'],
'aria-label' => $this->t('Remove @label', ['@label' => $media->getName()]),
],
'#ajax' => [
......@@ -349,13 +328,13 @@ protected function buildEntityFormElement(MediaInterface $media, array $form, Fo
}
$form_display->buildForm($media, $element['fields'], $form_state);
// We hide the preview of the uploaded file in the image widget with CSS.
// We hide the preview of the uploaded file in the image widget with CSS, so
// set a property so themes and form_alter hooks can easily identify the
// source field.
// @todo Improve hiding file widget elements in
// https://www.drupal.org/project/drupal/issues/2987921
$source_field_name = $this->getSourceFieldName($media->bundle->entity);
if (isset($element['fields'][$source_field_name])) {
$element['fields'][$source_field_name]['#attributes']['class'][] = 'media-library-add-form__source-field';
}
$element['fields']['#source_field_name'] = $this->getSourceFieldName($media->bundle->entity);
// The revision log field is currently not configurable from the form
// display, so hide it by changing the access.
// @todo Make the revision_log_message field configurable in
......@@ -366,6 +345,34 @@ protected function buildEntityFormElement(MediaInterface $media, array $form, Fo
return $element;
}
/**
* {@inheritdodc}
*/
public static function trustedCallbacks() {
return ['preRenderAddedMedia'];
}
/**
* Converts the set of newly added media into an item list for rendering.
*
* @param array $element
* The render element to transform.
*
* @return array
* The transformed render element.
*/
public function preRenderAddedMedia(array $element) {
// Transform the element into an item list for rendering.
$element['#theme'] = 'item_list__media_library_add_form_media_list';
$element['#list_type'] = 'ul';
foreach (Element::children($element) as $delta) {
$element['#items'][$delta] = $element[$delta];
unset($element[$delta]);
}
return $element;
}
/**
* Returns a render array containing the current selection.
*
......@@ -386,13 +393,11 @@ protected function buildCurrentSelectionArea(array $form, FormStateInterface $fo
$selection = [
'#type' => 'details',
'#theme_wrappers' => [
'details__media_library_add_form_selected_media',
],
'#open' => FALSE,
'#title' => $this->t('Additional selected media'),
'#attributes' => [
'class' => [
'media-library-add-form__selected-media',
],
],
];
foreach ($pre_selected_items as $media_id => $media) {
$selection[$media_id] = $this->buildSelectedItemElement($media, $form, $form_state);
......@@ -416,12 +421,9 @@ protected function buildCurrentSelectionArea(array $form, FormStateInterface $fo
*/
protected function buildSelectedItemElement(MediaInterface $media, array $form, FormStateInterface $form_state) {
return [
'#type' => 'container',
'#theme' => 'media_library_item__small',
'#attributes' => [
'class' => [
'media-library-item',
'media-library-item--grid',
'media-library-item--small',
'js-media-library-item',
'js-click-to-select',
],
......@@ -430,7 +432,7 @@ protected function buildSelectedItemElement(MediaInterface $media, array $form,
'#type' => 'container',
'#attributes' => [
'class' => [
'js-click-to-select-checkbox media-library-item__click-to-select-checkbox',
'js-click-to-select-checkbox',
],
],
'select_checkbox' => [
......
......@@ -27,9 +27,7 @@
* Creates a form to create media entities from uploaded files.
*
* @internal
* Media Library is an experimental module and its internal code may be
* subject to change in minor releases. External code should not instantiate
* or extend this class.
* Form classes are internal.
*/
class FileUploadForm extends AddFormBase {
......@@ -106,6 +104,13 @@ public static function create(ContainerInterface $container) {
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return $this->getBaseFormId() . '_upload';
}
/**
* {@inheritdoc}
*/
......@@ -128,8 +133,6 @@ protected function getMediaType(FormStateInterface $form_state) {
* {@inheritdoc}
*/
protected function buildInputElement(array $form, FormStateInterface $form_state) {
$form['#attributes']['class'][] = 'media-library-add-form--upload';
// Create a file item to get the upload validators.
$media_type = $this->getMediaType($form_state);
$item = $this->createFileItem($media_type);
......@@ -145,9 +148,6 @@ protected function buildInputElement(array $form, FormStateInterface $form_state
// Add a container to group the input elements for styling purposes.
$form['container'] = [
'#type' => 'container',
'#attributes' => [
'class' => ['media-library-add-form__input-wrapper'],
],
];
$process = (array) $this->elementInfo->getInfoProperty('managed_file', '#process', []);
......
......@@ -18,9 +18,7 @@
* Creates a form to create media entities from oEmbed URLs.
*
* @internal
* Media Library is an experimental module and its internal code may be
* subject to change in minor releases. External code should not instantiate
* or extend this class.
* Form classes are internal.
*/
class OEmbedForm extends AddFormBase {
......@@ -71,6 +69,13 @@ public static function create(ContainerInterface $container) {
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return $this->getBaseFormId() . '_oembed';
}
/**
* {@inheritdoc}
*/
......@@ -90,17 +95,12 @@ protected function getMediaType(FormStateInterface $form_state) {
* {@inheritdoc}
*/
protected function buildInputElement(array $form, FormStateInterface $form_state) {
$form['#attributes']['class'][] = 'media-library-add-form--oembed';
$media_type = $this->getMediaType($form_state);
$providers = $media_type->getSource()->getProviders();
// Add a container to group the input elements for styling purposes.
$form['container'] = [
'#type' => 'container',
'#attributes' => [
'class' => ['media-library-add-form__input-wrapper'],
],
];
$form['container']['url'] = [
......@@ -114,7 +114,6 @@ protected function buildInputElement(array $form, FormStateInterface $form_state
'#required' => TRUE,
'#attributes' => [
'placeholder' => 'https://',
'class' => ['media-library-add-form-oembed-url'],
],
];
......@@ -139,9 +138,6 @@ protected function buildInputElement(array $form, FormStateInterface $form_state
],
],
],
'#attributes' => [
'class' => ['media-library-add-form-oembed-submit'],
],
];
return $form;
}
......
......@@ -7,6 +7,9 @@
/**
* Defines a form for configuring the Media Library module.
*
* @internal
* Form classes are internal.
*/
class SettingsForm extends ConfigFormBase {
......
......@@ -14,9 +14,7 @@
* @see \Drupal\media_library\Plugin\CKEditorPlugin\DrupalMediaLibrary
*
* @internal
* This is an internal part of the media system in Drupal core and may be
* subject to change in minor releases. This class should not be
* instantiated or extended by external code.
* This service is an internal part of Media Library's CKEditor integration.
*/
class MediaLibraryEditorOpener implements MediaLibraryOpenerInterface {
......
......@@ -12,6 +12,9 @@
/**
* The media library opener for field widgets.
*
* @internal
* This service is an internal part of Media Library's field widget.
*/
class MediaLibraryFieldWidgetOpener implements MediaLibraryOpenerInterface {
......
......@@ -36,11 +36,6 @@
* with them either.
*
* @see \Drupal\media_library\MediaLibraryOpenerInterface
*
* @internal
* Media Library is an experimental module and its internal code may be
* subject to change in minor releases. External code should not instantiate
* or extend this class.
*/
class MediaLibraryState extends ParameterBag {
......
......@@ -17,9 +17,8 @@
* Service which builds the media library.
*
* @internal
* Media Library is an experimental module and its internal code may be
* subject to change in minor releases. External code should not instantiate
* or extend this class.
* This service is an internal part of the modal media library dialog and
* does not provide any extension points.
*/
class MediaLibraryUiBuilder {
......@@ -123,11 +122,9 @@ public function buildUi(MediaLibraryState $state = NULL) {
}
else {
return [
'#type' => 'html_tag',
'#tag' => 'div',
'#theme' => 'media_library_wrapper',
'#attributes' => [
'id' => 'media-library-wrapper',
'class' => ['media-library-wrapper'],
],
'menu' => $this->buildMediaTypeMenu($state),
'content' => $this->buildLibraryContent($state),
......@@ -157,11 +154,12 @@ public function buildUi(MediaLibraryState $state = NULL) {
*/
protected function buildLibraryContent(MediaLibraryState $state) {
return [
'#type' => 'html_tag',
'#tag' => 'div',
'#type' => 'container',
'#theme_wrappers' => [
'container__media_library_content',
],
'#attributes' => [
'id' => 'media-library-content',
'class' => ['media-library-content'],
],
'form' => $this->buildMediaTypeAddForm($state),
'view' => $this->buildMediaLibraryView($state),
......@@ -234,10 +232,10 @@ protected function buildMediaTypeMenu(MediaLibraryState $state) {
// @todo: Add a class to the li element.
// https://www.drupal.org/project/drupal/issues/3029227
$menu = [
'#theme' => 'links',
'#theme' => 'links__media_library_menu',
'#links' => [],
'#attributes' => [
'class' => ['media-library-menu', 'js-media-library-menu'],
'class' => ['js-media-library-menu'],
],
];
......@@ -267,7 +265,6 @@ protected function buildMediaTypeMenu(MediaLibraryState $state) {
'query' => $link_state->all(),
]),
'attributes' => [
'class' => ['media-library-menu__link'],
'role' => 'button',
'data-title' => $title,
],
......
......@@ -11,6 +11,10 @@
* services which implement \Drupal\media_library\MediaLibraryOpenerInterface.
* It is not an API and should not be extended or used by code that does not
* interact with the Media Library module.
*
* @internal
* This service is an internal part of the modal media library dialog and
* does not provide any extension points or public API.
*/
class OpenerResolver implements OpenerResolverInterface {
......
......@@ -9,6 +9,11 @@
* services which implement \Drupal\media_library\MediaLibraryOpenerInterface.
* It is not an API and should not be extended or used by code that does not
* interact with the Media Library module.
*
* @internal
* This interface is an internal part of the modal media library dialog and
* is only implemented by \Drupal\media_library\OpenerResolver. It is not a
* public API.
*/
interface OpenerResolverInterface {
......
......@@ -17,6 +17,7 @@
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Security\TrustedCallbackInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Drupal\field_ui\FieldUI;
......@@ -40,11 +41,9 @@
* )
*
* @internal
* Media Library is an experimental module and its internal code may be
* subject to change in minor releases. External code should not instantiate
* or extend this class.
* Plugin classes are internal.
*/
class MediaLibraryWidget extends WidgetBase implements ContainerFactoryPluginInterface {
class MediaLibraryWidget extends WidgetBase implements ContainerFactoryPluginInterface, TrustedCallbackInterface {
/**
* Entity type manager service.
......@@ -317,11 +316,17 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
'#target_bundles' => isset($settings['target_bundles']) ? $settings['target_bundles'] : FALSE,
'#attributes' => [
'id' => $wrapper_id,
'class' => ['js-media-library-widget', 'media-library-widget'],
'class' => ['js-media-library-widget'],
],
'#pre_render' => [
[$this, 'preRenderWidget'],
],
'#attached' => [
'library' => ['media_library/widget'],
],
'#theme_wrappers' => [
'fieldset__media_library_widget',
],
];
// When the list of allowed types in the field configuration is null,
......@@ -337,26 +342,21 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
}
if (empty($referenced_entities)) {
$element['empty_selection'] = [
'#type' => 'html_tag',
'#tag' => 'p',
'#value' => $this->t('No media items are selected.'),
'#attributes' => [
'class' => [
'media-library-widget-empty-text',
],
],
$element['#field_prefix']['empty_selection'] = [
'#markup' => $this->t('No media items are selected.'),
];
}
else {
$element['weight_toggle'] = [
// @todo Use a <button> link here, and delete
// seven_preprocess_fieldset__media_library_widget(), when
// https://www.drupal.org/project/drupal/issues/2999549 lands.
$element['#field_prefix']['weight_toggle'] = [
'#type' => 'html_tag',
'#tag' => 'button',
'#value' => $this->t('Show media item weights'),
'#attributes' => [
'class' => [
'link',
'media-library-widget__toggle-weight',
'js-media-library-widget-toggle-weight',
],
],
......@@ -365,21 +365,21 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
$element['selection'] = [
'#type' => 'container',
'#theme_wrappers' => [
'container__media_library_widget_selection',
],
'#attributes' => [
'class' => [
'js-media-library-selection',
'media-library-selection',
],
],
];
foreach ($referenced_entities as $delta => $media_item) {
$element['selection'][$delta] = [
'#type' => 'container',
'#theme' => 'media_library_item__widget',
'#attributes' => [
'class' => [
'media-library-item',
'media-library-item--grid',
'js-media-library-item',
],
// Add the tabindex '-1' to allow the focus to be shifted to the next
......@@ -393,32 +393,28 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
// @see ::updateWidget()
'data-media-library-item-delta' => $delta,
],
'preview' => [
'#type' => 'container',
'remove_button' => [
'#type' => 'submit',
'#name' => $field_name . '-' . $delta . '-media-library-remove-button' . $id_suffix,
'#value' => $this->t('Remove'),
'#media_id' => $media_item->id(),
'#attributes' => [
'class' => ['media-library-item__remove'],
'aria-label' => $this->t('Remove @label', ['@label' => $media_item->label()]),
],
'#ajax' => [
'callback' => [static::class, 'updateWidget'],
'wrapper' => $wrapper_id,
'progress' => [
'type' => 'throbber',
'message' => $this->t('Removing @label.', ['@label' => $media_item->label()]),
],
'remove_button' => [
'#type' => 'submit',
'#name' => $field_name . '-' . $delta . '-media-library-remove-button' . $id_suffix,
'#value' => $this->t('Remove'),
'#media_id' => $media_item->id(),
'#attributes' => [
'aria-label' => $this->t('Remove @label', ['@label' => $media_item->label()]),
],
'#ajax' => [
'callback' => [static::class, 'updateWidget'],
'wrapper' => $wrapper_id,
'progress' => [
'type' => 'throbber',
'message' => $this->t('Removing @label.', ['@label' => $media_item->label()]),
],
'#submit' => [[static::class, 'removeItem']],
// Prevent errors in other widgets from preventing removal.
'#limit_validation_errors' => $limit_validation_errors,
],
// @todo Make the view mode configurable in https://www.drupal.org/project/drupal/issues/2971209
'rendered_entity' => $view_builder->view($media_item, 'media_library'),
'#submit' => [[static::class, 'removeItem']],
// Prevent errors in other widgets from preventing removal.
'#limit_validation_errors' => $limit_validation_errors,
],
// @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' => [
'#type' => 'hidden',
'#value' => $media_item->id(),
......@@ -426,12 +422,12 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
// This hidden value can be toggled visible for accessibility.
'weight' => [
'#type' => 'number',
'#theme' => 'input__number__media_library_item_weight',
'#title' => $this->t('Weight'),
'#default_value' => $delta,
'#attributes' => [
'class' => [
'js-media-library-item-weight',
'media-library-item__weight',
],
],
],
......@@ -481,13 +477,12 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
$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['media_library_open_button'] = [
$element['open_button'] = [
'#type' => 'submit',
'#value' => $this->t('Add media'),
'#name' => $field_name . '-media-library-open-button' . $id_suffix,
'#attributes' => [
'class' => [
'media-library-open-button',
'js-media-library-open-button',
],
// The jQuery UI dialog automatically moves focus to the first :tabbable
......@@ -514,8 +509,8 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
// JavaScript by adding the 'data-disabled-focus' attribute.
// @see Drupal.behaviors.MediaLibraryWidgetDisableButton
if (!$cardinality_unlimited && $remaining === 0) {
$element['media_library_open_button']['#attributes']['data-disabled-focus'] = 'true';
$element['media_library_open_button']['#attributes']['class'][] = 'visually-hidden';
$element['open_button']['#attributes']['data-disabled-focus'] = 'true';
$element['open_button']['#attributes']['class'][] = 'visually-hidden';
}
// This hidden field and button are used to add new items to the widget.
......@@ -558,6 +553,32 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
return $element;
}
/**
* {@inheritdoc}
*/
public static function trustedCallbacks() {
return ['preRenderWidget'];
}
/**
* Prepares the widget's render element for rendering.
*
* @param array $element
* The element to transform.
*
* @return array
* The transformed element.
*
* @see ::formElement()
*/
public function preRenderWidget(array $element) {
if (isset($element['open_button'])) {
$element['#field_suffix']['open_button'] = $element['open_button'];
unset($element['open_button']);
}
return $element;
}
/**
* Gets the message to display when there are no allowed media types.
*
......@@ -633,7 +654,7 @@ public static function updateWidget(array $form, FormStateInterface $form_state)
// This callback is either invoked from the remove button or the update
// button, which have different nesting levels.
$is_remove_button = end($triggering_element['#parents']) === 'remove_button';
$length = $is_remove_button ? -4 : -1;
$length = $is_remove_button ? -3 : -1;
if (count($triggering_element['#array_parents']) < abs($length)) {
throw new \LogicException('The element that triggered the widget update was at an unexpected depth. Triggering element parents were: ' . implode(',', $triggering_element['#array_parents']));
}
......@@ -690,7 +711,7 @@ public static function updateWidget(array $form, FormStateInterface $form_state)
// 'data-disabled-focus' attribute and we also don't want to set the focus
// here.
// @see Drupal.behaviors.MediaLibraryWidgetDisableButton
elseif ($removed_last || (!$is_remove_button && !isset($element['media_library_open_button']['#attributes']['data-disabled-focus']))) {
elseif ($removed_last || (!$is_remove_button && !isset($element['open_button']['#attributes']['data-disabled-focus']))) {
$response->addCommand(new InvokeCommand("#$wrapper_id .js-media-library-open-button", 'focus'));
}
......@@ -712,7 +733,7 @@ public static function removeItem(array $form, FormStateInterface $form_state) {
if (count($triggering_element['#array_parents']) < 4) {
throw new \LogicException('Expected the remove button to be more than four levels deep in the form. Triggering element parents were: ' . implode(',', $triggering_element['#array_parents']));
}
$parents = array_slice($triggering_element['#array_parents'], 0, -4);
$parents = array_slice($triggering_element['#array_parents'], 0, -3);
$element = NestedArray::getValue($form, $parents);
// Get the field state.
......@@ -721,7 +742,7 @@ public static function removeItem(array $form, FormStateInterface $form_state) {
$field_state = static::getFieldState($element, $form_state);
// Get the delta of the item being removed.
$delta = array_slice($triggering_element['#array_parents'], -3, 1)[0];
$delta = array_slice($triggering_element['#array_parents'], -2, 1)[0];
if (isset($values['selection'][$delta])) {
// Add the weight of the removed item to the field state so we can shift
// focus to the next/previous item in an easy way.
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment