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:
......
......@@ -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.
*