From aefc6a29b189cb5d9366d6344ee450b01130f3d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Smyrek?= Date: Mon, 12 Apr 2021 11:45:07 +0200 Subject: [PATCH] Merge pull request #9418 from tony/tn-mediaembed-element-name-alt Feature (media-embed): Introduced the `config.mediaEmbed.elementName` to allow setting semantic element name. Closes #9373. --- .../docs/features/media-embed.md | 10 ++ .../ckeditor5-media-embed/src/converters.js | 1 + .../ckeditor5-media-embed/src/mediaembed.js | 28 ++++ .../src/mediaembedediting.js | 16 +- .../src/mediaregistry.js | 4 +- packages/ckeditor5-media-embed/src/utils.js | 1 + .../tests/mediaembedediting.js | 150 ++++++++++++++++++ 7 files changed, 203 insertions(+), 7 deletions(-) diff --git a/packages/ckeditor5-media-embed/docs/features/media-embed.md b/packages/ckeditor5-media-embed/docs/features/media-embed.md index 931c5442e7b..ffac32038e2 100644 --- a/packages/ckeditor5-media-embed/docs/features/media-embed.md +++ b/packages/ckeditor5-media-embed/docs/features/media-embed.md @@ -101,6 +101,16 @@ By default, the media embed feature outputs semantic `` tags f ``` +Further customization of semantic data output can be done through the {@link module:media-embed/mediaembed~MediaEmbedConfig#elementName `config.mediaEmbed.elementName`}. As an example, if `elementName` is set to `o-embed`: + +```html +
+ +
+``` + +If `elementName` is overridden to something beside the default value, existing `` elements will still be shown when for backward compatibility purposes. + #### Including previews in data Optionally, by setting `mediaEmbed.previewsInData` to `true` you can configure the media embed feature to output media in the same way they look in the editor. So if the media element is "previewable", the media preview (HTML) is saved to the database: diff --git a/packages/ckeditor5-media-embed/src/converters.js b/packages/ckeditor5-media-embed/src/converters.js index 93ed2bcf823..ca7eb565860 100644 --- a/packages/ckeditor5-media-embed/src/converters.js +++ b/packages/ckeditor5-media-embed/src/converters.js @@ -28,6 +28,7 @@ * @param {module:media-embed/mediaregistry~MediaRegistry} registry The registry providing * the media and their content. * @param {Object} options + * @param {String} [options.elementName] When set, overrides the default element name for semantic media embeds. * @param {String} [options.renderMediaPreview] When `true`, the converter will create the view in the non-semantic form. * @param {String} [options.renderForEditingView] When `true`, the converter will create a view specific for the * editing pipeline (e.g. including CSS classes, content placeholders). diff --git a/packages/ckeditor5-media-embed/src/mediaembed.js b/packages/ckeditor5-media-embed/src/mediaembed.js index 89e3a2854f1..0233f6a10cf 100644 --- a/packages/ckeditor5-media-embed/src/mediaembed.js +++ b/packages/ckeditor5-media-embed/src/mediaembed.js @@ -236,6 +236,34 @@ export default class MediaEmbed extends Plugin { * @member {Array.} module:media-embed/mediaembed~MediaEmbedConfig#removeProviders */ +/** + * Overrides the element name used for "semantic" data. + * + * This is not relevant if {@link module:media-embed/mediaembed~MediaEmbedConfig#previewsInData `config.mediaEmbed.previewsInData`} + * is set to `true`. + * + * When unset, the feature produces tag ``: + * + *
+ * + *
+ * + * To override the element name with, for instance, the `o-embed` name: + * + * mediaEmbed: { + * elementName: 'o-embed' + * } + * + * This will produce semantic data with `` tag: + * + *
+ * + *
+ * + * @default 'oembed' + * @member {String} [module:media-embed/mediaembed~MediaEmbedConfig#elementName] + */ + /** * Controls the data format produced by the feature. * diff --git a/packages/ckeditor5-media-embed/src/mediaembedediting.js b/packages/ckeditor5-media-embed/src/mediaembedediting.js index 7cdcd100081..c709b2957a2 100644 --- a/packages/ckeditor5-media-embed/src/mediaembedediting.js +++ b/packages/ckeditor5-media-embed/src/mediaembedediting.js @@ -36,6 +36,7 @@ export default class MediaEmbedEditing extends Plugin { super( editor ); editor.config.define( 'mediaEmbed', { + elementName: 'oembed', providers: [ { name: 'dailymotion', @@ -162,6 +163,8 @@ export default class MediaEmbedEditing extends Plugin { const t = editor.t; const conversion = editor.conversion; const renderMediaPreview = editor.config.get( 'mediaEmbed.previewsInData' ); + const elementName = editor.config.get( 'mediaEmbed.elementName' ); + const registry = this.registry; editor.commands.add( 'mediaEmbed', new MediaEmbedCommand( editor ) ); @@ -181,6 +184,7 @@ export default class MediaEmbedEditing extends Plugin { const url = modelElement.getAttribute( 'url' ); return createMediaFigureElement( writer, registry, url, { + elementName, renderMediaPreview: url && renderMediaPreview } ); } @@ -189,6 +193,7 @@ export default class MediaEmbedEditing extends Plugin { // Model -> Data (url -> data-oembed-url) conversion.for( 'dataDowncast' ).add( modelToViewUrlAttributeConverter( registry, { + elementName, renderMediaPreview } ) ); @@ -198,6 +203,7 @@ export default class MediaEmbedEditing extends Plugin { view: ( modelElement, { writer } ) => { const url = modelElement.getAttribute( 'url' ); const figure = createMediaFigureElement( writer, registry, url, { + elementName, renderForEditingView: true } ); @@ -208,6 +214,7 @@ export default class MediaEmbedEditing extends Plugin { // Model -> View (url -> data-oembed-url) conversion.for( 'editingDowncast' ).add( modelToViewUrlAttributeConverter( registry, { + elementName, renderForEditingView: true } ) ); @@ -215,12 +222,9 @@ export default class MediaEmbedEditing extends Plugin { conversion.for( 'upcast' ) // Upcast semantic media. .elementToElement( { - view: { - name: 'oembed', - attributes: { - url: true - } - }, + view: element => [ 'oembed', elementName ].includes( element.name ) && element.getAttribute( 'url' ) ? + { name: true } : + null, model: ( viewMedia, { writer } ) => { const url = viewMedia.getAttribute( 'url' ); diff --git a/packages/ckeditor5-media-embed/src/mediaregistry.js b/packages/ckeditor5-media-embed/src/mediaregistry.js index 8fd3fbfbbb5..67522d97666 100644 --- a/packages/ckeditor5-media-embed/src/mediaregistry.js +++ b/packages/ckeditor5-media-embed/src/mediaregistry.js @@ -88,6 +88,7 @@ export default class MediaRegistry { * @param {module:engine/view/downcastwriter~DowncastWriter} writer The view writer used to produce a view element. * @param {String} url The URL to be translated into a view element. * @param {Object} options + * @param {String} [options.elementName] * @param {String} [options.renderMediaPreview] * @param {String} [options.renderForEditingView] * @returns {module:engine/view/element~Element} @@ -206,6 +207,7 @@ class Media { * * @param {module:engine/view/downcastwriter~DowncastWriter} writer The view writer used to produce a view element. * @param {Object} options + * @param {String} [options.elementName] * @param {String} [options.renderMediaPreview] * @param {String} [options.renderForEditingView] * @returns {module:engine/view/element~Element} @@ -233,7 +235,7 @@ class Media { attributes.url = this.url; } - viewElement = writer.createEmptyElement( 'oembed', attributes ); + viewElement = writer.createEmptyElement( options.elementName, attributes ); } writer.setCustomProperty( 'media-content', true, viewElement ); diff --git a/packages/ckeditor5-media-embed/src/utils.js b/packages/ckeditor5-media-embed/src/utils.js index f2b20b76c06..3913749aee8 100644 --- a/packages/ckeditor5-media-embed/src/utils.js +++ b/packages/ckeditor5-media-embed/src/utils.js @@ -68,6 +68,7 @@ export function isMediaWidget( viewElement ) { * @param {module:media-embed/mediaregistry~MediaRegistry} registry * @param {String} url * @param {Object} options + * @param {String} [options.elementName] * @param {String} [options.useSemanticWrapper] * @param {String} [options.renderForEditingView] * @returns {module:engine/view/containerelement~ContainerElement} diff --git a/packages/ckeditor5-media-embed/tests/mediaembedediting.js b/packages/ckeditor5-media-embed/tests/mediaembedediting.js index f4c0667ce6f..9d68e272b5d 100644 --- a/packages/ckeditor5-media-embed/tests/mediaembedediting.js +++ b/packages/ckeditor5-media-embed/tests/mediaembedediting.js @@ -477,6 +477,156 @@ describe( 'MediaEmbedEditing', () => { } ); describe( 'conversion in the data pipeline', () => { + describe( 'elementName#o-embed', () => { + beforeEach( () => { + return createTestEditor( { + elementName: 'o-embed', + providers: providerDefinitions + } ) + .then( newEditor => { + editor = newEditor; + model = editor.model; + doc = model.document; + view = editor.editing.view; + } ); + } ); + + describe( 'model to view', () => { + it( 'should convert', () => { + setModelData( model, '' ); + + expect( editor.getData() ).to.equal( + '
' + + '' + + '
' ); + } ); + + it( 'should convert (no url)', () => { + setModelData( model, '' ); + + expect( editor.getData() ).to.equal( + '
' + + '' + + '
' ); + } ); + + it( 'should convert (preview-less media)', () => { + setModelData( model, '' ); + + expect( editor.getData() ).to.equal( + '
' + + '' + + '
' ); + } ); + } ); + + describe( 'view to model', () => { + it( 'should convert media figure', () => { + editor.setData( '
' ); + + expect( getModelData( model, { withoutSelection: true } ) ) + .to.equal( '' ); + } ); + + it( 'should not convert if there is no media class', () => { + editor.setData( '
My quote
' ); + + expect( getModelData( model, { withoutSelection: true } ) ) + .to.equal( '' ); + } ); + + it( 'should not convert if there is no o-embed wrapper inside #1', () => { + editor.setData( '
' ); + + expect( getModelData( model, { withoutSelection: true } ) ) + .to.equal( '' ); + } ); + + it( 'should not convert if there is no o-embed wrapper inside #2', () => { + editor.setData( '
test
' ); + + expect( getModelData( model, { withoutSelection: true } ) ) + .to.equal( '' ); + } ); + + it( 'should not convert when the wrapper has no data-o-embed-url attribute', () => { + editor.setData( '
' ); + + expect( getModelData( model, { withoutSelection: true } ) ) + .to.equal( '' ); + } ); + + it( 'should not convert in the wrong context', () => { + model.schema.register( 'blockquote', { inheritAllFrom: '$block' } ); + model.schema.addChildCheck( ( ctx, childDef ) => { + if ( ctx.endsWith( '$root' ) && childDef.name == 'media' ) { + return false; + } + } ); + + editor.conversion.elementToElement( { model: 'blockquote', view: 'blockquote' } ); + + editor.setData( + '
' ); + + expect( getModelData( model, { withoutSelection: true } ) ) + .to.equal( '
' ); + } ); + + it( 'should not convert if the o-embed wrapper is already consumed', () => { + editor.data.upcastDispatcher.on( 'element:figure', ( evt, data, conversionApi ) => { + const img = data.viewItem.getChild( 0 ); + conversionApi.consumable.consume( img, { name: true } ); + }, { priority: 'high' } ); + + editor.setData( '
' ); + + expect( getModelData( model, { withoutSelection: true } ) ) + .to.equal( '' ); + } ); + + it( 'should not convert if the figure is already consumed', () => { + editor.data.upcastDispatcher.on( 'element:figure', ( evt, data, conversionApi ) => { + conversionApi.consumable.consume( data.viewItem, { name: true, class: 'image' } ); + }, { priority: 'high' } ); + + editor.setData( '
' ); + + expect( getModelData( model, { withoutSelection: true } ) ) + .to.equal( '' ); + } ); + + it( 'should discard the contents of the media', () => { + editor.setData( '
foo bar
' ); + + expect( getModelData( model, { withoutSelection: true } ) ) + .to.equal( '' ); + } ); + + it( 'should not convert unknown media', () => { + return createTestEditor( { + providers: [ + testProviders.A + ] + } ) + .then( newEditor => { + newEditor.setData( + '
' + + '
' + + '
' + + '
' + + '
' + + '
' ); + + expect( getModelData( newEditor.model, { withoutSelection: true } ) ) + .to.equal( '' ); + + return newEditor.destroy(); + } ); + } ); + } ); + } ); + describe( 'previewsInData=false', () => { beforeEach( () => { return createTestEditor( {