Skip to content

Commit

Permalink
Internal: Allow setting semantic element name in Media Embeds, fixes c…
Browse files Browse the repository at this point in the history
  • Loading branch information
tony committed Apr 3, 2021
1 parent 3c69604 commit d44ae84
Show file tree
Hide file tree
Showing 5 changed files with 226 additions and 2 deletions.
24 changes: 24 additions & 0 deletions packages/ckeditor5-media-embed/docs/features/media-embed.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,30 @@ By default, the media embed feature outputs semantic `<oembed url="...">` tags f
</figure>
```

Further customization of semantic data output can be done through the {@link module:media-embed/mediaembed~MediaEmbedConfig#preferredElementName `config.mediaEmbed.preferredElementName`} and {@link module:media-embed/mediaembed~MediaEmbedConfig#elementNames `config.mediaEmbed.elementNames`} options. As an example, if `preferredElementName` is set to `o-embed`:

```html
<figure class="media">
<o-embed url="https://media-url"></oembed>
</figure>
```

And further, to be backward compatible with legacy semantic elements (any element using the `url` attribute, these can be passed via `elementNames`: `['oembed', 'o-embed']`. If there is a semantic tag for `<mytag url="..."></mytag>`, you can set `['oembed', 'o-embed', 'mytag']`. To remove support for tags, omit the tag name. To skip handling of `<oembed>` tags, `['oembed']`:

```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
plugins: [ MediaEmbed, ... ],,
toolbar: [ 'mediaEmbed', ... ]
mediaEmbed: {
elementNames: ['oembed']
}
} )
.then( ... )
.catch( ... );
```


#### 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:
Expand Down
38 changes: 38 additions & 0 deletions packages/ckeditor5-media-embed/src/mediaembed.js
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,44 @@ export default class MediaEmbed extends Plugin {
* @member {Array.<String>} module:media-embed/mediaembed~MediaEmbedConfig#removeProviders
*/

/**
* Customizing semantic element name.
*
* When `oembed` (default), the feature produces "semantic" data with tag `<oembed>`:
*
* <figure class="media">
* <oembed url="https://url"></oembed>
* </figure>
*
* It can also be set to other element names, for instance, `o-embed` will produce:
*
* <figure class="media">
* <o-embed url="https://url"></oembed>
* </figure>
*
* @member {String} [module:media-embed/mediaembed~MediaEmbedConfig#preferredElementName]
*/

/**
* Supporting legacy semantic element names.
*
* When `['oembed', 'o-embed']` (default), the feature renders "semantic" data for content with
* `<oembed>` an `<o-embed>` tags:
*
* <figure class="media">
* <oembed url="https://url"></oembed>
* </figure>
*
* <figure class="media">
* <o-embed url="https://url"></oembed>
* </figure>
*
* By default, the feature will render new media embeds via the option
* {@link module:media-embed/mediaembed~MediaEmbedConfig#preferredElementName `config.mediaEmbed.preferredElementName`}
*
* @member {Array} [module:media-embed/mediaembed~MediaEmbedConfig#elementNames]
*/

/**
* Controls the data format produced by the feature.
*
Expand Down
10 changes: 9 additions & 1 deletion packages/ckeditor5-media-embed/src/mediaembedediting.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export default class MediaEmbedEditing extends Plugin {
super( editor );

editor.config.define( 'mediaEmbed', {
elementNames: [ 'oembed', 'o-embed' ],
preferredElementName: 'oembed',
providers: [
{
name: 'dailymotion',
Expand Down Expand Up @@ -162,6 +164,8 @@ export default class MediaEmbedEditing extends Plugin {
const t = editor.t;
const conversion = editor.conversion;
const renderMediaPreview = editor.config.get( 'mediaEmbed.previewsInData' );
const elementNames = editor.config.get( 'mediaEmbed.elementNames' );
const preferredElementName = editor.config.get( 'mediaEmbed.preferredElementName' );
const registry = this.registry;

editor.commands.add( 'mediaEmbed', new MediaEmbedCommand( editor ) );
Expand All @@ -181,6 +185,7 @@ export default class MediaEmbedEditing extends Plugin {
const url = modelElement.getAttribute( 'url' );

return createMediaFigureElement( writer, registry, url, {
preferredElementName,
renderMediaPreview: url && renderMediaPreview
} );
}
Expand All @@ -189,6 +194,7 @@ export default class MediaEmbedEditing extends Plugin {
// Model -> Data (url -> data-oembed-url)
conversion.for( 'dataDowncast' ).add(
modelToViewUrlAttributeConverter( registry, {
preferredElementName,
renderMediaPreview
} ) );

Expand All @@ -198,6 +204,7 @@ export default class MediaEmbedEditing extends Plugin {
view: ( modelElement, { writer } ) => {
const url = modelElement.getAttribute( 'url' );
const figure = createMediaFigureElement( writer, registry, url, {
preferredElementName,
renderForEditingView: true
} );

Expand All @@ -208,6 +215,7 @@ export default class MediaEmbedEditing extends Plugin {
// Model -> View (url -> data-oembed-url)
conversion.for( 'editingDowncast' ).add(
modelToViewUrlAttributeConverter( registry, {
preferredElementName,
renderForEditingView: true
} ) );

Expand All @@ -216,7 +224,7 @@ export default class MediaEmbedEditing extends Plugin {
// Upcast semantic media.
.elementToElement( {
view: {
name: 'oembed',
name: new RegExp( `^(${ elementNames.join( '|' ) })$` ),
attributes: {
url: true
}
Expand Down
10 changes: 9 additions & 1 deletion packages/ckeditor5-media-embed/src/mediaregistry.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ export default class MediaRegistry {
* @member {Array}
*/
this.providerDefinitions = providerDefinitions;

/**
* The preferred element names for newly added media embed.
*
* @member {String}
*/
this.preferredElementName = config.preferredElementName;
}

/**
Expand Down Expand Up @@ -206,6 +213,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.preferredElementName]
* @param {String} [options.renderMediaPreview]
* @param {String} [options.renderForEditingView]
* @returns {module:engine/view/element~Element}
Expand Down Expand Up @@ -233,7 +241,7 @@ class Media {
attributes.url = this.url;
}

viewElement = writer.createEmptyElement( 'oembed', attributes );
viewElement = writer.createEmptyElement( options.preferredElementName, attributes );
}

writer.setCustomProperty( 'media-content', true, viewElement );
Expand Down
146 changes: 146 additions & 0 deletions packages/ckeditor5-media-embed/tests/mediaembedediting.js
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,152 @@ describe( 'MediaEmbedEditing', () => {
} );

describe( 'conversion in the data pipeline', () => {
describe( 'preferredElementName#o-embed', () => {
beforeEach( () => {
return createTestEditor( {
preferredElementName: '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, '<media url="https://ckeditor.com"></media>' );

expect( editor.getData() ).to.equal(
'<figure class="media">' +
'<o-embed url="https://ckeditor.com"></o-embed>' +
'</figure>' );
} );

it( 'should convert (no url)', () => {
setModelData( model, '<media></media>' );

expect( editor.getData() ).to.equal(
'<figure class="media">' +
'<o-embed></o-embed>' +
'</figure>' );
} );

it( 'should convert (preview-less media)', () => {
setModelData( model, '<media url="https://preview-less"></media>' );

expect( editor.getData() ).to.equal(
'<figure class="media">' +
'<o-embed url="https://preview-less"></o-embed>' +
'</figure>' );
} );
} );

describe( 'view to model', () => {
it( 'should convert media figure', () => {
editor.setData( '<figure class="media"><o-embed url="https://ckeditor.com"></o-embed></figure>' );

expect( getModelData( model, { withoutSelection: true } ) )
.to.equal( '<media url="https://ckeditor.com"></media>' );
} );

it( 'should not convert if there is no media class', () => {
editor.setData( '<figure class="quote">My quote</figure>' );

expect( getModelData( model, { withoutSelection: true } ) )
.to.equal( '' );
} );

it( 'should not convert if there is no o-embed wrapper inside #1', () => {
editor.setData( '<figure class="media"></figure>' );

expect( getModelData( model, { withoutSelection: true } ) )
.to.equal( '' );
} );

it( 'should not convert if there is no o-embed wrapper inside #2', () => {
editor.setData( '<figure class="media">test</figure>' );

expect( getModelData( model, { withoutSelection: true } ) )
.to.equal( '' );
} );

it( 'should not convert when the wrapper has no data-o-embed-url attribute', () => {
editor.setData( '<figure class="media"><div></div></figure>' );

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(
'<blockquote><figure class="media"><o-embed url="https://ckeditor.com"></o-embed></figure></blockquote>' );

expect( getModelData( model, { withoutSelection: true } ) )
.to.equal( '<blockquote></blockquote>' );
} );

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( '<figure class="media"><o-embed url="https://ckeditor.com"></o-embed></figure>' );

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( '<figure class="media"><o-embed url="https://ckeditor.com"></o-embed></figure>' );

expect( getModelData( model, { withoutSelection: true } ) )
.to.equal( '' );
} );

it( 'should discard the contents of the media', () => {
editor.setData( '<figure class="media"><o-embed url="https://ckeditor.com">foo bar</o-embed></figure>' );

expect( getModelData( model, { withoutSelection: true } ) )
.to.equal( '<media url="https://ckeditor.com"></media>' );
} );

it( 'should not convert unknown media', () => {
return createTestEditor( {
providers: [
testProviders.A
]
} )
.then( newEditor => {
newEditor.setData(
'<figure class="media"><o-embed url="unknown.media"></o-embed></figure>' +
'<figure class="media"><o-embed url="foo.com/123"></o-embed></figure>' );

expect( getModelData( newEditor.model, { withoutSelection: true } ) )
.to.equal( '<media url="foo.com/123"></media>' );

return newEditor.destroy();
} );
} );
} );
} );

describe( 'previewsInData=false', () => {
beforeEach( () => {
return createTestEditor( {
Expand Down

0 comments on commit d44ae84

Please sign in to comment.