@ckeditor/ckeditor5-html-support
Version:
HTML Support feature for CKEditor 5.
126 lines (125 loc) • 5.25 kB
JavaScript
/**
* @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module html-support/integrations/mediaembed
*/
import { Plugin } from 'ckeditor5/src/core.js';
import { DataFilter } from '../datafilter.js';
import { DataSchema } from '../dataschema.js';
import { updateViewAttributes, getHtmlAttributeName } from '../utils.js';
import { getDescendantElement } from './integrationutils.js';
/**
* Provides the General HTML Support integration with {@link module:media-embed/mediaembed~MediaEmbed Media Embed} feature.
*/
export class MediaEmbedElementSupport extends Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [DataFilter];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'MediaEmbedElementSupport';
}
/**
* @inheritDoc
*/
static get isOfficialPlugin() {
return true;
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
// Stop here if MediaEmbed plugin is not provided or the integrator wants to output markup with previews as
// we do not support filtering previews.
if (!editor.plugins.has('MediaEmbed') || editor.config.get('mediaEmbed.previewsInData')) {
return;
}
const schema = editor.model.schema;
const conversion = editor.conversion;
const dataFilter = this.editor.plugins.get(DataFilter);
const dataSchema = this.editor.plugins.get(DataSchema);
const mediaElementName = editor.config.get('mediaEmbed.elementName');
// Overwrite GHS schema definition for a given elementName.
dataSchema.registerBlockElement({
model: 'media',
view: mediaElementName
});
dataFilter.on('register:figure', () => {
conversion.for('upcast').add(viewToModelFigureAttributesConverter(dataFilter));
});
dataFilter.on(`register:${mediaElementName}`, (evt, definition) => {
if (definition.model !== 'media') {
return;
}
schema.extend('media', {
allowAttributes: [
getHtmlAttributeName(mediaElementName),
'htmlFigureAttributes'
]
});
conversion.for('upcast').add(viewToModelMediaAttributesConverter(dataFilter, mediaElementName));
conversion.for('dataDowncast').add(modelToViewMediaAttributeConverter(mediaElementName));
evt.stop();
});
}
}
function viewToModelMediaAttributesConverter(dataFilter, mediaElementName) {
const upcastMedia = (evt, data, conversionApi) => {
const viewMediaElement = data.viewItem;
preserveElementAttributes(viewMediaElement, getHtmlAttributeName(mediaElementName));
function preserveElementAttributes(viewElement, attributeName) {
const viewAttributes = dataFilter.processViewAttributes(viewElement, conversionApi);
if (viewAttributes) {
conversionApi.writer.setAttribute(attributeName, viewAttributes, data.modelRange);
}
}
};
return (dispatcher) => {
dispatcher.on(`element:${mediaElementName}`, upcastMedia, { priority: 'low' });
};
}
/**
* View-to-model conversion helper preserving allowed attributes on {@link module:media-embed/mediaembed~MediaEmbed MediaEmbed}
* feature model element from figure view element.
*
* @returns Returns a conversion callback.
*/
function viewToModelFigureAttributesConverter(dataFilter) {
return (dispatcher) => {
dispatcher.on('element:figure', (evt, data, conversionApi) => {
const viewFigureElement = data.viewItem;
if (!data.modelRange || !viewFigureElement.hasClass('media')) {
return;
}
const viewAttributes = dataFilter.processViewAttributes(viewFigureElement, conversionApi);
if (viewAttributes) {
conversionApi.writer.setAttribute('htmlFigureAttributes', viewAttributes, data.modelRange);
}
}, { priority: 'low' });
};
}
function modelToViewMediaAttributeConverter(mediaElementName) {
return (dispatcher) => {
addAttributeConversionDispatcherHandler(mediaElementName, getHtmlAttributeName(mediaElementName));
addAttributeConversionDispatcherHandler('figure', 'htmlFigureAttributes');
function addAttributeConversionDispatcherHandler(elementName, attributeName) {
dispatcher.on(`attribute:${attributeName}:media`, (evt, data, conversionApi) => {
if (!conversionApi.consumable.consume(data.item, evt.name)) {
return;
}
const { attributeOldValue, attributeNewValue } = data;
const containerElement = conversionApi.mapper.toViewElement(data.item);
const viewElement = getDescendantElement(conversionApi.writer, containerElement, elementName);
updateViewAttributes(conversionApi.writer, attributeOldValue, attributeNewValue, viewElement);
});
}
};
}