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

merge-requests/64/head
xjm 2019-11-04 18:00:11 +01:00
parent 9ef14c6195
commit 946835f45f
60 changed files with 1427 additions and 486 deletions

View File

@ -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

View File

@ -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,57 +786,6 @@ display:
display_extenders: { }
path: admin/content/media-widget
fields:
rendered_entity:
id: rendered_entity
table: media
field: rendered_entity
relationship: none
group_type: group
admin_label: ''
label: ''
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: media-library-item__content
element_label_type: ''
element_label_class: ''
element_label_colon: false
element_wrapper_type: ''
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: rendered_entity
media_library_select_form:
id: media_library_select_form
table: media
@ -879,7 +828,7 @@ 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
@ -887,6 +836,57 @@ display:
hide_alter_empty: true
entity_type: media
plugin_id: media_library_select_form
rendered_entity:
id: rendered_entity
table: media
field: rendered_entity
relationship: none
group_type: group
admin_label: ''
label: ''
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: false
element_wrapper_type: ''
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: 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

View File

@ -1,118 +0,0 @@
/**
* @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;
}

View File

@ -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',

View File

@ -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;

View File

@ -1,7 +1,7 @@
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:

View File

@ -1,11 +1,3 @@
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:

View File

@ -24,7 +24,6 @@ use Drupal\media\MediaTypeInterface;
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;
}

View File

@ -18,9 +18,8 @@ use Drupal\Core\Ajax\CommandInterface;
* @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 {

View File

@ -9,8 +9,11 @@ use Drupal\Core\Ajax\ReplaceCommand;
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 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* 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 @@ abstract class AddFormBase extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
public function getBaseFormId() {
return 'media_library_add_form';
}
@ -137,9 +135,8 @@ abstract class AddFormBase extends FormBase {
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 @@ abstract class AddFormBase extends FormBase {
];
$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 @@ abstract class AddFormBase extends FormBase {
'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 @@ abstract class AddFormBase extends FormBase {
$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 @@ abstract class AddFormBase extends FormBase {
'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 @@ abstract class AddFormBase extends FormBase {
'#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 @@ abstract class AddFormBase extends FormBase {
}
$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 @@ abstract class AddFormBase extends FormBase {
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 @@ abstract class AddFormBase extends FormBase {
$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 @@ abstract class AddFormBase extends FormBase {
*/
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 @@ abstract class AddFormBase extends FormBase {
'#type' => 'container',
'#attributes' => [
'class' => [
'js-click-to-select-checkbox media-library-item__click-to-select-checkbox',
'js-click-to-select-checkbox',
],
],
'select_checkbox' => [

View File

@ -27,9 +27,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
* 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 @@ class FileUploadForm extends AddFormBase {
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return $this->getBaseFormId() . '_upload';
}
/**
* {@inheritdoc}
*/
@ -128,8 +133,6 @@ class FileUploadForm extends AddFormBase {
* {@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 @@ class FileUploadForm extends AddFormBase {
// 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', []);

View File

@ -18,9 +18,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
* 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 @@ class OEmbedForm extends AddFormBase {
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return $this->getBaseFormId() . '_oembed';
}
/**
* {@inheritdoc}
*/
@ -90,17 +95,12 @@ class OEmbedForm extends AddFormBase {
* {@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 @@ class OEmbedForm extends AddFormBase {
'#required' => TRUE,
'#attributes' => [
'placeholder' => 'https://',
'class' => ['media-library-add-form-oembed-url'],
],
];
@ -139,9 +138,6 @@ class OEmbedForm extends AddFormBase {
],
],
],
'#attributes' => [
'class' => ['media-library-add-form-oembed-submit'],
],
];
return $form;
}

View File

@ -7,6 +7,9 @@ use Drupal\Core\Form\FormStateInterface;
/**
* Defines a form for configuring the Media Library module.
*
* @internal
* Form classes are internal.
*/
class SettingsForm extends ConfigFormBase {

View File

@ -14,9 +14,7 @@ use Drupal\editor\Ajax\EditorDialogSave;
* @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 {

View File

@ -12,6 +12,9 @@ use Drupal\Core\Session\AccountInterface;
/**
* The media library opener for field widgets.
*
* @internal
* This service is an internal part of Media Library's field widget.
*/
class MediaLibraryFieldWidgetOpener implements MediaLibraryOpenerInterface {

View File

@ -36,11 +36,6 @@ use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
* 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 {

View File

@ -17,9 +17,8 @@ use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
* 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 @@ class MediaLibraryUiBuilder {
}
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 @@ class MediaLibraryUiBuilder {
*/
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 @@ class MediaLibraryUiBuilder {
// @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 @@ class MediaLibraryUiBuilder {
'query' => $link_state->all(),
]),
'attributes' => [
'class' => ['media-library-menu__link'],
'role' => 'button',
'data-title' => $title,
],

View File

@ -11,6 +11,10 @@ use Symfony\Component\DependencyInjection\ContainerAwareTrait;
* 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 {

View File

@ -9,6 +9,11 @@ namespace Drupal\media_library;
* 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 {

View File

@ -17,6 +17,7 @@ use Drupal\Core\Field\FieldStorageDefinitionInterface;
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 @@ use Symfony\Component\Validator\ConstraintViolationInterface;
* )
*
* @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 @@ class MediaLibraryWidget extends WidgetBase implements ContainerFactoryPluginInt
'#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 @@ class MediaLibraryWidget extends WidgetBase implements ContainerFactoryPluginInt
}
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 @@ class MediaLibraryWidget extends WidgetBase implements ContainerFactoryPluginInt
$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 @@ class MediaLibraryWidget extends WidgetBase implements ContainerFactoryPluginInt
// @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()]),
],
],
'#submit' => [[static::class, 'removeItem']],
// Prevent errors in other widgets from preventing removal.
'#limit_validation_errors' => $limit_validation_errors,
'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()]),
],
// @todo Make the view mode configurable in https://www.drupal.org/project/drupal/issues/2971209
'rendered_entity' => $view_builder->view($media_item, 'media_library'),
'#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'),
'target_id' => [
'#type' => 'hidden',
'#value' => $media_item->id(),
@ -426,12 +422,12 @@ class MediaLibraryWidget extends WidgetBase implements ContainerFactoryPluginInt
// 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 @@ class MediaLibraryWidget extends WidgetBase implements ContainerFactoryPluginInt
$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 @@ class MediaLibraryWidget extends WidgetBase implements ContainerFactoryPluginInt
// 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 @@ class MediaLibraryWidget extends WidgetBase implements ContainerFactoryPluginInt
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 @@ class MediaLibraryWidget extends WidgetBase implements ContainerFactoryPluginInt
// 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 @@ class MediaLibraryWidget extends WidgetBase implements ContainerFactoryPluginInt
// '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 @@ class MediaLibraryWidget extends WidgetBase implements ContainerFactoryPluginInt
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 @@ class MediaLibraryWidget extends WidgetBase implements ContainerFactoryPluginInt
$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.

View File

@ -18,9 +18,7 @@ use Symfony\Component\HttpFoundation\Request;
* @ViewsField("media_library_select_form")
*
* @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 MediaLibrarySelectForm extends FieldPluginBase {
@ -47,9 +45,7 @@ class MediaLibrarySelectForm extends FieldPluginBase {
* The current state of the form.
*/
public function viewsForm(array &$form, FormStateInterface $form_state) {
$form['#attributes'] = [
'class' => ['media-library-views-form', 'js-media-library-views-form'],
];
$form['#attributes']['class'] = ['js-media-library-views-form'];
// Add an attribute that identifies the media type displayed in the form.
if (isset($this->view->args[0])) {
@ -107,10 +103,7 @@ class MediaLibrarySelectForm extends FieldPluginBase {
// the opener, and the opener should be responsible for moving the focus. An
// example of this can be seen in MediaLibraryWidget::updateWidget().
// @see \Drupal\media_library\Plugin\Field\FieldWidget\MediaLibraryWidget::updateWidget()
$form['actions']['submit']['#attributes'] = [
'class' => ['media-library-select'],
'data-disable-refocus' => 'true',
];
$form['actions']['submit']['#attributes']['data-disable-refocus'] = 'true';
}
/**

View File

@ -7,6 +7,9 @@ use Symfony\Component\Routing\RouteCollection;
/**
* Subscriber for media library routes.
*
* @internal
* Tagged services are internal.
*/
class RouteSubscriber extends RouteSubscriberBase {

View File

@ -29,22 +29,21 @@
* - status: Whether or not the Media is published.
*
* @see template_preprocess_media()
* @see media_library_preprocess_media()
*
* @ingroup themeable
*/
#}
<article{{ attributes }}>
{% if content %}
<div{{ preview_attributes }}>
<div{{ preview_attributes.addClass('js-media-library-item-preview') }}>
{{ content|without('name') }}
</div>
{% if not status %}
<div class="media-library-item__status">{{ "unpublished" | t }}</div>
{{ "unpublished" | t }}
{% endif %}
<div{{ metadata_attributes }}>
<div class="media-library-item__name">
{{ name }}
</div>
{{ name }}
</div>
{% endif %}
</article>

View File

@ -0,0 +1,22 @@
{#
/**
* @file
* Default theme implementation of a media library item.
*
* This is used when displaying selected media items, either in the field
* widget or in the "Additional selected media" area when adding new
* media items in the media library modal dialog.
*
* Available variables:
* - attributes: HTML attributes for the containing element.
* - content: The content of the media library item, plus any additional
* fields or elements surrounding it.
*
* @see template_preprocess_media_library_item()
*
* @ingroup themeable
*/
#}
<div{{ attributes }}>
{{ content }}
</div>

View File

@ -0,0 +1,21 @@
{#
/**
* @file
* Default theme implementation of a container used to wrap the media library's
* modal dialog interface.
*
* Available variables:
* - attributes: HTML attributes for the containing element.
* - menu: The menu of availble media types to choose from.
* - content: The form to add new media items, followed by the grid or table of
* existing media items to choose from.
*
* @see template_preprocess_media_library_wrapper()
*
* @ingroup themeable
*/
#}
<div{{ attributes }}>
{{ menu }}
{{ content }}
</div>

View File

@ -128,16 +128,16 @@ class MediaLibraryTest extends WebDriverTestBase {
// Test that users can filter by type.
$page->selectFieldOption('Media type', 'Type One');
$page->pressButton('Apply filters');
$this->waitForText('Dog');
$this->waitForNoText('Turtle');
$assert_session->pageTextContains('Dog');
$page->selectFieldOption('Media type', 'Type Two');
$page->pressButton('Apply filters');
$this->waitForNoText('Dog');
$this->waitForText('Turtle');
$assert_session->pageTextNotContains('Dog');
// Test that selecting elements as a part of bulk operations works.
$page->selectFieldOption('Media type', '- Any -');
$page->pressButton('Apply filters');
$assert_session->elementExists('css', '#views-exposed-form-media-library-page')->submit();
$this->waitForText('Dog');
// This tests that anchor tags clicked inside the preview are suppressed.
@ -145,14 +145,17 @@ class MediaLibraryTest extends WebDriverTestBase {
$this->submitForm([], 'Apply to selected items');
$assert_session->pageTextContains('Dog');
$assert_session->pageTextNotContains('Cat');
$this->submitForm([], 'Delete');
// For reasons that are not clear, deleting media items by pressing the
// "Delete" button can fail (the button is found, but never actually pressed
// by the Mink driver). This workaround allows the delete form to be
// submitted.
$assert_session->elementExists('css', 'form')->submit();
$assert_session->pageTextNotContains('Dog');
$assert_session->pageTextContains('Cat');
// Test the 'Select all media' checkbox and assert that it makes the
// expected announcements.
$select_all = $assert_session->waitForField('Select all media');
$this->assertNotEmpty($select_all);
$select_all = $this->waitForFieldExists('Select all media');
$select_all->check();
$this->waitForText('All 7 items selected');
$select_all->uncheck();
@ -160,7 +163,11 @@ class MediaLibraryTest extends WebDriverTestBase {
$select_all->check();
$page->selectFieldOption('Action', 'media_delete_action');
$this->submitForm([], 'Apply to selected items');
$page->pressButton('Delete');
// For reasons that are not clear, deleting media items by pressing the
// "Delete" button can fail (the button is found, but never actually pressed
// by the Mink driver). This workaround allows the delete form to be
// submitted.
$assert_session->elementExists('css', 'form')->submit();
$assert_session->pageTextNotContains('Cat');
$assert_session->pageTextNotContains('Turtle');
@ -439,7 +446,6 @@ class MediaLibraryTest extends WebDriverTestBase {
// Assert generic media library elements.
$this->openMediaLibraryForField('field_unlimited_media');
$this->assertFalse($assert_session->elementExists('css', '.media-library-select-all')->isVisible());
$assert_session->elementExists('css', '.ui-dialog-titlebar-close')->click();
// Assert that the media type menu is available when more than 1 type is
@ -984,13 +990,11 @@ class MediaLibraryTest extends WebDriverTestBase {
$assert_session->fieldExists('Add files');
// Assert we can upload a file to the default tab type_three.
$assert_session->elementExists('css', '.media-library-add-form--without-input');
$assert_session->elementNotExists('css', '.media-library-add-form--with-input');
$assert_session->elementNotExists('css', '.js-media-library-add-form[data-input]');
$this->addMediaFileToField('Add files', $this->container->get('file_system')->realpath($png_image->uri));
$this->assertMediaAdded();
$assert_session->elementExists('css', '.media-library-add-form--with-input');
$assert_session->elementNotExists('css', '.media-library-add-form--without-input');
// We do not have a pre-selected items, so the container should not be added
$assert_session->elementExists('css', '.js-media-library-add-form[data-input]');
// We do not have pre-selected items, so the container should not be added
// to the form.
$assert_session->pageTextNotContains('Additional selected media');
// Files are temporary until the form is saved.
@ -1326,12 +1330,10 @@ class MediaLibraryTest extends WebDriverTestBase {
$assert_session->fieldExists('Add files');
// Assert we can upload a file to the default tab type_three.
$assert_session->elementExists('css', '.media-library-add-form--without-input');
$assert_session->elementNotExists('css', '.media-library-add-form--with-input');
$assert_session->elementNotExists('css', '.js-media-library-add-form[data-input]');
$this->addMediaFileToField('Add files', $this->container->get('file_system')->realpath($png_image->uri));
$this->assertMediaAdded();
$assert_session->elementExists('css', '.media-library-add-form--with-input');
$assert_session->elementNotExists('css', '.media-library-add-form--without-input');
$assert_session->elementExists('css', '.js-media-library-add-form[data-input]');
// We do not have a pre-selected items, so the container should not be added
// to the form.
$assert_session->elementNotExists('css', 'details summary:contains(Additional selected media)');
@ -2247,6 +2249,9 @@ class MediaLibraryTest extends WebDriverTestBase {
$assert_session->linkExists('Grid');
$assert_session->linkExists('Table');
// The "select all" checkbox should never be present in the modal.
$assert_session->elementNotExists('css', '.media-library-select-all');
return $this->assertElementExistsAfterWait('css', $after_open_selector);
}
@ -2379,18 +2384,16 @@ class MediaLibraryTest extends WebDriverTestBase {
* Asserts that the grid display of the widget view is visible.
*/
protected function assertMediaLibraryGrid() {
$assert_session = $this->assertSession();
$assert_session->elementExists('css', '.view-media-library.view-display-id-widget');
$assert_session->elementNotExists('css', '.view-media-library.view-display-id-widget_table');
$this->assertSession()
->elementExists('css', '.js-media-library-view[data-view-display-id="widget"]');
}
/**
* Asserts that the table display of the widget view is visible.
*/
protected function assertMediaLibraryTable() {
$assert_session = $this->assertSession();
$assert_session->elementExists('css', '.view-media-library.view-display-id-widget_table');
$assert_session->elementNotExists('css', '.view-media-library.view-display-id-widget');
$this->assertSession()
->elementExists('css', '.js-media-library-view[data-view-display-id="widget_table"]');
}
/**

View File

@ -25,6 +25,10 @@ libraries-extend:
- classy/progress
media/media_embed_ckeditor_theme:
- classy/media_embed_ckeditor_theme
media_library/view:
- classy/media_library
media_library/widget:
- classy/media_library
ckeditor_stylesheets:
- css/components/media-embed-error.css

View File

@ -68,6 +68,12 @@ indented:
component:
css/components/indented.css: {}
media_library:
version: VERSION
css:
layout:
css/layout/media-library.css: {}
messages:
version: VERSION
css:

View File

@ -0,0 +1,34 @@
<?php
/**
* @file
* Functions to support theming in the Classy theme.
*/
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Form\ViewsForm;
/**
* Implements hook_preprocess_links__media_library_menu().
*
* This targets the menu of available media types in the media library's modal
* dialog.
*
* @todo Do this in the relevant template once
* https://www.drupal.org/project/drupal/issues/3088856 is resolved.
*/
function classy_preprocess_links__media_library_menu(array &$variables) {
foreach ($variables['links'] as &$link) {
$link['link']['#options']['attributes']['class'][] = 'media-library-menu__link';
}
}
/**
* Implements hook_form_alter().
*/
function classy_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';
}
}

View File

@ -0,0 +1,28 @@
/**
* @file
* Contains minimal layout styling for the media library.
*/
.media-library-wrapper {
display: flex;
}
.media-library-menu {
flex-basis: 20%;
flex-shrink: 0;
}
.media-library-content {
flex-grow: 1;
}
.media-library-views-form {
display: flex;
flex-wrap: wrap;
}
.media-library-views-form .media-library-item {
justify-content: space-between;
max-width: 23%;
margin: 1%;
}

View File

@ -0,0 +1,28 @@
{#
/**
* @file
* Theme implementation the content area of the modal media library dialog.
*
* The content area is everything that is not the menu of available media
* types. This includes the form to add new media items, if available, and
* the view of available media to select.
*
* Available variables:
* - attributes: HTML attributes for the containing element.
* - children: The rendered child elements of the container.
* - has_parent: A flag to indicate that the container has one or more parent
containers.
*
* @see template_preprocess_container()
*
* @ingroup themeable
*/
#}
{%
set classes = [
has_parent ? 'js-form-wrapper',
has_parent ? 'form-wrapper',
'media-library-content',
]
%}
<div{{ attributes.addClass(classes) }}>{{ children }}</div>

View File

@ -0,0 +1,28 @@
{#
/**
* @file
* Theme implementation of a wrapper for selected media items.
*
* This is used to wrap around the set of media items that are currently
* selected in the media library widget (not the modal dialog), which may
* be used for entity reference fields that target media.
*
* Available variables:
* - attributes: HTML attributes for the containing element.
* - children: The rendered child elements of the container.
* - has_parent: A flag to indicate that the container has one or more parent
containers.
*
* @see template_preprocess_container()
*
* @ingroup themeable
*/
#}
{%
set classes = [
has_parent ? 'js-form-wrapper',
has_parent ? 'form-wrapper',
'media-library-selection',
]
%}
<div{{ attributes.addClass(classes) }}>{{ children }}</div>

View File

@ -0,0 +1,36 @@
{% extends "links.html.twig" %}
{#
/**
* @file
* Theme implementation of the media type menu in the media library dialog.
*
* Available variables:
* - attributes: Attributes for the UL containing the list of links.
* - links: Links to be output.
* Each link will have the following elements:
* - title: The link text.
* - href: The link URL. If omitted, the 'title' is shown as a plain text
* item in the links list. If 'href' is supplied, the entire link is passed
* to l() as its $options parameter.
* - attributes: (optional) HTML attributes for the anchor, or for the <span>
* tag if no 'href' is supplied.
* - heading: (optional) A heading to precede the links.
* - text: The heading text.
* - level: The heading level (e.g. 'h2', 'h3').
* - attributes: (optional) A keyed list of attributes for the heading.
* If the heading is a string, it will be used as the text of the heading and
* the level will default to 'h2'.
*
* Headings should be used on navigation menus and any list of links that
* consistently appears on multiple pages. To make the heading invisible use
* the 'visually-hidden' CSS class. Do not use 'display:none', which
* removes it from screen readers and assistive technology. Headings allow
* screen reader and keyboard only users to navigate to or skip the links.
* See http://juicystudio.com/article/screen-readers-display-none.php and
* http://www.w3.org/TR/WCAG-TECHS/H42.html for more information.
*
* @see classy_preprocess_links__media_library_menu()
* @see template_preprocess_links()
*/
#}
{% set attributes = attributes.addClass('media-library-menu') %}

View File

@ -0,0 +1,55 @@
{#
/**
* @file
* Theme override of a media item in the media library.
*
* This is used for media that the user can select from the grid of media
* items. It is not used for items that have already been selected in the
* corresponding field widget, or for items that have been previously selected
* before adding new media to the library.
*
* Available variables:
* - media: The entity with limited access to object properties and methods.
* Only method names starting with "get", "has", or "is" and a few common
* methods such as "id", "label", and "bundle" are available. For example:
* - entity.getEntityTypeId() will return the entity type ID.
* - entity.hasField('field_example') returns TRUE if the entity includes
* field_example. (This does not indicate the presence of a value in this
* field.)
* Calling other methods, such as entity.delete(), will result in an exception.
* See \Drupal\Core\Entity\EntityInterface for a full list of methods.
* - name: Name of the media.
* - content: Media content.
* - title_prefix: Additional output populated by modules, intended to be
* displayed in front of the main title tag that appears in the template.
* - title_suffix: Additional output populated by modules, intended to be
* displayed after the main title tag that appears in the template.
* - view_mode: View mode; for example, "teaser" or "full".
* - attributes: HTML attributes for the containing element.
* - title_attributes: Same as attributes, except applied to the main title
* tag that appears in the template.
* - url: Direct URL of the media.
* - preview_attributes: HTML attributes for the preview wrapper.
* - metadata_attributes: HTML attributes for the expandable metadata area.
* - status: Whether or not the Media is published.
*
* @see template_preprocess_media()
*
* @ingroup themeable
*/
#}
<article{{ attributes }}>
{% if content %}
<div{{ preview_attributes.addClass('media-library-item__preview js-media-library-item-preview') }}>
{{ content|without('name') }}
</div>
{% if not status %}
<div class="media-library-item__status">{{ "unpublished" | t }}</div>
{% endif %}
<div{{ metadata_attributes.addClass('media-library-item__attributes') }}>
<div class="media-library-item__name">
{{ name }}
</div>
</div>
{% endif %}
</article>

View File

@ -0,0 +1,31 @@
{#
/**
* @file
* Default theme implementation of a media library item.
*
* This is used when displaying selected media items, either in the field
* widget or in the "Additional selected media" area when adding new
* media items in the media library modal dialog.
*
* Available variables:
* - attributes: HTML attributes for the containing element.
* - content: The content of the media library item, plus any additional
* fields or elements surrounding it.
*
* @see seven_preprocess_media_library_item__small()
* @see seven_preprocess_media_library_item__widget()
* @see template_preprocess_media_library_item()
*
* @ingroup themeable
*/
#}
{%
set classes = [
'media-library-item',
'media-library-item--grid',
'media-library-item--small',
]
%}
<div{{ attributes.addClass(classes) }}>
{{ content }}
</div>

View File

@ -0,0 +1,28 @@
{#
/**
* @file
* Default theme implementation of a media library item.
*
* This is used when displaying selected media items, either in the field
* widget or in the "Additional selected media" area when adding new
* media items in the media library modal dialog.
*
* Available variables:
* - attributes: HTML attributes for the containing element.
* - content: The content of the media library item, plus any additional
* fields or elements surrounding it.
*
* @see template_preprocess_media_library_item()
*
* @ingroup themeable
*/
#}
{%
set classes = [
'media-library-item',
'media-library-item--grid',
]
%}
<div{{ attributes.addClass(classes) }}>
{{ content }}
</div>

View File

@ -0,0 +1,21 @@
{#
/**
* @file
* Theme override of a container used to wrap the media library's modal dialog
* interface.
*
* Available variables:
* - attributes: HTML attributes for the containing element.
* - menu: The menu of availble media types to choose from.
* - content: The form to add new media items, followed by the grid or table of
* existing media items to choose from.
*
* @see template_preprocess_media_library_wrapper()
*
* @ingroup themeable
*/
#}
<div{{ attributes.addClass('media-library-wrapper') }}>
{{ menu }}
{{ content }}
</div>

View File

@ -0,0 +1,35 @@
{#
/**
* @file
* Theme override of the media library view.
*
* This is used to display a grid of media items, in both the administrative
* interface and in the modal media library dialog's grid layout.
*
* Available variables:
* - title: The title of this group of rows. May be empty.
* - rows: A list of the view's row items.
* - attributes: The row's HTML attributes.
* - content: The row's content.
* - view: The view object.
* - default_row_class: A flag indicating whether default classes should be
* used on rows.
*
* @see template_preprocess_views_view_unformatted()
*/
#}
{% if title %}
<h3>{{ title }}</h3>
{% endif %}
{% for row in rows %}
{%
set row_classes = [
default_row_class ? 'views-row',
'media-library-item',
'media-library-item--grid',
]
%}
<div{{ row.attributes.addClass(row_classes) }}>
{{- row.content -}}
</div>
{% endfor %}

View File

@ -1,11 +1,11 @@
/**
* @file media_library.theme.css
*
* @todo Move into the Seven theme when this module is marked as stable.
* @see https://www.drupal.org/project/drupal/issues/2980769
* @file media-library.css
* Styling for Media Library.
*/
.media-library-wrapper {
display: flex;
margin: -1em;
}
@ -33,6 +33,8 @@
*/
.media-library-menu li {
display: block;
padding: 0;
list-style: none;
}
.media-library-menu__link {
@ -91,12 +93,14 @@
}
/**
* Remove outline from added media container.
* Remove outline from added media list.
*
* The added media container receives focus after adding new media, but since
* it is not an interactive element it does not need an outline.
* The added media list receives focus after adding new media, but since it is
* not an interactive element, it does not need an outline.
*/
.media-library-add-form__added-media {
margin: 0;
padding: 0;
outline: none;
}
@ -116,10 +120,6 @@
margin: 8px 0 0;
}
.media-library-add-form__description {
margin: 0;
}
/* Style the media add oEmbed form. */
.media-library-add-form--oembed .media-library-add-form__input-wrapper {
display: flex;
@ -163,21 +163,56 @@
/* Generic media library view styles. */
.media-library-select-all {
margin: 10px 0 10px 0;
flex-basis: 100%;
width: 100%;
margin: 10px 8px;
}
.media-library-select-all input {
margin-right: 10px;
}
[dir="rtl"] .media-library-select-all input {
margin-left: 10px;
}
.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 > .form-actions {
flex-basis: 100%;
}
.media-library-views-form__header {
flex-basis: 100%;
}
.media-library-views-form__header .form-item {
margin-right: 8px;
}
.media-library-views-form__rows {
display: flex;
flex-wrap: wrap;
flex-basis: 100%;
margin: 0 -8px;
}
.media-library-view .form-actions {
align-self: flex-end;
margin: 0.75em 0;
}
@media screen and (max-width: 600px) {
.media-library-view .form-actions {
flex-basis: 100%;
}
}
.media-library-view .media-library-view--form-actions {
clear: left;
align-self: flex-end;
@ -203,13 +238,8 @@
justify-content: space-between;
}
/**
* @todo Remove order and reorder the views header and filters via a views
* template in https://www.drupal.org/project/drupal/issues/3035994
*/
.media-library-wrapper .view-header {
align-self: flex-end;
order: 2;
margin: 1em 0;
text-align: right; /* LTR */
}
@ -217,29 +247,8 @@
text-align: left;
}
/**
* @todo Remove order and reorder the views header and filters via a views
* template in https://www.drupal.org/project/drupal/issues/3035994
*/
.media-library-wrapper .media-library-view .view-filters {
order: 1;
}
/**
* @todo Remove order and reorder the views header and filters via a views
* template in https://www.drupal.org/project/drupal/issues/3035994
*/
.media-library-wrapper .media-library-view .view-content {
flex: 0 0 100%;
order: 3;
}
/**
* @todo Remove order and reorder the views header and filters via a views
* template in https://www.drupal.org/project/drupal/issues/3035994
*/
.media-library-wrapper .media-library-view .pager {
order: 4;
}
.media-library-wrapper .views-display-link {
@ -260,46 +269,31 @@
.media-library-wrapper .views-display-link-widget {
margin-right: 15px;
background: url(../../../misc/icons/333333/grid.svg) left 0 no-repeat; /* LTR */
background: url(../../../../misc/icons/333333/grid.svg) left 0 no-repeat; /* LTR */
}
[dir="rtl"] .media-library-wrapper .views-display-link-widget {
background-position: right 0;
}
.media-library-wrapper .views-display-link-widget_table {
background: url(../../../misc/icons/333333/table.svg) left 0 no-repeat; /* LTR */
background: url(../../../../misc/icons/333333/table.svg) left 0 no-repeat; /* LTR */
}
[dir="rtl"] .media-library-wrapper .views-display-link-widget_table {
background-position: right 0;
}
/* Media library item grid styles. */
.media-library-views-form {
margin: 0 -8px;
}
/**
* Fix the negative margin of the grid.
*
* We need to fix the negative margin of the grid for table based displays and
* form elements that should not be part of the grid.
*
* @todo: Remove when new wrapper is added to apply negative margins in
* https://www.drupal.org/project/drupal/issues/3038489
*/
.media-library-views-form__header,
.media-library-select-all,
.media-library-views-form > .views-table {
margin: 0 8px;
}
/**
* Style the media library grid items.
*
* The media library item container receives screen reader focus when items are
* removed. Since it is not an interactive element, it does not need an
* outline.
*/
.media-library-item {
position: relative;
}
/**
* The media library item container receives screen reader focus when items are
* removed. Since it is not an interactive element, it does not need an
* outline.
*/
.media-library-item--grid {
justify-content: center;
box-sizing: border-box;
@ -332,6 +326,23 @@
width: 33.3%;
}
.media-library-widget-modal .ui-dialog-buttonpane {
display: flex;
align-items: center;
}
.media-library-widget-modal .ui-dialog-buttonpane .form-actions {
flex: 1;
}
/**
* 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 screen and (min-width: 45em) {
.media-library-item--grid {
width: 33.3%;
@ -409,6 +420,18 @@
border-color: #0076c0;
}
.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__click-to-select-checkbox input {
width: 20px;
height: 20px;
@ -418,6 +441,12 @@
margin: 0;
}
.media-library-item__click-to-select-trigger {
overflow: hidden;
height: 100%;
cursor: pointer;
}
/* Media library item table styles. */
.media-library-item--table img {
max-width: 100px;
@ -427,15 +456,24 @@
/* Media library entity view display styles. */
.media-library-item__preview {
padding-bottom: 34px;
cursor: move;
}
.media-library-item__status {
position: absolute;
top: 40px;
left: 5px; /* LTR */
padding: 5px 10px;
pointer-events: none;
color: #e4e4e4;
background: #666;
font-size: 12px;
font-style: italic;
}
[dir="rtl"] .media-library-item__status {
right: 5px;
left: auto;
}
.media-library-item__attributes {
position: absolute;
@ -472,6 +510,7 @@
}
.media-library-item--disabled {
pointer-events: none;
opacity: 0.5;
}
@ -560,7 +599,7 @@
}
.media-library-item__edit {
background: url("../../../misc/icons/787878/pencil.svg") #fff center no-repeat;
background: url("../../../../misc/icons/787878/pencil.svg") #fff center no-repeat;
background-size: 13px;
}
.media-library-item__remove,
@ -570,7 +609,7 @@
.media-library-item__remove.button:disabled:active,
.media-library-item__remove.button:hover,
.media-library-item__remove.button:focus {
background: url("../../../misc/icons/787878/ex.svg") #fff center no-repeat;
background: url("../../../../misc/icons/787878/ex.svg") #fff center no-repeat;
background-size: 13px;
}
.media-library-item__edit:hover,
@ -656,7 +695,7 @@
color: transparent;
border: 0;
border-radius: 0;
background: transparent url(../../../misc/icons/787878/ex.svg) right 2px no-repeat; /* LTR */
background: transparent url(../../../../misc/icons/787878/ex.svg) right 2px no-repeat; /* LTR */
font-weight: normal;
line-height: 16px;
}
@ -673,7 +712,7 @@
.media-library-add-form__remove-button.button:focus {
color: #787878;
border: 0;
background: transparent url(../../../misc/icons/787878/ex.svg) right 2px no-repeat; /* LTR */
background: transparent url(../../../../misc/icons/787878/ex.svg) right 2px no-repeat; /* LTR */
}
[dir="rtl"] .media-library-add-form__remove-button:focus,
[dir="rtl"] .media-library-add-form__remove-button.button:disabled,
@ -686,10 +725,15 @@
.media-library-add-form__remove-button.button:hover {
color: #e00;
border: 0;
background: transparent url(../../../misc/icons/ee0000/ex.svg) right 2px no-repeat; /* LTR */
background: transparent url(../../../../misc/icons/ee0000/ex.svg) right 2px no-repeat; /* LTR */
box-shadow: none;
}
[dir="rtl"] .media-library-add-form__remove-button:hover,
[dir="rtl"] .media-library-add-form__remove-button.button:hover {
background-position: left 2px;
}
/* @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;
}

View File

@ -46,6 +46,10 @@ libraries-override:
css:
component:
css/components/details.css: false
classy/media_library:
css:
layout:
css/layout/media-library.css: false
libraries-extend:
core/ckeditor:
@ -54,6 +58,10 @@ libraries-extend:
- seven/vertical-tabs
core/jquery.ui:
- seven/seven.jquery.ui
media_library/view:
- seven/media_library
media_library/widget:
- seven/media_library
tour/tour-styling:
- seven/tour-styling
quickedit_stylesheets:

View File

@ -132,6 +132,12 @@ media-form:
dependencies:
- media/form
media_library:
version: VERSION
css:
theme:
css/theme/media-library.css: {}
layout_builder_content_translation_admin:
version: VERSION
css:

View File

@ -8,6 +8,8 @@
use Drupal\Core\Url;
use Drupal\Core\Form\FormStateInterface;
use Drupal\media\MediaForm;
use Drupal\views\Form\ViewsForm;
use Drupal\views\ViewExecutable;
/**
* Implements hook_preprocess_HOOK() for HTML document templates.
@ -186,3 +188,189 @@ function seven_form_media_form_alter(&$form, FormStateInterface $form_state) {
function seven_form_language_content_settings_form_alter(array &$form, FormStateInterface $form_state) {
$form['#attached']['library'][] = 'seven/layout_builder_content_translation_admin';
}
/**
* Implements hook_preprocess_views_view_fields().
*
* This targets each rendered media item in the grid display of the media
* library's modal dialog.
*/
function seven_preprocess_views_view_fields__media_library(array &$variables) {
// Add classes to media rendered entity field so it can be targeted for
// styling. Adding this class in a template is very difficult to do.
if (isset($variables['fields']['rendered_entity']->wrapper_attributes)) {
$variables['fields']['rendered_entity']->wrapper_attributes->addClass('media-library-item__click-to-select-trigger');
}
}
/**
* Implements hook_form_alter().
*/
function seven_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) {
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';
}
$form['actions']['submit']['#attributes']['class'] = ['media-library-select'];
}
// Add after build to add a CSS class to the form actions.
if ($form_id === 'views_exposed_form' && strpos($form['#id'], 'views-exposed-form-media-library-widget') === 0) {
$form['actions']['#attributes']['class'][] = 'media-library-view--form-actions';
}
}
/**
* Implements hook_form_BASE_FORM_ID_alter().
*/
function seven_form_media_library_add_form_alter(array &$form, FormStateInterface $form_state) {
$form['#attributes']['class'][] = 'media-library-add-form';
$form['#attached']['library'][] = 'seven/media_library';
// If there are unsaved media items, apply styling classes to various parts
// of the form.
if (isset($form['media'])) {
$form['#attributes']['class'][] = 'media-library-add-form--with-input';
// Put a wrapper around the informational message above the unsaved media
// items.
$form['description']['#template'] = '<p class="media-library-add-form__description">{{ text }}</p>';
}
else {
$form['#attributes']['class'][] = 'media-library-add-form--without-input';
}
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function seven_form_media_library_add_form_upload_alter(array &$form, FormStateInterface $form_state) {
$form['attributes']['class'][] = 'media-library-add-form--upload';
if (isset($form['container'])) {
$form['container']['#attributes']['class'][] = 'media-library-add-form__input-wrapper';
}
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function seven_form_media_library_add_form_oembed_alter(array &$form, FormStateInterface $form_state) {
$form['attributes']['class'][] = 'media-library-add-form--oembed';
// If no media items have been added yet, add a couple of styling classes
// to the initial URL form.
if (isset($form['container'])) {
$form['container']['#attributes']['class'][] = 'media-library-add-form__input-wrapper';
$form['container']['url']['#attributes']['class'][] = 'media-library-add-form-oembed-url';
$form['container']['submit']['#attributes']['class'][] = 'media-library-add-form-oembed-submit';
}
}
/**
* Implements hook_preprocess_item_list__media_library_add_form_media_list().
*
* This targets each new, unsaved media item added to the media library, before
* they are saved.
*/
function seven_preprocess_item_list__media_library_add_form_media_list(array &$variables) {
foreach ($variables['items'] as &$item) {
$item['value']['preview']['#attributes']['class'][] = 'media-library-add-form__preview';
$item['value']['fields']['#attributes']['class'][] = 'media-library-add-form__fields';
$item['value']['remove_button']['#attributes']['class'][] = 'media-library-add-form__remove-button';
// #source_field_name is set by AddFormBase::buildEntityFormElement()
// to help themes and form_alter hooks identify the source field.
$fields = &$item['value']['fields'];
$source_field_name = $fields['#source_field_name'];
if (isset($fields[$source_field_name])) {
$fields[$source_field_name]['#attributes']['class'][] = 'media-library-add-form__source-field';
}
}
}
/**
* Implements hook_preprocess_media_library_item__widget().
*
* This targets each media item selected in an entity reference field.
*/
function seven_preprocess_media_library_item__widget(array &$variables) {
$variables['content']['remove_button']['#attributes']['class'][] = 'media-library-item__remove';
}
/**
* Implements hook_preprocess_media_library_item__small().
*
* This targets each pre-selected media item selected when adding new media in
* the modal media library dialog.
*/
function seven_preprocess_media_library_item__small(array &$variables) {
$variables['content']['select']['#attributes']['class'][] = 'media-library-item__click-to-select-checkbox';
}
/**
* @todo Remove this when https://www.drupal.org/project/drupal/issues/2999549
* lands.
*
* @see \Drupal\media_library\Plugin\Field\FieldWidget\MediaLibraryWidget::formElement()
*/
function seven_preprocess_fieldset__media_library_widget(array &$variables) {
if (isset($variables['prefix']['weight_toggle'])) {
$variables['prefix']['weight_toggle']['#attributes']['class'][] = 'media-library-widget__toggle-weight';
}
if (isset($variables['suffix']['open_button'])) {
$variables['suffix']['open_button']['#attributes']['class'][] = 'media-library-open-button';
}
}
/**
* Implements hook_views_pre_render().
*/
function seven_views_pre_render(ViewExecutable $view) {
$add_classes = function (&$option, array $classes_to_add) {
$classes = preg_split('/\s+/', $option);
$classes = array_filter($classes);
$classes = array_merge($classes, $classes_to_add);
$option = implode(' ', array_unique($classes));
};
if ($view->id() === 'media_library') {
if ($view->display_handler->options['defaults']['css_class']) {
$add_classes($view->displayHandlers->get('default')->options['css_class'], ['media-library-view']);
}
else {
$add_classes($view->display_handler->options['css_class'], ['media-library-view']);
}
if ($view->current_display === 'page') {
if (array_key_exists('media_bulk_form', $view->field)) {
$add_classes($view->field['media_bulk_form']->options['element_class'], ['media-library-item__click-to-select-checkbox']);
}
if (array_key_exists('rendered_entity', $view->field)) {
$add_classes($view->field['rendered_entity']->options['element_class'], ['media-library-item__content']);
}
if (array_key_exists('edit_media', $view->field)) {
$add_classes($view->field['edit_media']->options['alter']['link_class'], ['media-library-item__edit']);
}
if (array_key_exists('delete_media', $view->field)) {
$add_classes($view->field['delete_media']->options['alter']['link_class'], ['media-library-item__remove']);
}
}
elseif (strpos($view->current_display, 'widget') === 0) {
if (array_key_exists('rendered_entity', $view->field)) {
$add_classes($view->field['rendered_entity']->options['element_class'], ['media-library-item__content']);
}
if (array_key_exists('media_library_select_form', $view->field)) {
$add_classes($view->field['media_library_select_form']->options['element_wrapper_class'], ['media-library-item__click-to-select-checkbox']);
}
if ($view->display_handler->options['defaults']['css_class']) {
$add_classes($view->displayHandlers->get('default')->options['css_class'], ['media-library-view--widget']);
}
else {
$add_classes($view->display_handler->options['css_class'], ['media-library-view--widget']);
}
}
}
}

View File

@ -0,0 +1,20 @@
{% extends "details.html.twig" %}
{#
/**
* @file
* Theme override for the "Additional selected media" area of the modal media
* library dialog.
*
* Available variables
* - attributes: A list of HTML attributes for the details element.
* - errors: (optional) Any errors for this details element, may not be set.
* - title: (optional) The title of the element, may not be set.
* - summary_attributes: A list of HTML attributes for the summary element.
* - description: (optional) The description of the element, may not be set.
* - children: (optional) The children of the element, may not be set.
* - value: (optional) The value of the element, may not be set.
*
* @see template_preprocess_details()
*/
#}
{% set attributes = attributes.addClass('media-library-add-form__selected-media seven-details') %}

View File

@ -0,0 +1,64 @@
{#
/**
* @file
* Theme override for the media library widget.
*
* Available variables:
* - attributes: HTML attributes for the fieldset element.
* - errors: (optional) Any errors for this fieldset element, may not be set.
* - required: Boolean indicating whether the fieldeset element is required.
* - legend: The legend element containing the following properties:
* - title: Title of the fieldset, intended for use as the text of the legend.
* - attributes: HTML attributes to apply to the legend.
* - description: The description element containing the following properties:
* - content: The description content of the fieldset.
* - attributes: HTML attributes to apply to the description container.
* - children: The rendered child elements of the fieldset.
* - prefix: The content to add before the fieldset children.
* - suffix: The content to add after the fieldset children.
*
* @see seven_preprocess_fieldset__media_library_widget()
* @see template_preprocess_fieldset()
*/
#}
{%
set classes = [
'js-form-item',
'form-item',
'js-form-wrapper',
'form-wrapper',
'media-library-widget',
]
%}
<fieldset{{ attributes.addClass(classes) }}>
{%
set legend_span_classes = [
'fieldset-legend',
required ? 'js-form-required',
required ? 'form-required',
]
%}
{# Always wrap fieldset legends in a <span> for CSS positioning. #}
<legend{{ legend.attributes }}>
<span{{ legend_span.attributes.addClass(legend_span_classes) }}>{{ legend.title }}</span>
</legend>
<div class="fieldset-wrapper">
{% if errors %}
<div class="form-item--error-message">
<strong>{{ errors }}</strong>
</div>
{% endif %}
{% if prefix.empty_selection %}
<p class="media-library-widget-empty-text">{{ prefix.empty_selection }}</p>
{% elseif prefix.weight_toggle %}
{{ prefix.weight_toggle }}
{% endif %}
{{ children }}
{% if suffix %}
<span class="field-suffix">{{ suffix }}</span>
{% endif %}
{% if description.content %}
<div{{ description.attributes.addClass('description') }}>{{ description.content }}</div>
{% endif %}
</div>
</fieldset>

View File

@ -0,0 +1,33 @@
{#
/**
* @file
* Theme override for a list of new, unsaved media items being added in the
* modal media library dialog.
*
* Available variables:
* - items: A list of items. Each item contains:
* - attributes: HTML attributes to be applied to each list item.
* - value: The content of the list element.
* - title: The title of the list.
* - list_type: The tag for list element ("ul" or "ol").
* - wrapper_attributes: HTML attributes to be applied to the list wrapper.
* - attributes: HTML attributes to be applied to the list.
* - empty: A message to display when there are no items. Allowed value is a
* string or render array.
* - context: A list of contextual data associated with the list. May contain:
* - list_style: The custom list style.
*
* @see seven_preprocess_item_list__media_library_add_form_media_list()
* @see template_preprocess_item_list()
*/
#}
{% if items -%}
{%- if title is not empty -%}
<h3>{{ title }}</h3>
{%- endif -%}
<{{ list_type }}{{ attributes.addClass('media-library-add-form__added-media') }}>
{%- for item in items -%}
<li{{ item.attributes.addClass('media-library-add-form__media') }}>{{ item.value }}</li>
{%- endfor -%}
</{{ list_type }}>
{%- endif %}

View File

@ -0,0 +1,95 @@
{#
/**
* @file
* Theme override for the media_library view template.
*
* Available variables:
* - attributes: Remaining HTML attributes for the element.
* - css_name: A css-safe version of the view name.
* - css_class: The user-specified classes names, if any.
* - header: The optional header.
* - footer: The optional footer.
* - rows: The results of the view query, if any.
* - empty: The content to display if there are no rows.
* - pager: The optional pager next/prev links to display.
* - exposed: Exposed widget form/info to display.
* - feed_icons: Optional feed icons to display.
* - more: An optional link to the next page of results.
* - title: Title of the view, only used when displaying in the admin preview.
* - title_prefix: Additional output populated by modules, intended to be
* displayed in front of the view title.
* - title_suffix: Additional output populated by modules, intended to be
* displayed after the view title.
* - attachment_before: An optional attachment view to be displayed before the
* view content.
* - attachment_after: An optional attachment view to be displayed after the
* view content.
* - dom_id: Unique id for every view being printed to give unique class for
* Javascript.
*
* @see template_preprocess_views_view()
*/
#}
{%
set classes = [
'view',
'view-' ~ id|clean_class,
'view-id-' ~ id,
'view-display-id-' ~ display_id,
dom_id ? 'js-view-dom-id-' ~ dom_id,
]
%}
<div{{ attributes.addClass(classes) }}>
{{ title_prefix }}
{% if title %}
{{ title }}
{% endif %}
{{ title_suffix }}
{% if exposed %}
<div class="view-filters">
{{ exposed }}
</div>
{% endif %}
{% if header %}
<div class="view-header">
{{ header }}
</div>
{% endif %}
{% if attachment_before %}
<div class="attachment attachment-before">
{{ attachment_before }}
</div>
{% endif %}
{% if rows %}
<div class="view-content">
{{ rows }}
</div>
{% elseif empty %}
<div class="view-empty">
{{ empty }}
</div>
{% endif %}
{% if pager %}
{{ pager }}
{% endif %}
{% if attachment_after %}
<div class="attachment attachment-after">
{{ attachment_after }}
</div>
{% endif %}
{% if more %}
{{ more }}
{% endif %}
{% if footer %}
<div class="view-footer">
{{ footer }}
</div>
{% endif %}
{% if feed_icons %}
<div class="feed-icons">
{{ feed_icons }}
</div>
{% endif %}
</div>

View File

@ -0,0 +1,34 @@
{#
/**
* @file
* Theme override for the media_library display of unformatted rows.
*
* Available variables:
* - title: The title of this group of rows. May be empty.
* - rows: A list of the view's row items.
* - attributes: The row's HTML attributes.
* - content: The row's content.
* - view: The view object.
* - default_row_class: A flag indicating whether default classes should be
* used on rows.
*
* @see template_preprocess_views_view_unformatted()
*/
#}
{% if title %}
<h3>{{ title }}</h3>
{% endif %}
<div class="media-library-views-form__rows">
{% for row in rows %}
{%
set row_classes = [
default_row_class ? 'views-row',
'media-library-item',
'media-library-item--grid',
]
%}
<div{{ row.attributes.addClass(row_classes) }}>
{{- row.content -}}
</div>
{% endfor %}
</div>

View File

@ -0,0 +1,49 @@
{#
/**
* @file
* Default theme implementation to present a media entity in the media library.
*
* Available variables:
* - media: The entity with limited access to object properties and methods.
* Only method names starting with "get", "has", or "is" and a few common
* methods such as "id", "label", and "bundle" are available. For example:
* - entity.getEntityTypeId() will return the entity type ID.
* - entity.hasField('field_example') returns TRUE if the entity includes
* field_example. (This does not indicate the presence of a value in this
* field.)
* Calling other methods, such as entity.delete(), will result in an exception.
* See \Drupal\Core\Entity\EntityInterface for a full list of methods.
* - name: Name of the media.
* - content: Media content.
* - title_prefix: Additional output populated by modules, intended to be
* displayed in front of the main title tag that appears in the template.
* - title_suffix: Additional output populated by modules, intended to be
* displayed after the main title tag that appears in the template.
* - view_mode: View mode; for example, "teaser" or "full".
* - attributes: HTML attributes for the containing element.
* - title_attributes: Same as attributes, except applied to the main title
* tag that appears in the template.
* - url: Direct URL of the media.
* - preview_attributes: HTML attributes for the preview wrapper.
* - metadata_attributes: HTML attributes for the expandable metadata area.
* - status: Whether or not the Media is published.
*
* @see template_preprocess_media()
* @see media_library_preprocess_media()
*
* @ingroup themeable
*/
#}
<article{{ attributes }}>
{% if content %}
<div{{ preview_attributes.addClass('js-media-library-item-preview') }}>
{{ content|without('name') }}
</div>
{% if not status %}
{{ "unpublished" | t }}
{% endif %}
<div{{ metadata_attributes }}>
{{ name }}
</div>
{% endif %}
</article>

View File

@ -0,0 +1,22 @@
{#
/**
* @file
* Default theme implementation of a media library item.
*
* This is used when displaying selected media items, either in the field
* widget or in the "Additional selected media" area when adding new
* media items in the media library modal dialog.
*
* Available variables:
* - attributes: HTML attributes for the containing element.
* - content: The content of the media library item, plus any additional
* fields or elements surrounding it.
*
* @see template_preprocess_media_library_item()
*
* @ingroup themeable
*/
#}
<div{{ attributes }}>
{{ content }}
</div>

View File

@ -0,0 +1,21 @@
{#
/**
* @file
* Default theme implementation of a container used to wrap the media library's
* modal dialog interface.
*
* Available variables:
* - attributes: HTML attributes for the containing element.
* - menu: The menu of availble media types to choose from.
* - content: The form to add new media items, followed by the grid or table of
* existing media items to choose from.
*
* @see template_preprocess_media_library_wrapper()
*
* @ingroup themeable
*/
#}
<div{{ attributes }}>
{{ menu }}
{{ content }}
</div>