.../modules/media/js/plugins/drupalmedia/plugin.js | 46 +++++++ core/modules/media/media.routing.yml | 8 ++ core/modules/media/src/Form/EditorMediaDialog.php | 138 +++++++++++++++++++++ .../src/Plugin/CKEditorPlugin/DrupalMedia.php | 1 + 4 files changed, 193 insertions(+) diff --git a/core/modules/media/js/plugins/drupalmedia/plugin.js b/core/modules/media/js/plugins/drupalmedia/plugin.js index 947fe9b87e..ed734f7ae0 100644 --- a/core/modules/media/js/plugins/drupalmedia/plugin.js +++ b/core/modules/media/js/plugins/drupalmedia/plugin.js @@ -144,6 +144,7 @@ this._loadPreview(function (widget) { widget._setUpDynamicEditables(); + widget._setUpButtons(); editor.fire('unlockSnapshot'); }); } @@ -186,8 +187,53 @@ if (captionEditable.$.childNodes.length === 1 && captionEditable.$.childNodes.item(0).nodeName === 'BR') { captionEditable.$.removeChild(captionEditable.$.childNodes.item(0)); } + } + }, + /** + * Injects HTML for buttons into the preview that was just loaded. + */ + _setUpButtons: function () { + // No buttons for missing media. + if (this.element.findOne('img[src$="no-thumbnail.png"]')) { + return; } + + var isElementNode = function (n) { return n.type === CKEDITOR.NODE_ELEMENT; }; + + // Find the actual embedded media in the DOM. + var embeddedMediaContainer = this.data.hasCaption ? this.element.findOne('figure') : this.element; + var embeddedMedia = embeddedMediaContainer.getFirst(isElementNode); + if (this.data.link) { + embeddedMedia = embeddedMedia.getFirst(isElementNode); + } + embeddedMedia.setStyle('position', 'relative'); + var editButton = CKEDITOR.dom.element.createFromHtml('' + Drupal.t('Edit media') + ''); + embeddedMedia.getFirst().insertBeforeMe(editButton); + var deleteButton = CKEDITOR.dom.element.createFromHtml('' + Drupal.t('Delete media') + ''); + embeddedMedia.getFirst().insertBeforeMe(deleteButton); + + // Make the buttons do things. + var widget = this; + this.element.findOne('.media-library-item__edit') + .on('click', function (event) { + var saveCallback = function (values) { + event.cancel(); + editor.fire('saveSnapshot'); + if (values.hasOwnProperty('attributes')) { + CKEDITOR.tools.extend(values.attributes, widget.data.attributes) + } + widget.setData({ attributes: values.attributes, hasCaption: !!values.hasCaption }); + editor.fire('saveSnapshot'); + }; + + Drupal.ckeditor.openDialog(editor, Drupal.url('editor/dialog/media/' + editor.config.drupal.format), widget.data, saveCallback, {}); + }); + this.element.findOne('.media-library-item__remove') + .on('click', function(event) { + event.cancel(); + widget.repository.del(widget); + }); }, _tearDownDynamicEditables: function () { diff --git a/core/modules/media/media.routing.yml b/core/modules/media/media.routing.yml index b1d3a5e22a..d91f7d4f15 100644 --- a/core/modules/media/media.routing.yml +++ b/core/modules/media/media.routing.yml @@ -46,3 +46,11 @@ media.filter.preview: _controller: '\Drupal\media\Controller\MediaFilterController::preview' requirements: _entity_access: 'filter_format.use' + +editor.media_dialog: + path: '/editor/dialog/media/{editor}' + defaults: + _form: '\Drupal\media\Form\EditorMediaDialog' + _title: 'Edit media' + requirements: + _entity_access: 'editor.use' diff --git a/core/modules/media/src/Form/EditorMediaDialog.php b/core/modules/media/src/Form/EditorMediaDialog.php new file mode 100644 index 0000000000..dbd2c31e50 --- /dev/null +++ b/core/modules/media/src/Form/EditorMediaDialog.php @@ -0,0 +1,138 @@ +getUserInput()['editor_object'])) { + // By convention, the data that the text editor sends to any dialog is in + // the 'editor_object' key. + $media_embed_element = $form_state->getUserInput()['editor_object']['attributes']; + $form_state->set('media_embed_element', $media_embed_element); + $has_caption = $form_state->getUserInput()['editor_object']['hasCaption']; + $form_state->set('hasCaption', $has_caption); + $form_state->setCached(TRUE); + } + else { + // Retrieve the user input from form state. + $media_embed_element = $form_state->get('media_embed_element'); + $has_caption = $form_state->get('hasCaption'); + } + + $form['#tree'] = TRUE; + $form['#attached']['library'][] = 'editor/drupal.editor.dialog'; + $form['#prefix'] = '
'; + $form['#suffix'] = '
'; + + // When Drupal core's filter_align is being used, the text editor offers the + // ability to change the alignment. + if ($editor->getFilterFormat()->filters('filter_align')->status) { + $form['align'] = [ + '#title' => $this->t('Align'), + '#type' => 'radios', + '#options' => [ + 'none' => $this->t('None'), + 'left' => $this->t('Left'), + 'center' => $this->t('Center'), + 'right' => $this->t('Right'), + ], + '#default_value' => (!isset($media_embed_element['data-align']) || $media_embed_element['data-align'] === '') ? 'none' : $media_embed_element['data-align'], + '#wrapper_attributes' => ['class' => ['container-inline']], + '#attributes' => ['class' => ['container-inline']], + '#parents' => ['attributes', 'data-align'], + ]; + } + + // When Drupal core's filter_caption is being used, the text editor offers + // the ability to in-place edit the media's caption: show a toggle. + if ($editor->getFilterFormat()->filters('filter_caption')->status) { + $form['caption'] = [ + '#title' => $this->t('Caption'), + '#type' => 'checkbox', + '#default_value' => $has_caption === 'true', + '#parents' => ['hasCaption'], + ]; + } + + $form['actions'] = [ + '#type' => 'actions', + ]; + $form['actions']['save_modal'] = [ + '#type' => 'submit', + '#value' => $this->t('Save'), + // No regular submit-handler. This form only works via JavaScript. + '#submit' => [], + '#ajax' => [ + 'callback' => '::submitForm', + 'event' => 'click', + ], + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $response = new AjaxResponse(); + + if ($form_state->getErrors()) { + unset($form['#prefix'], $form['#suffix']); + $form['status_messages'] = [ + '#type' => 'status_messages', + '#weight' => -10, + ]; + $response->addCommand(new HtmlCommand('#editor-media-dialog-form', $form)); + } + else { + // Only send back the relevant values. + $values = [ + 'hasCaption' => $form_state->getValue('hasCaption'), + 'attributes' => $form_state->getValue('attributes'), + ]; + $response->addCommand(new EditorDialogSave($values)); + $response->addCommand(new CloseModalDialogCommand()); + } + + return $response; + } + +} diff --git a/core/modules/media/src/Plugin/CKEditorPlugin/DrupalMedia.php b/core/modules/media/src/Plugin/CKEditorPlugin/DrupalMedia.php index 59b77c11b0..5492bd1cf4 100644 --- a/core/modules/media/src/Plugin/CKEditorPlugin/DrupalMedia.php +++ b/core/modules/media/src/Plugin/CKEditorPlugin/DrupalMedia.php @@ -78,6 +78,7 @@ public function getCssFiles(Editor $editor) { return [ drupal_get_path('module', 'media') . '/css/plugins/drupalmedia/ckeditor.drupalmedia.css', drupal_get_path('module', 'system') . '/css/components/hidden.module.css', + drupal_get_path('module', 'media_library') . '/css/media_library.theme.css', ]; }