diff --git a/core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.js b/core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.js index 42d9e93..f4743a4 100644 --- a/core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.js +++ b/core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.js @@ -7,78 +7,133 @@ (function (CKEDITOR) { - "use strict"; - - CKEDITOR.plugins.add('drupalimagecaption', { - requires: 'widget', - init: function (editor) { - - /** - * Override drupalimage plugin's image insertion mechanism with our own, to - * ensure a widget is inserted, rather than a simple image (Widget's auto- - * discovery only runs upon init). - */ - editor.on('drupalimageinsert', function (event) { - editor.execCommand('widgetDrupalimagecaption'); - event.cancel(); - }); +"use strict"; + +CKEDITOR.plugins.add('drupalimagecaption', { + requires: 'widget', + init: function (editor) { + + /** + * Override drupalimage plugin's image insertion mechanism with our own, to + * ensure a widget is inserted, rather than a simple image (Widget's auto- + * discovery only runs upon init). + */ + editor.on('drupalimageinsert', function (event) { + editor.execCommand('widgetDrupalimagecaption'); + event.cancel(); + }); + + // Register the widget with a unique name "drupalimagecaption". + editor.widgets.add('drupalimagecaption', { + allowedContent: 'img[!src,alt,width,height,!data-caption,!data-align]', + template: '', + parts: { + image: 'img' + }, + + // Parse all attributes, ignoring CKEditor's own additions. + _parseNonCKEAttributes: function (node) { + var data = {}; + var attrs = node.attributes; + for (var i = 0; i < attrs.length; i++) { + var attr = attrs.item(i); + // Ignore CKEditor's 'data-widget' and data-cke-saved-' attributes. + if (attr.name === 'data-widget' || attr.name.substr(0, 15) === 'data-cke-saved-') { + continue; + } + data[attr.name] = attr.value; + } + // Ignore CKEditor's 'cke_widget_element' class. + if (data.class) { + data.class = data.class.replace('cke_widget_element', '').trim(); + if (data.class.length === 0) { + delete data.class; + } + } - // Register the widget with a unique name "drupalimagecaption". - editor.widgets.add('drupalimagecaption', { - allowedContent: 'img[!src,alt,width,height,!data-caption,!data-align]', - template: '', - parts: { - image: 'img' - }, + return data; + }, + + // Initialization method called for every widget instance being + // upcasted. + init: function () { + var image = this.parts.image; + + // Parse all attributes; we want to persist all attributes, not just the + // ones this plugin handles! + var data = this._parseNonCKEAttributes(image.$); + + // Transform the 'data-caption' and 'data-align' attributes into the + // form expected by this CKEditor plugin. + // @todo Ideally, migrate away from the "data_something" variants. + data['data_caption'] = null; + if (data['data-caption']) { + data['data_caption'] = data['data-caption']; + delete data['data-caption']; + } + data['data_align'] = null; + if (data['data-align']) { + data['data_align'] = data['data-align']; + delete data['data-align']; + } + data.hasCaption = image.hasAttribute('data-caption'); - // Initialization method called for every widget instance being - // upcasted. - init: function () { - var image = this.parts.image; - - // Save the initial widget data. - this.setData({ - 'data-editor-file-uuid': image.getAttribute('data-editor-file-uuid'), - src: image.getAttribute('src'), - width: image.getAttribute('width') || '', - height: image.getAttribute('height') || '', - alt: image.getAttribute('alt') || '', - data_caption: image.getAttribute('data-caption'), - data_align: image.getAttribute('data-align'), - hasCaption: image.hasAttribute('data-caption') - }); + // Save the initial widget data. + this.setData(data); + + image.removeStyle('float'); + }, + // Called after initialization and on "data" changes. + data: function () { + if (this.data['data-editor-file-uuid'] !== null) { + this.parts.image.setAttribute('data-editor-file-uuid', this.data['data-editor-file-uuid']); + this.parts.image.setAttribute('data-cke-saved-data-editor-file-uuid', this.data['data-editor-file-uuid']); + } + this.parts.image.setAttribute('src', this.data.src); + this.parts.image.setAttribute('data-cke-saved-src', this.data.src); + this.parts.image.setAttribute('alt', this.data.alt); + this.parts.image.setAttribute('data-cke-saved-alt', this.data.alt); + this.parts.image.setAttribute('width', this.data.width); + this.parts.image.setAttribute('data-cke-saved-width', this.data.width); + this.parts.image.setAttribute('height', this.data.height); + this.parts.image.setAttribute('data-cke-saved-height', this.data.height); + if (this.data.hasCaption) { + this.parts.image.setAttribute('data-caption', this.data.data_caption); + this.parts.image.setAttribute('data-cke-saved-data-caption', this.data.data_caption); + } + else { + this.parts.image.removeAttributes(['data-caption', 'data-cke-saved-data-caption']); + } + if (this.data.data_align !== null) { + this.parts.image.setAttribute('data-align', this.data.data_align); + this.parts.image.setAttribute('data-cke-saved-data-align', this.data.data_align); + } + else { + this.parts.image.removeAttributes(['data-align', 'data-cke-saved-data-align']); + } image.removeStyle('float'); }, - // Called after initialization and on "data" changes. - data: function () { - if (this.data['data-editor-file-uuid'] !== null) { - this.parts.image.setAttribute('data-editor-file-uuid', this.data['data-editor-file-uuid']); - this.parts.image.setAttribute('data-cke-saved-data-editor-file-uuid', this.data['data-editor-file-uuid']); - } - this.parts.image.setAttribute('src', this.data.src); - this.parts.image.setAttribute('data-cke-saved-src', this.data.src); - this.parts.image.setAttribute('alt', this.data.alt); - this.parts.image.setAttribute('data-cke-saved-alt', this.data.alt); - this.parts.image.setAttribute('width', this.data.width); - this.parts.image.setAttribute('data-cke-saved-width', this.data.width); - this.parts.image.setAttribute('height', this.data.height); - this.parts.image.setAttribute('data-cke-saved-height', this.data.height); - if (this.data.hasCaption) { - this.parts.image.setAttribute('data-caption', this.data.data_caption); - this.parts.image.setAttribute('data-cke-saved-data-caption', this.data.data_caption); - } - else { - this.parts.image.removeAttributes(['data-caption', 'data-cke-saved-data-caption']); - } - if (this.data.data_align !== null) { - this.parts.image.setAttribute('data-align', this.data.data_align); - this.parts.image.setAttribute('data-cke-saved-data-align', this.data.data_align); - } - else { - this.parts.image.removeAttributes(['data-align', 'data-cke-saved-data-align']); + + // Convert the element back to its desired output representation. + downcast: function (el) { + // Set all known attributes. + for (var key in this.data) { + if (this.data.hasOwnProperty(key)) { + // Skip over the 'data_caption', 'data_align' and 'hasCaption' + // values, because those don't map 1:1 to attributes. + // @todo Ideally, get rid of these. + if (key === 'data_align' || key === 'data_caption' || key === 'hasCaption') { + continue; + } + el.attributes[key] = this.data[key]; } + } + + if (this.data.hasCaption) { + el.attributes['data-caption'] = this.data.data_caption; + } // Float the wrapper too. if (this.data.data_align === null) { @@ -168,7 +223,7 @@ html += ' />'; var el = new CKEDITOR.dom.element.createFromHtml(html, editor.document); editor.insertElement(editor.widgets.wrapElement(el, 'drupalimagecaption')); - + // Save snapshot for undo support. editor.fire('saveSnapshot'); @@ -196,16 +251,11 @@ var saveCallback = function (returnValues) { editor.fire('saveSnapshot'); // Set the updated widget data. - that.setData({ - 'data-editor-file-uuid': returnValues.attributes['data-editor-file-uuid'], - src: returnValues.attributes.src, - width: returnValues.attributes.width, - height: returnValues.attributes.height, - alt: returnValues.attributes.alt, - hasCaption: !!returnValues.hasCaption, - data_caption: returnValues.hasCaption ? that.data.data_caption : '', - data_align: returnValues.attributes.data_align === 'none' ? null : returnValues.attributes.data_align - }); + var data = returnValues.attributes; + data.hasCaption = !!returnValues.hasCaption; + data.data_caption = returnValues.hasCaption ? that.data.data_caption : ''; + data.data_align = returnValues.attributes.data_align === 'none' ? null : returnValues.attributes.data_align; + that.setData(data); // Save snapshot for undo support. editor.fire('saveSnapshot'); }; diff --git a/core/modules/editor/lib/Drupal/editor/Form/EditorImageDialog.php b/core/modules/editor/lib/Drupal/editor/Form/EditorImageDialog.php index b3512b5..176f3e6 100644 --- a/core/modules/editor/lib/Drupal/editor/Form/EditorImageDialog.php +++ b/core/modules/editor/lib/Drupal/editor/Form/EditorImageDialog.php @@ -130,6 +130,8 @@ public function buildForm(array $form, array &$form_state, FilterFormat $filter_ '#parents' => array('attributes', 'height'), ); + $filters = $filter_format->filters()->getAll(); + // When Drupal core's filter_caption is being used, the text editor may // offer the ability to change the alignment. if (isset($image_element['data_align'])) { @@ -178,6 +180,9 @@ public function buildForm(array $form, array &$form_state, FilterFormat $filter_ $form['actions']['save_modal'] = array( '#type' => 'submit', '#value' => $this->t('Save'), + '#validate' => array( + array($this, 'validateForm'), + ), // No regular submit-handler. This form only works via JavaScript. '#submit' => array(), '#ajax' => array( @@ -192,9 +197,7 @@ public function buildForm(array $form, array &$form_state, FilterFormat $filter_ /** * {@inheritdoc} */ - public function submitForm(array &$form, array &$form_state) { - $response = new AjaxResponse(); - + public function validateForm(array &$form, array &$form_state) { // Convert any uploaded files from the FID values to data-editor-file-uuid // attributes. if (!empty($form_state['values']['fid'][0])) { @@ -206,6 +209,13 @@ public function submitForm(array &$form, array &$form_state) { $form_state['values']['attributes']['src'] = $file_url; $form_state['values']['attributes']['data-editor-file-uuid'] = $file->uuid(); } + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, array &$form_state) { + $response = new AjaxResponse(); if (form_get_errors($form_state)) { unset($form['#prefix'], $form['#suffix']); diff --git a/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterCaption.php b/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterCaption.php index 5b5ce63..e91b7c4 100644 --- a/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterCaption.php +++ b/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterCaption.php @@ -13,7 +13,7 @@ use Drupal\filter\Plugin\FilterBase; /** - * Provides a filter to display image captions and align images. + * Provides a filter to caption and align images (as well as other elements). * * @Filter( * id = "filter_caption", diff --git a/core/modules/image/css/image.admin.css b/core/modules/image/css/image.admin.css index 3e1f901..b398bcd 100644 --- a/core/modules/image/css/image.admin.css +++ b/core/modules/image/css/image.admin.css @@ -16,6 +16,7 @@ .image-style-preview .preview-image { margin: auto; position: relative; + display: block; } .image-style-preview .preview-image .width { border: 1px solid #666; diff --git a/core/modules/image/image.admin.inc b/core/modules/image/image.admin.inc index 9675e98..d982d52 100644 --- a/core/modules/image/image.admin.inc +++ b/core/modules/image/image.admin.inc @@ -136,7 +136,7 @@ function theme_image_style_preview($variables) { '#attributes' => $original_image, ); $output .= '
' . implode('
, ', array_keys($image_styles)) . '
';
+ return t('
+ You can display images using site-wide styles by adding a data-image-style
attribute, whose values is one of the image style machine names: !image-style-machine-name-list.
' . implode('
, ', array_keys($picture_mappings)) . '
';
+ return t('
+ You can make images responsive by adding a data-picture-mapping
attribute, whose values is one of the picture mapping machine names: !picture-mapping-machine-name-list.
-
-
-
'
filter_html_help: false
filter_html_nofollow: false
+ filter_picturemapping:
+ id: filter_picturemapping
+ module: filter
+ status: 1
+ weight: 7
+ settings: { }
filter_caption:
id: filter_caption
provider: filter
diff --git a/core/profiles/standard/config/filter.format.full_html.yml b/core/profiles/standard/config/filter.format.full_html.yml
index 37daecc..dc2f590 100644
--- a/core/profiles/standard/config/filter.format.full_html.yml
+++ b/core/profiles/standard/config/filter.format.full_html.yml
@@ -7,6 +7,12 @@ roles:
- administrator
cache: true
filters:
+ filter_picturemapping:
+ id: filter_picturemapping
+ module: filter
+ status: 1
+ weight: 8
+ settings: { }
filter_caption:
id: filter_caption
provider: filter
diff --git a/core/profiles/standard/config/picture.mappings.standard_responsive_image.yml b/core/profiles/standard/config/picture.mappings.standard_responsive_image.yml
new file mode 100644
index 0000000..25af37d
--- /dev/null
+++ b/core/profiles/standard/config/picture.mappings.standard_responsive_image.yml
@@ -0,0 +1,13 @@
+id: standard_responsive_image
+uuid: 789bae80-c66c-4e38-bf00-bb450f4bac33
+label: 'Standard responsive image'
+mappings:
+ module.toolbar.narrow:
+ 1x: thumbnail
+ module.toolbar.standard:
+ 1x: medium
+ module.toolbar.wide:
+ 1x: large
+breakpointGroup: module.toolbar.toolbar
+status: true
+langcode: en
diff --git a/core/profiles/standard/standard.info.yml b/core/profiles/standard/standard.info.yml
index ba41dd5..0054462 100644
--- a/core/profiles/standard/standard.info.yml
+++ b/core/profiles/standard/standard.info.yml
@@ -25,6 +25,7 @@ dependencies:
- number
- options
- path
+ - picture
- taxonomy
- dblog
- search