|
|
@@ -98,10 +98,6 @@ export default class HtmlEmbedEditing extends Plugin {
|
|
|
const upcastWriter = new UpcastWriter( view.document );
|
|
|
const htmlProcessor = new HtmlDataProcessor( view.document );
|
|
|
|
|
|
- const editButtonDOMElement = getButtonDOMElement( editor.locale, 'edit' );
|
|
|
- const saveButtonDOMElement = getButtonDOMElement( editor.locale, 'save' );
|
|
|
- const cancelButtonDOMElement = getButtonDOMElement( editor.locale, 'cancel' );
|
|
|
-
|
|
|
editor.conversion.for( 'upcast' ).elementToElement( {
|
|
|
view: {
|
|
|
name: 'div',
|
|
|
@@ -134,113 +130,68 @@ export default class HtmlEmbedEditing extends Plugin {
|
|
|
const widgetLabel = t( 'HTML snippet' );
|
|
|
const placeholder = t( 'Paste raw HTML here...' );
|
|
|
|
|
|
- const widgetView = writer.createContainerElement( 'div', {
|
|
|
+ const viewContainer = writer.createContainerElement( 'div', {
|
|
|
class: 'raw-html-embed'
|
|
|
} );
|
|
|
|
|
|
- const rawHtmlContainer = writer.createContainerElement( 'div', {
|
|
|
+ const viewRawHtmlContainer = writer.createContainerElement( 'div', {
|
|
|
class: 'raw-html-embed__content-wrapper'
|
|
|
} );
|
|
|
|
|
|
- const textareaAttributes = {
|
|
|
- placeholder,
|
|
|
- disabled: true,
|
|
|
- class: 'ck ck-reset ck-input ck-input-text raw-html-embed__source'
|
|
|
- };
|
|
|
-
|
|
|
- // The editing raw HTML field.
|
|
|
- const sourceElement = writer.createUIElement( 'textarea', textareaAttributes, function( domDocument ) {
|
|
|
- const root = this.toDomElement( domDocument );
|
|
|
-
|
|
|
- writer.setCustomProperty( 'domElement', root, sourceElement );
|
|
|
- root.value = modelElement.getAttribute( 'value' ) || '';
|
|
|
- root.readOnly = editor.isReadOnly;
|
|
|
-
|
|
|
- // When focusing the "edit source" element, the model selection must be updated.
|
|
|
- // If we do not do it, the `updateHtmlEmbed` command will fail because it
|
|
|
- // searches the `rawHtml` element based on selection.
|
|
|
- root.addEventListener( 'focus', () => {
|
|
|
- editor.model.change( writer => {
|
|
|
- writer.setSelection( modelElement, 'on' );
|
|
|
- } );
|
|
|
- } );
|
|
|
-
|
|
|
- root.addEventListener( 'input', () => {
|
|
|
- editor.execute( 'updateHtmlEmbed', root.value );
|
|
|
- } );
|
|
|
-
|
|
|
- return root;
|
|
|
- } );
|
|
|
-
|
|
|
- const buttonsWrapperAttributes = {
|
|
|
- class: 'raw-html-embed__buttons-wrapper'
|
|
|
- };
|
|
|
+ const viewTextarea = writer.createUIElement(
|
|
|
+ 'textarea',
|
|
|
+ {
|
|
|
+ placeholder,
|
|
|
+ disabled: true,
|
|
|
+ class: 'ck ck-reset ck-input ck-input-text raw-html-embed__source'
|
|
|
+ },
|
|
|
+ domDocument => {
|
|
|
+ return createDomTextarea( { editor, writer, viewTextarea, modelElement, domDocument } );
|
|
|
+ }
|
|
|
+ );
|
|
|
|
|
|
// The switch button between preview and editing HTML.
|
|
|
- const buttonsWrapper = writer.createUIElement( 'div', buttonsWrapperAttributes, function( domDocument ) {
|
|
|
- const buttonsWrapperDOMElement = this.toDomElement( domDocument );
|
|
|
-
|
|
|
- buttonsWrapperDOMElement.appendChild( editButtonDOMElement.cloneNode( true ) );
|
|
|
-
|
|
|
- writer.setCustomProperty( 'domElement', buttonsWrapperDOMElement, buttonsWrapper );
|
|
|
-
|
|
|
- buttonsWrapperDOMElement.addEventListener( 'click', evt => {
|
|
|
- view.change( writer => {
|
|
|
- if ( htmlEmbedConfig.showPreviews ) {
|
|
|
- if ( widgetView.hasClass( DISPLAY_PREVIEW_CLASS ) ) {
|
|
|
- writer.setAttribute( IGNORE_EVENTS_ATTRIBUTE, 'true', rawHtmlContainer );
|
|
|
- writer.removeClass( DISPLAY_PREVIEW_CLASS, widgetView );
|
|
|
- } else {
|
|
|
- writer.addClass( DISPLAY_PREVIEW_CLASS, widgetView );
|
|
|
- writer.removeAttribute( IGNORE_EVENTS_ATTRIBUTE, rawHtmlContainer );
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- const textarea = sourceElement.getCustomProperty( 'domElement' );
|
|
|
- textarea.disabled = !textarea.disabled;
|
|
|
-
|
|
|
- while ( buttonsWrapperDOMElement.firstChild ) {
|
|
|
- buttonsWrapperDOMElement.firstChild.remove();
|
|
|
- }
|
|
|
-
|
|
|
- if ( textarea.disabled ) {
|
|
|
- buttonsWrapperDOMElement.appendChild( editButtonDOMElement.cloneNode( true ) );
|
|
|
- } else {
|
|
|
- buttonsWrapperDOMElement.appendChild( saveButtonDOMElement.cloneNode( true ) );
|
|
|
- buttonsWrapperDOMElement.appendChild( cancelButtonDOMElement.cloneNode( true ) );
|
|
|
- }
|
|
|
+ const viewButtonsWrapper = writer.createUIElement(
|
|
|
+ 'div',
|
|
|
+ {
|
|
|
+ class: 'raw-html-embed__buttons-wrapper'
|
|
|
+ },
|
|
|
+ function( domDocument ) {
|
|
|
+ return createDomButtonsWrapper( {
|
|
|
+ editor, domDocument, viewButtonsWrapper, htmlEmbedConfig,
|
|
|
+ writer, viewContainer, viewRawHtmlContainer, viewTextarea
|
|
|
} );
|
|
|
+ }
|
|
|
+ );
|
|
|
|
|
|
- evt.preventDefault();
|
|
|
- } );
|
|
|
-
|
|
|
- return buttonsWrapperDOMElement;
|
|
|
- } );
|
|
|
-
|
|
|
- writer.insert( writer.createPositionAt( widgetView, 0 ), rawHtmlContainer );
|
|
|
- writer.insert( writer.createPositionAt( rawHtmlContainer, 0 ), buttonsWrapper );
|
|
|
- writer.insert( writer.createPositionAt( rawHtmlContainer, 1 ), sourceElement );
|
|
|
+ writer.insert( writer.createPositionAt( viewContainer, 0 ), viewRawHtmlContainer );
|
|
|
+ writer.insert( writer.createPositionAt( viewRawHtmlContainer, 0 ), viewButtonsWrapper );
|
|
|
+ writer.insert( writer.createPositionAt( viewRawHtmlContainer, 1 ), viewTextarea );
|
|
|
|
|
|
// The container that renders the HTML should be created only when `htmlEmbed.showPreviews=true` in the config.
|
|
|
if ( htmlEmbedConfig.showPreviews ) {
|
|
|
- writer.addClass( [ 'raw-html-embed_preview_enabled', DISPLAY_PREVIEW_CLASS ], widgetView );
|
|
|
+ writer.addClass( [ 'raw-html-embed_preview_enabled', DISPLAY_PREVIEW_CLASS ], viewContainer );
|
|
|
|
|
|
- const previewContainer = writer.createRawElement( 'div', { class: 'raw-html-embed__preview' }, function( domElement ) {
|
|
|
- writer.setCustomProperty( 'domElement', domElement, previewContainer );
|
|
|
+ const viewPreviewContainer = writer.createRawElement(
|
|
|
+ 'div',
|
|
|
+ { class: 'raw-html-embed__preview' },
|
|
|
+ function( domElement ) {
|
|
|
+ writer.setCustomProperty( 'domElement', domElement, viewPreviewContainer );
|
|
|
|
|
|
- const sanitizeOutput = htmlEmbedConfig.sanitizeHtml( modelElement.getAttribute( 'value' ) || '' );
|
|
|
- domElement.innerHTML = sanitizeOutput.html;
|
|
|
- } );
|
|
|
+ const sanitizedOutput = htmlEmbedConfig.sanitizeHtml( modelElement.getAttribute( 'value' ) || '' );
|
|
|
+ domElement.innerHTML = sanitizedOutput.html;
|
|
|
+ }
|
|
|
+ );
|
|
|
|
|
|
- writer.insert( writer.createPositionAt( rawHtmlContainer, 2 ), previewContainer );
|
|
|
+ writer.insert( writer.createPositionAt( viewRawHtmlContainer, 2 ), viewPreviewContainer );
|
|
|
}
|
|
|
|
|
|
// Listen to read-only changes.
|
|
|
this.listenTo( editor, 'change:isReadOnly', ( evt, name, value ) => {
|
|
|
- sourceElement.getCustomProperty( 'domElement' ).readOnly = value;
|
|
|
+ viewTextarea.getCustomProperty( 'domElement' ).readOnly = value;
|
|
|
} );
|
|
|
|
|
|
- return toRawHtmlWidget( widgetView, writer, widgetLabel );
|
|
|
+ return toRawHtmlWidget( viewContainer, writer, widgetLabel );
|
|
|
}
|
|
|
} );
|
|
|
|
|
|
@@ -248,6 +199,77 @@ export default class HtmlEmbedEditing extends Plugin {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+function createDomTextarea( { editor, writer, viewTextarea, modelElement, domDocument } ) {
|
|
|
+ const domTextarea = viewTextarea.toDomElement( domDocument );
|
|
|
+
|
|
|
+ writer.setCustomProperty( 'domElement', domTextarea, viewTextarea );
|
|
|
+
|
|
|
+ domTextarea.value = modelElement.getAttribute( 'value' ) || '';
|
|
|
+ domTextarea.readOnly = editor.isReadOnly;
|
|
|
+
|
|
|
+ // When focusing the "edit source" element, the model selection must be updated.
|
|
|
+ // If we do not do it, the `updateHtmlEmbed` command will fail because it
|
|
|
+ // searches the `rawHtml` element based on selection.
|
|
|
+ domTextarea.addEventListener( 'focus', () => {
|
|
|
+ editor.model.change( writer => {
|
|
|
+ writer.setSelection( modelElement, 'on' );
|
|
|
+ } );
|
|
|
+ } );
|
|
|
+
|
|
|
+ domTextarea.addEventListener( 'input', () => {
|
|
|
+ editor.execute( 'updateHtmlEmbed', domTextarea.value );
|
|
|
+ } );
|
|
|
+
|
|
|
+ return domTextarea;
|
|
|
+}
|
|
|
+
|
|
|
+function createDomButtonsWrapper( {
|
|
|
+ editor, domDocument, viewButtonsWrapper, htmlEmbedConfig,
|
|
|
+ writer, viewContainer, viewRawHtmlContainer, viewTextarea
|
|
|
+} ) {
|
|
|
+ const domButtonsWrapper = viewButtonsWrapper.toDomElement( domDocument );
|
|
|
+
|
|
|
+ const domEditButton = createDomButton( editor.locale, 'edit' );
|
|
|
+ const domSaveButton = createDomButton( editor.locale, 'save' );
|
|
|
+ const domCancelButton = createDomButton( editor.locale, 'cancel' );
|
|
|
+
|
|
|
+ domButtonsWrapper.appendChild( domEditButton.cloneNode( true ) );
|
|
|
+
|
|
|
+ writer.setCustomProperty( 'domElement', domButtonsWrapper, viewButtonsWrapper );
|
|
|
+
|
|
|
+ domButtonsWrapper.addEventListener( 'click', evt => {
|
|
|
+ editor.editing.view.change( writer => {
|
|
|
+ if ( htmlEmbedConfig.showPreviews ) {
|
|
|
+ if ( viewContainer.hasClass( DISPLAY_PREVIEW_CLASS ) ) {
|
|
|
+ writer.setAttribute( IGNORE_EVENTS_ATTRIBUTE, 'true', viewRawHtmlContainer );
|
|
|
+ writer.removeClass( DISPLAY_PREVIEW_CLASS, viewContainer );
|
|
|
+ } else {
|
|
|
+ writer.addClass( DISPLAY_PREVIEW_CLASS, viewContainer );
|
|
|
+ writer.removeAttribute( IGNORE_EVENTS_ATTRIBUTE, viewRawHtmlContainer );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const textarea = viewTextarea.getCustomProperty( 'domElement' );
|
|
|
+ textarea.disabled = !textarea.disabled;
|
|
|
+
|
|
|
+ while ( domButtonsWrapper.firstChild ) {
|
|
|
+ domButtonsWrapper.firstChild.remove();
|
|
|
+ }
|
|
|
+
|
|
|
+ if ( textarea.disabled ) {
|
|
|
+ domButtonsWrapper.appendChild( domEditButton.cloneNode( true ) );
|
|
|
+ } else {
|
|
|
+ domButtonsWrapper.appendChild( domSaveButton.cloneNode( true ) );
|
|
|
+ domButtonsWrapper.appendChild( domCancelButton.cloneNode( true ) );
|
|
|
+ }
|
|
|
+ } );
|
|
|
+
|
|
|
+ evt.preventDefault();
|
|
|
+ } );
|
|
|
+
|
|
|
+ return domButtonsWrapper;
|
|
|
+}
|
|
|
+
|
|
|
// Returns a converter that handles the `value` attribute of the `rawHtml` element.
|
|
|
//
|
|
|
// It updates the source (`textarea`) value and passes an HTML to the preview element if `htmlEmbed.showPreviews=true`.
|
|
|
@@ -258,15 +280,15 @@ function downcastRawHtmlValueAttribute( htmlEmbedConfig ) {
|
|
|
return dispatcher => {
|
|
|
dispatcher.on( 'attribute:value:rawHtml', ( evt, data, conversionApi ) => {
|
|
|
const widgetView = conversionApi.mapper.toViewElement( data.item );
|
|
|
- const rawHtmlContainer = widgetView.getChild( 1 );
|
|
|
- const textareaDomElement = rawHtmlContainer.getChild( 1 ).getCustomProperty( 'domElement' );
|
|
|
+ const viewRawHtmlContainer = widgetView.getChild( 1 );
|
|
|
+ const textareaDomElement = viewRawHtmlContainer.getChild( 1 ).getCustomProperty( 'domElement' );
|
|
|
|
|
|
if ( textareaDomElement ) {
|
|
|
textareaDomElement.value = data.item.getAttribute( 'value' );
|
|
|
}
|
|
|
|
|
|
if ( htmlEmbedConfig.showPreviews ) {
|
|
|
- const previewDomElement = rawHtmlContainer.getChild( 2 ).getCustomProperty( 'domElement' );
|
|
|
+ const previewDomElement = viewRawHtmlContainer.getChild( 2 ).getCustomProperty( 'domElement' );
|
|
|
|
|
|
if ( previewDomElement ) {
|
|
|
const sanitizeOutput = htmlEmbedConfig.sanitizeHtml( data.item.getAttribute( 'value' ) || '' );
|
|
|
@@ -299,7 +321,7 @@ function toRawHtmlWidget( viewElement, writer, label ) {
|
|
|
// @param {module:utils/locale~Locale} locale Editor locale.
|
|
|
// @param {'edit'|'save'|'cancel'} type Type of button to create.
|
|
|
// @returns {HTMLElement}
|
|
|
-function getButtonDOMElement( locale, type ) {
|
|
|
+function createDomButton( locale, type ) {
|
|
|
const t = locale.t;
|
|
|
const buttonView = new ButtonView( locale );
|
|
|
|