diff --git a/core/modules/ckeditor/js/plugins/drupalimage/plugin.js b/core/modules/ckeditor/js/plugins/drupalimage/plugin.js index 2cd6254..3e45eff 100644 --- a/core/modules/ckeditor/js/plugins/drupalimage/plugin.js +++ b/core/modules/ckeditor/js/plugins/drupalimage/plugin.js @@ -30,7 +30,17 @@ } // Override requiredContent & allowedContent. - widgetDefinition.requiredContent = 'img[alt,src,width,height,data-entity-type,data-entity-uuid]'; + widgetDefinition.requiredContent = new CKEDITOR.style({ + element: 'img', + attributes: { + 'alt' : '', + 'src': '', + 'width': '', + 'height': '', + 'data-entity-type': '', + 'data-entity-uuid': '' + } + }); widgetDefinition.allowedContent.img.attributes += ',!data-entity-type,!data-entity-uuid'; // We don't allow
,
,
or

in our downcast. delete widgetDefinition.allowedContent.figure; diff --git a/core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.js b/core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.js index f61a545..5461914 100644 --- a/core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.js +++ b/core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.js @@ -51,7 +51,10 @@ }, true); // Override requiredContent & allowedContent. - widgetDefinition.requiredContent = 'img[alt,src,width,height,data-entity-type,data-entity-uuid,data-align,data-caption]'; + var requiredContent = widgetDefinition.requiredContent.getDefinition(); + requiredContent.attributes['data-align'] = ''; + requiredContent.attributes['data-caption'] = ''; + widgetDefinition.requiredContent = new CKEDITOR.style(requiredContent); widgetDefinition.allowedContent.img.attributes += ',!data-align,!data-caption'; // Override allowedContent setting for the 'caption' nested editable. @@ -63,9 +66,12 @@ // Override downcast(): ensure we *only* output , but also ensure // we include the data-entity-type, data-entity-uuid, data-align and // data-caption attributes. + var originalDowncast = widgetDefinition.downcast; widgetDefinition.downcast = function (element) { - // Find an image element in the one being downcasted (can be itself). - var img = findElementByName(element, 'img'); + var img = originalDowncast.call(this, element); + if (!img) { + img = findElementByName(element, 'img'); + } var caption = this.editables.caption; var captionHtml = caption && caption.getData(); var attrs = img.attributes; @@ -94,6 +100,7 @@ // - tag in a paragraph (non-captioned, centered image), // -

tag (captioned image). // We take the same attributes into account as downcast() does. + var originalUpcast = widgetDefinition.upcast; widgetDefinition.upcast = function (element, data) { if (element.name !== 'img' || !element.attributes['data-entity-type'] || !element.attributes['data-entity-uuid']) { return; @@ -103,6 +110,7 @@ return; } + element = originalUpcast.call(this, element, data); var attrs = element.attributes; var retElement = element; var caption; diff --git a/core/modules/ckeditor/js/plugins/drupallink/plugin.js b/core/modules/ckeditor/js/plugins/drupallink/plugin.js index 4d0832a..f4db09e 100644 --- a/core/modules/ckeditor/js/plugins/drupallink/plugin.js +++ b/core/modules/ckeditor/js/plugins/drupallink/plugin.js @@ -13,8 +13,17 @@ init: function (editor) { // Add the commands for link and unlink. editor.addCommand('drupallink', { - allowedContent: 'a[!href,target]', - requiredContent: 'a[href]', + allowedContent: { + a: { + attributes: '!href,target' + } + }, + requiredContent: new CKEDITOR.style({ + element: 'a', + attributes: { + href: '' + } + }), modes: {wysiwyg: 1}, canUndo: true, exec: function (editor) { @@ -111,8 +120,17 @@ editor.addCommand('drupalunlink', { contextSensitive: 1, startDisabled: 1, - allowedContent: 'a[!href]', - requiredContent: 'a[href]', + allowedContent: { + a: { + attributes: '!href,target' + } + }, + requiredContent: new CKEDITOR.style({ + element: 'a', + attributes: { + href: '' + } + }), exec: function (editor) { var style = new CKEDITOR.style({element: 'a', type: CKEDITOR.STYLE_INLINE, alwaysRemoveElement: 1}); editor.removeStyle(style); diff --git a/core/modules/image/js/plugins/drupalimagestyle/plugin.js b/core/modules/image/js/plugins/drupalimagestyle/plugin.js new file mode 100644 index 0000000..7b673ca --- /dev/null +++ b/core/modules/image/js/plugins/drupalimagestyle/plugin.js @@ -0,0 +1,80 @@ +/** + * @file + * Drupal Image Caption plugin. + * + * This alters the existing CKEditor image2 widget plugin, which is already + * altered by the Drupal Image plugin, to: + * - allow for the data-caption and data-align attributes to be set + * - mimic the upcasting behavior of the caption_filter filter. + * + * @ignore + */ + +(function (CKEDITOR) { + + "use strict"; + + CKEDITOR.plugins.add('drupalimagestyle', { + requires: 'drupalimage', + + beforeInit: function (editor) { + // Override the image2 widget definition to handle the additional + // data-responsive-image-style attributes. + editor.on('widgetDefinition', function (event) { + var widgetDefinition = event.data; + if (widgetDefinition.name !== 'image') { + return; + } + // Override default features definitions for drupalimagecaption. + CKEDITOR.tools.extend(widgetDefinition.features, { + responsiveimage: { + requiredContent: 'img[data-image-style]' + } + }, true); + + // Override requiredContent & allowedContent. + var requiredContent = widgetDefinition.requiredContent.getDefinition(); + requiredContent.attributes['data-image-style'] = ''; + widgetDefinition.requiredContent = new CKEDITOR.style(requiredContent); + widgetDefinition.allowedContent.img.attributes += ',!data-image-style'; + + // Override downcast(). + var originalDowncast = widgetDefinition.downcast; + widgetDefinition.downcast = function (element) { + var img = originalDowncast.call(this, element); + if (!img) { + img = findElementByName(element, 'img'); + } + img.attributes['data-image-style'] = this.data['data-image-style']; + return img; + }; + + // Override upcast(). + var originalUpcast = widgetDefinition.upcast; + widgetDefinition.upcast = function (element, data) { + if (element.name !== 'img' || !element.attributes['data-entity-type'] || !element.attributes['data-entity-uuid']) { + return; + } + // Don't initialize on pasted fake objects. + else if (element.attributes['data-cke-realelement']) { + return; + } + element = originalUpcast.call(this, element, data); + + // Parse the data-responsive-image-style attribute. + data['data-image-style'] = element.attributes['data-image-style']; + + return element; + }; + + // Protected; keys of the widget data to be sent to the Drupal dialog. + // Append to the values defined by the drupalimage plugin. + // @see core/modules/ckeditor/js/plugins/drupalimage/plugin.js + CKEDITOR.tools.extend(widgetDefinition._mapDataToDialog, { + 'data-image-style': 'data-image-style', + }); + // Low priority to ensure drupalimage's event handler runs first. + }, null, null, 20); + } + }); +})(CKEDITOR); diff --git a/core/modules/image/src/Plugin/CKEditorPlugin/DrupalImageStyle.php b/core/modules/image/src/Plugin/CKEditorPlugin/DrupalImageStyle.php new file mode 100644 index 0000000..2b87534 --- /dev/null +++ b/core/modules/image/src/Plugin/CKEditorPlugin/DrupalImageStyle.php @@ -0,0 +1,91 @@ +hasAssociatedFilterFormat()) { + return FALSE; + } + + // Automatically enable this plugin if the text format associated with this + // text editor uses the filter_responsive_image_style filter and the + // DrupalImage button is enabled. + $format = $editor->getFilterFormat(); + if ($format->filters('filter_imagestyle')->status) { + $enabled = FALSE; + $settings = $editor->getSettings(); + foreach ($settings['toolbar']['rows'] as $row) { + foreach ($row as $group) { + foreach ($group['items'] as $button) { + if ($button === 'DrupalImage') { + $enabled = TRUE; + } + } + } + } + return $enabled; + } + + return FALSE; + } + +} diff --git a/core/modules/image/src/Plugin/Filter/FilterImageStyle.php b/core/modules/image/src/Plugin/Filter/FilterImageStyle.php index d665071..bd6eea0 100644 --- a/core/modules/image/src/Plugin/Filter/FilterImageStyle.php +++ b/core/modules/image/src/Plugin/Filter/FilterImageStyle.php @@ -41,9 +41,9 @@ public function process($text, $langcode) { $dom = HTML::load($text); $xpath = new \DOMXPath($dom); - foreach ($xpath->query('//*[@data-editor-file-uuid and @data-image-style]') as $node) { - $file_uuid = $node->getAttribute('data-editor-file-uuid'); - $node->removeAttribute('data-editor-file-uuid'); + foreach ($xpath->query('//*[@data-entity-uuid and @data-image-style]') as $node) { + $file_uuid = $node->getAttribute('data-entity-uuid'); + $node->removeAttribute('data-entity-uuid'); $image_style_id = $node->getAttribute('data-image-style'); $node->removeAttribute('data-image-style'); @@ -52,7 +52,7 @@ public function process($text, $langcode) { continue; } - $file = entity_load_by_uuid('file', $file_uuid); + $file = \Drupal::entityManager()->loadEntityByUuid('file', $file_uuid); // Determine width/height of the source image. $width = $height = NULL; diff --git a/core/modules/responsive_image/js/plugins/drupalresponsiveimage/plugin.js b/core/modules/responsive_image/js/plugins/drupalresponsiveimage/plugin.js new file mode 100644 index 0000000..fe2bf47 --- /dev/null +++ b/core/modules/responsive_image/js/plugins/drupalresponsiveimage/plugin.js @@ -0,0 +1,80 @@ +/** + * @file + * Drupal Image Caption plugin. + * + * This alters the existing CKEditor image2 widget plugin, which is already + * altered by the Drupal Image plugin, to: + * - allow for the data-caption and data-align attributes to be set + * - mimic the upcasting behavior of the caption_filter filter. + * + * @ignore + */ + +(function (CKEDITOR) { + + "use strict"; + + CKEDITOR.plugins.add('drupalresponsiveimage', { + requires: 'drupalimage', + + beforeInit: function (editor) { + // Override the image2 widget definition to handle the additional + // data-responsive-image-style attributes. + editor.on('widgetDefinition', function (event) { + var widgetDefinition = event.data; + if (widgetDefinition.name !== 'image') { + return; + } + // Override default features definitions for drupalimagecaption. + CKEDITOR.tools.extend(widgetDefinition.features, { + responsiveimage: { + requiredContent: 'img[data-responsive-image-style]' + } + }, true); + + // Override requiredContent & allowedContent. + var requiredContent = widgetDefinition.requiredContent.getDefinition(); + requiredContent.attributes['data-responsive-image-style'] = ''; + widgetDefinition.requiredContent = new CKEDITOR.style(requiredContent); + widgetDefinition.allowedContent.img.attributes += ',!data-responsive-image-style'; + + // Override downcast(). + var originalDowncast = widgetDefinition.downcast; + widgetDefinition.downcast = function (element) { + var img = originalDowncast.call(this, element); + if (!img) { + img = findElementByName(element, 'img'); + } + img.attributes['data-responsive-image-style'] = this.data['data-responsive-image-style']; + return img; + }; + + // Override upcast(). + var originalUpcast = widgetDefinition.upcast; + widgetDefinition.upcast = function (element, data) { + if (element.name !== 'img' || !element.attributes['data-entity-type'] || !element.attributes['data-entity-uuid']) { + return; + } + // Don't initialize on pasted fake objects. + else if (element.attributes['data-cke-realelement']) { + return; + } + element = originalUpcast.call(this, element, data); + + // Parse the data-responsive-image-style attribute. + data['data-responsive-image-style'] = element.attributes['data-responsive-image-style']; + + return element; + }; + + // Protected; keys of the widget data to be sent to the Drupal dialog. + // Append to the values defined by the drupalimage plugin. + // @see core/modules/ckeditor/js/plugins/drupalimage/plugin.js + CKEDITOR.tools.extend(widgetDefinition._mapDataToDialog, { + 'data-responsive-image-style': 'data-responsive-image-style', + }); + // Low priority to ensure drupalimage's event handler runs first. + }, null, null, 20); + } + }); +})(CKEDITOR); diff --git a/core/modules/responsive_image/src/Plugin/CKEditorPlugin/DrupalResponsiveImage.php b/core/modules/responsive_image/src/Plugin/CKEditorPlugin/DrupalResponsiveImage.php new file mode 100644 index 0000000..7fd4a67 --- /dev/null +++ b/core/modules/responsive_image/src/Plugin/CKEditorPlugin/DrupalResponsiveImage.php @@ -0,0 +1,91 @@ +hasAssociatedFilterFormat()) { + return FALSE; + } + + // Automatically enable this plugin if the text format associated with this + // text editor uses the filter_responsive_image_style filter and the + // DrupalImage button is enabled. + $format = $editor->getFilterFormat(); + if ($format->filters('filter_responsive_image_style')->status) { + $enabled = FALSE; + $settings = $editor->getSettings(); + foreach ($settings['toolbar']['rows'] as $row) { + foreach ($row as $group) { + foreach ($group['items'] as $button) { + if ($button === 'DrupalImage') { + $enabled = TRUE; + } + } + } + } + return $enabled; + } + + return FALSE; + } + +} diff --git a/core/modules/responsive_image/src/Plugin/Filter/FilterResponsiveImageStyle.php b/core/modules/responsive_image/src/Plugin/Filter/FilterResponsiveImageStyle.php index 1c35bc9..8745b22 100644 --- a/core/modules/responsive_image/src/Plugin/Filter/FilterResponsiveImageStyle.php +++ b/core/modules/responsive_image/src/Plugin/Filter/FilterResponsiveImageStyle.php @@ -37,9 +37,9 @@ public function process($text, $langcode) { $dom = Html::load($text); $xpath = new \DOMXPath($dom); - foreach ($xpath->query('//*[@data-editor-file-uuid and @data-responsive-image-style]') as $node) { - $file_uuid = $node->getAttribute('data-editor-file-uuid'); - $node->removeAttribute('data-editor-file-uuid'); + foreach ($xpath->query('//*[@data-entity-uuid and @data-responsive-image-style]') as $node) { + $file_uuid = $node->getAttribute('data-entity-uuid'); + $node->removeAttribute('data-entity-uuid'); $responsive_image_style_id = $node->getAttribute('data-responsive-image-style'); $node->removeAttribute('data-responsive-image-style'); @@ -88,11 +88,10 @@ public function process($text, $langcode) { $altered_html = drupal_render($responsive_image); // Load the altered HTML into a new DOMDocument and retrieve the element. - $updated_node = Html::load($altered_html)->getElementsByTagName('body') + $updated_node = Html::load(trim($altered_html))->getElementsByTagName('body') ->item(0) ->childNodes ->item(0); - // Import the updated node from the new DOMDocument into the original // one, importing also the child nodes of the updated node. $updated_node = $dom->importNode($updated_node, TRUE);