|
|
@@ -9,11 +9,13 @@
|
|
|
|
|
|
import sanitizeHtml from 'sanitize-html';
|
|
|
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
|
|
|
+import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor';
|
|
|
+import UpcastWriter from '@ckeditor/ckeditor5-engine/src/view/upcastwriter';
|
|
|
import HTMLEmbedCommand from './htmlembedcommand';
|
|
|
+import { toWidget } from '@ckeditor/ckeditor5-widget/src/utils';
|
|
|
+import { clone } from 'lodash-es';
|
|
|
|
|
|
import '../theme/htmlembed.css';
|
|
|
-import { toWidget } from '@ckeditor/ckeditor5-widget/src/utils';
|
|
|
-import { stringify as viewStringify } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
|
|
|
|
|
|
/**
|
|
|
* The HTML embed editing feature.
|
|
|
@@ -36,6 +38,12 @@ export default class HTMLEmbedEditing extends Plugin {
|
|
|
const t = editor.t;
|
|
|
const schema = editor.model.schema;
|
|
|
const conversion = editor.conversion;
|
|
|
+ const viewDocument = editor.editing.view.document;
|
|
|
+
|
|
|
+ const upcastWriter = new UpcastWriter( viewDocument );
|
|
|
+ const htmlProcessor = new HtmlDataProcessor( viewDocument );
|
|
|
+
|
|
|
+ const saniteHtmlConfig = getSaniteHtmlConfig( sanitizeHtml.defaults );
|
|
|
|
|
|
schema.register( 'rawHtml', {
|
|
|
isObject: true,
|
|
|
@@ -49,13 +57,11 @@ export default class HTMLEmbedEditing extends Plugin {
|
|
|
classes: 'raw-html-embed'
|
|
|
},
|
|
|
model: ( viewElement, { writer } ) => {
|
|
|
- // Replace all view elements with their string presentations.
|
|
|
- const innerHTML = [ ...viewElement.getChildren() ]
|
|
|
- .map( child => viewStringify( child ) )
|
|
|
- .join( '' );
|
|
|
+ const fragment = upcastWriter.createDocumentFragment( viewElement.getChildren() );
|
|
|
+ const innerHtml = htmlProcessor.toData( fragment );
|
|
|
|
|
|
return writer.createElement( 'rawHtml', {
|
|
|
- value: innerHTML
|
|
|
+ value: innerHtml
|
|
|
} );
|
|
|
}
|
|
|
} );
|
|
|
@@ -75,9 +81,8 @@ export default class HTMLEmbedEditing extends Plugin {
|
|
|
const label = t( 'HTML snippet' );
|
|
|
const viewWrapper = writer.createContainerElement( 'div' );
|
|
|
|
|
|
- // TODO: `viewWrapper` should not be here but `toWidget` can be used only with the container element.
|
|
|
const rawElement = writer.createRawElement( 'div', { class: 'raw-html-embed' }, function( domElement ) {
|
|
|
- domElement.innerHTML = sanitizeHtml( modelElement.getAttribute( 'value' ) );
|
|
|
+ domElement.innerHTML = sanitizeHtml( modelElement.getAttribute( 'value' ), saniteHtmlConfig );
|
|
|
} );
|
|
|
|
|
|
writer.insert( writer.createPositionAt( viewWrapper, 0 ), rawElement );
|
|
|
@@ -100,7 +105,56 @@ export default class HTMLEmbedEditing extends Plugin {
|
|
|
// @param {String} label The element's label.
|
|
|
// @returns {module:engine/view/element~Element}
|
|
|
function toRawHtmlWidget( viewElement, writer, label ) {
|
|
|
- writer.setCustomProperty( 'rawHTML', true, viewElement );
|
|
|
+ writer.setCustomProperty( 'rawHtml', true, viewElement );
|
|
|
|
|
|
return toWidget( viewElement, writer, { label } );
|
|
|
}
|
|
|
+
|
|
|
+// Modifies the `defaultConfig` in order to match our needs. See #8204.
|
|
|
+//
|
|
|
+// @params {String} defaultConfig The default configuration that will be extended.
|
|
|
+// @returns {Object}
|
|
|
+function getSaniteHtmlConfig( defaultConfig ) {
|
|
|
+ const config = clone( defaultConfig );
|
|
|
+
|
|
|
+ config.allowedTags.push(
|
|
|
+ // Allows embedding iframes.
|
|
|
+ 'iframe',
|
|
|
+
|
|
|
+ // Allows embedding media.
|
|
|
+ 'audio',
|
|
|
+ 'video',
|
|
|
+ 'picture',
|
|
|
+ 'source',
|
|
|
+ 'img'
|
|
|
+ );
|
|
|
+
|
|
|
+ config.selfClosing.push( 'source' );
|
|
|
+
|
|
|
+ // Remove duplicates.
|
|
|
+ config.allowedTags = [ ...new Set( config.allowedTags ) ];
|
|
|
+
|
|
|
+ config.allowedSchemesAppliedToAttributes.push(
|
|
|
+ // Responsive images.
|
|
|
+ 'srcset'
|
|
|
+ );
|
|
|
+
|
|
|
+ for ( const htmlTag of config.allowedTags ) {
|
|
|
+ if ( !Array.isArray( config.allowedAttributes[ htmlTag ] ) ) {
|
|
|
+ config.allowedAttributes[ htmlTag ] = [];
|
|
|
+ }
|
|
|
+
|
|
|
+ // Allow inlining styles for all elements.
|
|
|
+ config.allowedAttributes[ htmlTag ].push( 'style' );
|
|
|
+ }
|
|
|
+
|
|
|
+ // Should we allow the `controls` attribute?
|
|
|
+ config.allowedAttributes.video.push( 'width', 'height', 'controls' );
|
|
|
+ config.allowedAttributes.audio.push( 'controls' );
|
|
|
+
|
|
|
+ config.allowedAttributes.iframe.push( 'src' );
|
|
|
+ config.allowedAttributes.img.push( 'srcset', 'sizes', 'src' );
|
|
|
+ config.allowedAttributes.source.push( 'src', 'srcset', 'media', 'sizes', 'type' );
|
|
|
+
|
|
|
+ return config;
|
|
|
+}
|