Loading core/modules/ckeditor5/js/build/drupalMedia.js +1 −1 File changed.Preview size limit exceeded, changes collapsed. Show changes core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/drupallinkmedia/drupallinkmediaediting.js +155 −1 Original line number Diff line number Diff line /* eslint-disable import/no-extraneous-dependencies */ /* cspell:words drupallinkmediaediting linkediting */ /* cspell:words drupallinkmediaediting linkediting linkimageediting linkcommand */ import { Plugin } from 'ckeditor5/src/core'; import { Matcher } from 'ckeditor5/src/engine'; import { toMap } from 'ckeditor5/src/utils'; /** * Returns the first drupal-media element in a given view element. Loading Loading @@ -192,6 +194,128 @@ function editingDowncastMediaLink() { }; } /** * Returns a converter that enables manual decorators on linked Drupal Media. * * @see \Drupal\editor\EditorXssFilter\Standard * * @param {module:link/link~LinkDecoratorDefinition} decorator * The link decorator. * @return {function} * Function attaching event listener to dispatcher. * * @private */ function downcastMediaLinkManualDecorator(decorator) { return (dispatcher) => { dispatcher.on( `attribute:${decorator.id}:drupalMedia`, (evt, data, conversionApi) => { const mediaContainer = conversionApi.mapper.toViewElement(data.item); // Scenario 1: `<figure>` element that contains `<a>`, generated by // `dataDowncast`. let mediaLink = Array.from(mediaContainer.getChildren()).find( (child) => child.name === 'a', ); // Scenario 2: `<drupal-media>` wrapped with `<a>`, generated by // `editingDowncast`. if (!mediaLink && mediaContainer.is('element', 'a')) { mediaLink = mediaContainer; } else { mediaLink = Array.from(mediaContainer.getAncestors()).find( (ancestor) => ancestor.name === 'a', ); } // The <a> element was removed by the time this converter is executed. // It may happen when the base `linkHref` and decorator attributes are // removed at the same time. if (!mediaLink) { return; } // eslint-disable-next-line no-restricted-syntax for (const [key, val] of toMap(decorator.attributes)) { conversionApi.writer.setAttribute(key, val, mediaLink); } if (decorator.classes) { conversionApi.writer.addClass(decorator.classes, mediaLink); } // Add support for `style` attribute in manual decorators to remain // consistent with CKEditor 5. This only works with text formats that // have no HTMl filtering enabled. // eslint-disable-next-line no-restricted-syntax for (const key in decorator.styles) { if (Object.prototype.hasOwnProperty.call(decorator.styles, key)) { conversionApi.writer.setStyle( key, decorator.styles[key], mediaLink, ); } } }, ); }; } /** * Returns a converter that applies manual decorators to linked Drupal Media. * * @param {module:core/editor/editor~Editor} editor * The editor. * @param {module:link/link~LinkDecoratorDefinition} decorator * The link decorator. * @return {function} * Function attaching event listener to dispatcher. * * @private */ function upcastMediaLinkManualDecorator(editor, decorator) { return (dispatcher) => { dispatcher.on( 'element:a', (evt, data, conversionApi) => { const viewLink = data.viewItem; const drupalMediaInLink = getFirstMedia(viewLink); // We need to check whether Drupal Media is inside a link because the // converter handles only manual decorators for linked Drupal Media. if (!drupalMediaInLink) { return; } const matcher = new Matcher(decorator._createPattern()); const result = matcher.match(viewLink); // The link element does not have required attributes or/and proper // values. if (!result) { return; } // Check whether we can consume those attributes. if (!conversionApi.consumable.consume(viewLink, result.match)) { return; } // At this stage we can assume that we have the `<drupalMedia>` element. const modelElement = data.modelCursor.nodeBefore; conversionApi.writer.setAttribute(decorator.id, true, modelElement); }, { priority: 'high' }, ); // Using the same priority as the media link upcast converter guarantees // that the linked `<drupalMedia>` was already converted. // @see upcastMediaLink(). }; } /** * Model to view and view to model conversions for linked media elements. * Loading Loading @@ -226,5 +350,35 @@ export default class DrupalLinkMediaEditing extends Plugin { editor.conversion.for('upcast').add(upcastMediaLink()); editor.conversion.for('editingDowncast').add(editingDowncastMediaLink()); editor.conversion.for('dataDowncast').add(dataDowncastMediaLink()); this._enableManualDecorators(); } /** * Processes transformed manual link decorators and attaches proper converters * that will work when linking Drupal Media. * * @see module:link/linkimageediting~LinkImageEditing * @see module:link/linkcommand~LinkCommand * @see module:link/utils~ManualDecorator * * @private */ _enableManualDecorators() { const editor = this.editor; const command = editor.commands.get('link'); // eslint-disable-next-line no-restricted-syntax for (const decorator of command.manualDecorators) { editor.model.schema.extend('drupalMedia', { allowAttributes: decorator.id, }); editor.conversion .for('downcast') .add(downcastMediaLinkManualDecorator(decorator)); editor.conversion .for('upcast') .add(upcastMediaLinkManualDecorator(editor, decorator)); } } } core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/drupalmediaediting.js +1 −0 Original line number Diff line number Diff line Loading @@ -90,6 +90,7 @@ export default class DrupalMediaEditing extends Plugin { allowWhere: '$block', isObject: true, isContent: true, isBlock: true, allowAttributes: Object.keys(this.attrs), }); } Loading core/modules/ckeditor5/tests/modules/ckeditor5_manual_decorator_test/ckeditor5_manual_decorator_test.ckeditor5.yml 0 → 100644 +25 −0 Original line number Diff line number Diff line ckeditor5_manual_decorator_test_openInNewTab: ckeditor5: plugins: [] config: link: decorators: openInNewTab: mode: 'manual' label: 'Open in a new tab' attributes: target: '_blank' rel: 'noopener noreferrer' classes: ['link-new-tab'] pinkColor: mode: 'manual' label: 'Pink color' styles: color: 'pink' drupal: label: Open in new tab elements: - <a target="_blank" rel="noopener noreferrer" class> conditions: plugins: - ckeditor5_link core/modules/ckeditor5/tests/modules/ckeditor5_manual_decorator_test/ckeditor5_manual_decorator_test.info.yml 0 → 100644 +6 −0 Original line number Diff line number Diff line name: CKEditor 5 Manual Decorator Test type: module description: "Provides configuration for CKEditor 5 link plugin manual decorator." package: Testing dependencies: - ckeditor5:ckeditor5 Loading
core/modules/ckeditor5/js/build/drupalMedia.js +1 −1 File changed.Preview size limit exceeded, changes collapsed. Show changes
core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/drupallinkmedia/drupallinkmediaediting.js +155 −1 Original line number Diff line number Diff line /* eslint-disable import/no-extraneous-dependencies */ /* cspell:words drupallinkmediaediting linkediting */ /* cspell:words drupallinkmediaediting linkediting linkimageediting linkcommand */ import { Plugin } from 'ckeditor5/src/core'; import { Matcher } from 'ckeditor5/src/engine'; import { toMap } from 'ckeditor5/src/utils'; /** * Returns the first drupal-media element in a given view element. Loading Loading @@ -192,6 +194,128 @@ function editingDowncastMediaLink() { }; } /** * Returns a converter that enables manual decorators on linked Drupal Media. * * @see \Drupal\editor\EditorXssFilter\Standard * * @param {module:link/link~LinkDecoratorDefinition} decorator * The link decorator. * @return {function} * Function attaching event listener to dispatcher. * * @private */ function downcastMediaLinkManualDecorator(decorator) { return (dispatcher) => { dispatcher.on( `attribute:${decorator.id}:drupalMedia`, (evt, data, conversionApi) => { const mediaContainer = conversionApi.mapper.toViewElement(data.item); // Scenario 1: `<figure>` element that contains `<a>`, generated by // `dataDowncast`. let mediaLink = Array.from(mediaContainer.getChildren()).find( (child) => child.name === 'a', ); // Scenario 2: `<drupal-media>` wrapped with `<a>`, generated by // `editingDowncast`. if (!mediaLink && mediaContainer.is('element', 'a')) { mediaLink = mediaContainer; } else { mediaLink = Array.from(mediaContainer.getAncestors()).find( (ancestor) => ancestor.name === 'a', ); } // The <a> element was removed by the time this converter is executed. // It may happen when the base `linkHref` and decorator attributes are // removed at the same time. if (!mediaLink) { return; } // eslint-disable-next-line no-restricted-syntax for (const [key, val] of toMap(decorator.attributes)) { conversionApi.writer.setAttribute(key, val, mediaLink); } if (decorator.classes) { conversionApi.writer.addClass(decorator.classes, mediaLink); } // Add support for `style` attribute in manual decorators to remain // consistent with CKEditor 5. This only works with text formats that // have no HTMl filtering enabled. // eslint-disable-next-line no-restricted-syntax for (const key in decorator.styles) { if (Object.prototype.hasOwnProperty.call(decorator.styles, key)) { conversionApi.writer.setStyle( key, decorator.styles[key], mediaLink, ); } } }, ); }; } /** * Returns a converter that applies manual decorators to linked Drupal Media. * * @param {module:core/editor/editor~Editor} editor * The editor. * @param {module:link/link~LinkDecoratorDefinition} decorator * The link decorator. * @return {function} * Function attaching event listener to dispatcher. * * @private */ function upcastMediaLinkManualDecorator(editor, decorator) { return (dispatcher) => { dispatcher.on( 'element:a', (evt, data, conversionApi) => { const viewLink = data.viewItem; const drupalMediaInLink = getFirstMedia(viewLink); // We need to check whether Drupal Media is inside a link because the // converter handles only manual decorators for linked Drupal Media. if (!drupalMediaInLink) { return; } const matcher = new Matcher(decorator._createPattern()); const result = matcher.match(viewLink); // The link element does not have required attributes or/and proper // values. if (!result) { return; } // Check whether we can consume those attributes. if (!conversionApi.consumable.consume(viewLink, result.match)) { return; } // At this stage we can assume that we have the `<drupalMedia>` element. const modelElement = data.modelCursor.nodeBefore; conversionApi.writer.setAttribute(decorator.id, true, modelElement); }, { priority: 'high' }, ); // Using the same priority as the media link upcast converter guarantees // that the linked `<drupalMedia>` was already converted. // @see upcastMediaLink(). }; } /** * Model to view and view to model conversions for linked media elements. * Loading Loading @@ -226,5 +350,35 @@ export default class DrupalLinkMediaEditing extends Plugin { editor.conversion.for('upcast').add(upcastMediaLink()); editor.conversion.for('editingDowncast').add(editingDowncastMediaLink()); editor.conversion.for('dataDowncast').add(dataDowncastMediaLink()); this._enableManualDecorators(); } /** * Processes transformed manual link decorators and attaches proper converters * that will work when linking Drupal Media. * * @see module:link/linkimageediting~LinkImageEditing * @see module:link/linkcommand~LinkCommand * @see module:link/utils~ManualDecorator * * @private */ _enableManualDecorators() { const editor = this.editor; const command = editor.commands.get('link'); // eslint-disable-next-line no-restricted-syntax for (const decorator of command.manualDecorators) { editor.model.schema.extend('drupalMedia', { allowAttributes: decorator.id, }); editor.conversion .for('downcast') .add(downcastMediaLinkManualDecorator(decorator)); editor.conversion .for('upcast') .add(upcastMediaLinkManualDecorator(editor, decorator)); } } }
core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/drupalmediaediting.js +1 −0 Original line number Diff line number Diff line Loading @@ -90,6 +90,7 @@ export default class DrupalMediaEditing extends Plugin { allowWhere: '$block', isObject: true, isContent: true, isBlock: true, allowAttributes: Object.keys(this.attrs), }); } Loading
core/modules/ckeditor5/tests/modules/ckeditor5_manual_decorator_test/ckeditor5_manual_decorator_test.ckeditor5.yml 0 → 100644 +25 −0 Original line number Diff line number Diff line ckeditor5_manual_decorator_test_openInNewTab: ckeditor5: plugins: [] config: link: decorators: openInNewTab: mode: 'manual' label: 'Open in a new tab' attributes: target: '_blank' rel: 'noopener noreferrer' classes: ['link-new-tab'] pinkColor: mode: 'manual' label: 'Pink color' styles: color: 'pink' drupal: label: Open in new tab elements: - <a target="_blank" rel="noopener noreferrer" class> conditions: plugins: - ckeditor5_link
core/modules/ckeditor5/tests/modules/ckeditor5_manual_decorator_test/ckeditor5_manual_decorator_test.info.yml 0 → 100644 +6 −0 Original line number Diff line number Diff line name: CKEditor 5 Manual Decorator Test type: module description: "Provides configuration for CKEditor 5 link plugin manual decorator." package: Testing dependencies: - ckeditor5:ckeditor5