Commit 92390915 authored by xjm's avatar xjm

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

(cherry picked from commit 946835f4)
parent 51adbc6a
......@@ -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
core: 8.x
dependencies:
......
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:
......
......@@ -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' => [