.../ckeditor/js/plugins/drupalimage/plugin.js | 91 +++++++++++++++++----- .../js/plugins/drupalimagecaption/plugin.js | 20 ++++- .../ckeditor/js/plugins/drupallink/plugin.js | 72 +++++++++++++++++ 3 files changed, 159 insertions(+), 24 deletions(-) diff --git a/core/modules/ckeditor/js/plugins/drupalimage/plugin.js b/core/modules/ckeditor/js/plugins/drupalimage/plugin.js index 80921c2..1c1ca87 100644 --- a/core/modules/ckeditor/js/plugins/drupalimage/plugin.js +++ b/core/modules/ckeditor/js/plugins/drupalimage/plugin.js @@ -65,10 +65,6 @@ } widgetDefinition.allowedContent = new CKEDITOR.style(allowedContentDefinition); - // Override the 'link' part, to completely disable image2's link - // support: http://dev.ckeditor.com/ticket/11341. - widgetDefinition.parts.link = 'This is a nonsensical selector to disable this functionality completely'; - // Override downcast(): since we only accept in our upcast method, // the element is already correct. We only need to update the element's // data-entity-uuid attribute. @@ -176,6 +172,17 @@ return widget; }; }; + + var originalInit = widgetDefinition.init; + widgetDefinition.init = function () { + originalInit.call(this); + + // Update data.link object with attributes if the link has been discovered. + // @see plugins/image2/plugin.js/init() in CKEditor; this is similar. + if (this.parts.link) { + this.setData('link', CKEDITOR.plugins.link.parseLinkAttributes(editor, this.parts.link)); + } + }; }); // Add a widget#edit listener to every instance of image2 widget in order @@ -233,25 +240,69 @@ } }, - // Disable image2's integration with the link/drupallink plugins: don't - // allow the widget itself to become a link. Support for that may be added - // by an text filter that adds a data- attribute specifically for that. afterInit: function (editor) { - if (editor.plugins.drupallink) { - var cmd = editor.getCommand('drupallink'); - // Needs to be refreshed on selection changes. - cmd.contextSensitive = 1; - // Disable command and cancel event when the image widget is selected. - cmd.on('refresh', function (evt) { - var widget = editor.widgets.focused; - if (widget && widget.name === 'image') { - this.setState(CKEDITOR.TRISTATE_DISABLED); - evt.cancel(); - } - }); - } + linkCommandIntegrator(editor); } }); + + function linkCommandIntegrator(editor) { + // Nothing to integrate with if link is not loaded. + if (!editor.plugins.drupallink) + return; + + // Overwrite default behaviour of unlink command. + editor.getCommand('drupalunlink').on('exec', function (evt) { + var widget = getFocusedWidget(editor); + + // Override unlink only when link truly belongs to the widget. + // If wrapped inline widget in a link, let default unlink work (#11814). + if (!widget || !widget.parts.link) + return; + + widget.setData('link', null); + + // Selection (which is fake) may not change if unlinked image in focused widget, + // i.e. if captioned image. Let's refresh command state manually here. + this.refresh(editor, editor.elementPath()); + + evt.cancel(); + }); + + // Overwrite default refresh of unlink command. + editor.getCommand('drupalunlink').on('refresh', function (evt) { + var widget = getFocusedWidget(editor); + + if (!widget) + return; + + // Note that widget may be wrapped in a link, which + // does not belong to that widget (#11814). + this.setState(widget.data.link || widget.wrapper.getAscendant('a') ? + CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED); + + evt.cancel(); + }); + } + + // Returns the focused widget, if of the type specific for this plugin. + // If no widget is focused, `null` is returned. + // + // @param {CKEDITOR.editor} + // @returns {CKEDITOR.plugins.widget} + function getFocusedWidget(editor) { + var widget = editor.widgets.focused; + + if (widget && widget.name == 'image') + return widget; + + return null; + } + + // Exposed some API just for convenience. + CKEDITOR.plugins.drupalimage = { + getFocusedWidget: getFocusedWidget + }; + })(jQuery, Drupal, CKEDITOR); diff --git a/core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.js b/core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.js index 8dd91b1..fe89872 100644 --- a/core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.js +++ b/core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.js @@ -93,8 +93,13 @@ } attrs['data-entity-type'] = this.data['data-entity-type']; attrs['data-entity-uuid'] = this.data['data-entity-uuid']; - - return img; + + // If img is wrapped with a link, we want to return that link. + if (img.parent.name == 'a') { + return img.parent; + } else { + return img; + } }; // We want to upcast elements to a DOM structure required by the @@ -115,6 +120,11 @@ element = originalUpcast.call(this, element, data); var attrs = element.attributes; + + if (element.parent.name === 'a') { + element = element.parent; + } + var retElement = element; var caption; @@ -132,11 +142,13 @@ delete attrs['data-entity-type']; data['data-entity-uuid'] = attrs['data-entity-uuid']; delete attrs['data-entity-uuid']; - + if (captionFilterEnabled) { // Unwrap from

wrapper created by HTML parser for a captioned // image. The captioned image will be transformed to

, so we // don't want the

anymore. + //var blockParent = element.parent.name == 'a' ? element.parent.parent : element.parent; + if (element.parent.name === 'p' && caption) { var index = element.getIndex(); var splitBefore = index > 0; @@ -268,4 +280,4 @@ return found; } -})(CKEDITOR); +})(CKEDITOR); \ No newline at end of file diff --git a/core/modules/ckeditor/js/plugins/drupallink/plugin.js b/core/modules/ckeditor/js/plugins/drupallink/plugin.js index e9fb555..28d2aaa 100644 --- a/core/modules/ckeditor/js/plugins/drupallink/plugin.js +++ b/core/modules/ckeditor/js/plugins/drupallink/plugin.js @@ -31,6 +31,7 @@ modes: {wysiwyg: 1}, canUndo: true, exec: function (editor) { + var focusedWidget = CKEDITOR.plugins.drupalimage.getFocusedWidget(editor); var linkElement = getSelectedLink(editor); var linkDOMElement = null; @@ -59,6 +60,22 @@ // Prepare a save callback to be used upon saving the dialog. var saveCallback = function (returnValues) { + // Let's check if a widget was focused. This means we're not editing + // an independent link, but we're wrapping a widget in a link. For + // example: a linked image. + if (focusedWidget) { + var urlMatch = returnValues.attributes.href.match(urlRegex); + focusedWidget.setData('link', { + type: 'url', + url: { + protocol: urlMatch[1], + url: urlMatch[2] + } + }); + editor.fire('saveSnapshot'); + return; + } + editor.fire('saveSnapshot'); // Create a new link element if needed. @@ -256,4 +273,59 @@ return null; } + var urlRegex = /^((?:http|https):\/\/)?(.*)$/; + + /** + * The image2 plugin is currently tightly coupled to the link plugin: it + * calls CKEDITOR.plugins.link.parseLinkAttributes(). + * + * Drupal 8's CKEditor build doesn't include the 'link' plugin. Because it + * includes its own link plugin that integrates with Drupal's dialog system. + * So, to allow images to be linked, we need to duplicate the necessary subset + * of the logic. + * + * @todo Remove once we update to CKEditor 4.5.5. + * @see https://dev.ckeditor.com/ticket/13885 + */ + CKEDITOR.plugins.link = { + parseLinkAttributes: function (editor, element) { + var href = (element && (element.data('cke-saved-href') || element.getAttribute('href'))) || ''; + var urlMatch = href.match(urlRegex); + return { + type: 'url', + url: { + protocol: urlMatch[1], + url: urlMatch[2] + } + }; + }, + getLinkAttributes: function (editor, data) { + var set = {}; + + var protocol = (data.url && data.url.protocol !== undefined) ? data.url.protocol : 'http://', + url = (data.url && CKEDITOR.tools.trim(data.url.url)) || ''; + set['data-cke-saved-href'] = (url.indexOf('/') === 0) ? url : protocol + url; + + // Browser need the "href" fro copy/paste link to work. (#6641) + if (set['data-cke-saved-href']) + set.href = set['data-cke-saved-href']; + + var removed = { + target: 1, + onclick: 1, + 'data-cke-pa-onclick': 1, + 'data-cke-saved-name': 1 + }; + + // Remove all attributes which are not currently set. + for (var s in set) + delete removed[s]; + + return { + set: set, + removed: CKEDITOR.tools.objectKeys(removed) + }; + } + } + })(jQuery, Drupal, drupalSettings, CKEDITOR);