Skip to content

Commit

Permalink
Merge pull request #8478 from ckeditor/i/8328
Browse files Browse the repository at this point in the history
Fix (html-embed): Fixed saving the widget content after it lost the selection. Closes #8328.
  • Loading branch information
niegowski committed Nov 23, 2020
2 parents 5dd05b9 + 395c9f6 commit 2151b57
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 1 deletion.
15 changes: 15 additions & 0 deletions packages/ckeditor5-html-embed/src/htmlembedediting.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,21 @@ export default class HtmlEmbedEditing extends Plugin {
domContentWrapper = domElement;

renderContent( { domElement, editor, state, props } );

// Since there is a `data-cke-ignore-events` attribute set on the wrapper element in the editable mode,
// the explicit `mousedown` handler on the `capture` phase is needed to move the selection onto the whole
// HTML embed widget.
domContentWrapper.addEventListener( 'mousedown', () => {
if ( state.isEditable ) {
const model = editor.model;
const selectedElement = model.document.selection.getSelectedElement();

// Move the selection onto the whole HTML embed widget if it's currently not selected.
if ( selectedElement !== modelElement ) {
model.change( writer => writer.setSelection( modelElement, 'on' ) );
}
}
}, true );
} );

// API exposed on each raw HTML embed widget so other features can control a particular widget.
Expand Down
93 changes: 92 additions & 1 deletion packages/ckeditor5-html-embed/tests/htmlembedediting.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

/* global console, document */
/* global console, document, Event */

import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
import HtmlEmbedEditing from '../src/htmlembedediting';
Expand Down Expand Up @@ -437,6 +437,61 @@ describe( 'HtmlEmbedEditing', () => {
sinon.assert.calledOnce( spy );
} );

it( 'does not select the unselected `rawHtml` element, if it is not in the editable mode', () => {
setModelData( model, '[<rawHtml value="foo"></rawHtml>]<rawHtml value="bar"></rawHtml>' );

// Get the second widget.
const widget = viewDocument.getRoot().getChild( 1 );
const contentWrapper = widget.getChild( 1 );
const domContentWrapper = editor.editing.view.domConverter.mapViewToDom( contentWrapper );

domContentWrapper.querySelector( 'textarea' ).dispatchEvent( new Event( 'mousedown' ) );

expect( getModelData( model ) ).to.equal( '[<rawHtml value="foo"></rawHtml>]<rawHtml value="bar"></rawHtml>' );
} );

it( 'does not unnecessarily select an already selected `rawHtml` element in the editable mode', () => {
setModelData( model, '[<rawHtml value="foo"></rawHtml>]' );

const spy = sinon.spy();

model.document.selection.on( 'change:range', spy );

const widget = viewDocument.getRoot().getChild( 0 );
const contentWrapper = widget.getChild( 1 );
const domContentWrapper = editor.editing.view.domConverter.mapViewToDom( contentWrapper );

widget.getCustomProperty( 'rawHtmlApi' ).makeEditable();

domContentWrapper.querySelector( 'textarea' ).dispatchEvent( new Event( 'mousedown' ) );

expect( spy.notCalled ).to.be.true;
} );

it( 'selects the unselected `rawHtml` element in editable mode after clicking on its textarea', () => {
setModelData( model, '<rawHtml value="foo"></rawHtml><rawHtml value="bar"></rawHtml>' );

const widgetFoo = viewDocument.getRoot().getChild( 0 );
const widgetBar = viewDocument.getRoot().getChild( 1 );

const contentWrapperFoo = widgetFoo.getChild( 1 );
const contentWrapperBar = widgetBar.getChild( 1 );

const domContentWrapperFoo = editor.editing.view.domConverter.mapViewToDom( contentWrapperFoo );
const domContentWrapperBar = editor.editing.view.domConverter.mapViewToDom( contentWrapperBar );

widgetFoo.getCustomProperty( 'rawHtmlApi' ).makeEditable();
widgetBar.getCustomProperty( 'rawHtmlApi' ).makeEditable();

domContentWrapperFoo.querySelector( 'textarea' ).dispatchEvent( new Event( 'mousedown' ) );

expect( getModelData( model ) ).to.equal( '[<rawHtml value="foo"></rawHtml>]<rawHtml value="bar"></rawHtml>' );

domContentWrapperBar.querySelector( 'textarea' ).dispatchEvent( new Event( 'mousedown' ) );

expect( getModelData( model ) ).to.equal( '<rawHtml value="foo"></rawHtml>[<rawHtml value="bar"></rawHtml>]' );
} );

describe( 'rawHtmlApi.makeEditable()', () => {
it( 'makes the textarea editable', () => {
setModelData( model, '<rawHtml value="foo"></rawHtml>' );
Expand All @@ -462,6 +517,42 @@ describe( 'HtmlEmbedEditing', () => {
expect( getModelData( model ) ).to.equal( '[<rawHtml value="bar"></rawHtml>]' );
} );

it( 'saves the new value to the model if given `rawHtml` element is not selected', () => {
setModelData( model, '<rawHtml value="foo"></rawHtml><rawHtml value="bar"></rawHtml>' );

const widgetFoo = viewDocument.getRoot().getChild( 0 );
const widgetBar = viewDocument.getRoot().getChild( 1 );

const contentWrapperFoo = widgetFoo.getChild( 1 );
const contentWrapperBar = widgetBar.getChild( 1 );

const domContentWrapperFoo = editor.editing.view.domConverter.mapViewToDom( contentWrapperFoo );
const domContentWrapperBar = editor.editing.view.domConverter.mapViewToDom( contentWrapperBar );

widgetFoo.getCustomProperty( 'rawHtmlApi' ).makeEditable();
widgetBar.getCustomProperty( 'rawHtmlApi' ).makeEditable();

domContentWrapperFoo.querySelector( 'textarea' ).value = 'FOO';

const domSaveButtonFoo = domContentWrapperFoo.querySelector( '.raw-html-embed__save-button' );

// Simulate the click event on the Save button from the first widget.
domSaveButtonFoo.dispatchEvent( new Event( 'mousedown' ) );
domSaveButtonFoo.dispatchEvent( new Event( 'mouseup' ) );
domSaveButtonFoo.dispatchEvent( new Event( 'click' ) );

domContentWrapperBar.querySelector( 'textarea' ).value = 'BAR';

const domSaveButtonBar = domContentWrapperBar.querySelector( '.raw-html-embed__save-button' );

// Simulate the click event on the Save button from the second widget.
domSaveButtonBar.dispatchEvent( new Event( 'mousedown' ) );
domSaveButtonBar.dispatchEvent( new Event( 'mouseup' ) );
domSaveButtonBar.dispatchEvent( new Event( 'click' ) );

expect( getModelData( model ) ).to.equal( '<rawHtml value="FOO"></rawHtml>[<rawHtml value="BAR"></rawHtml>]' );
} );

it( 'turns back to the non-editable mode and updates the textarea value', () => {
setModelData( model, '<rawHtml value="foo"></rawHtml>' );
const widget = viewDocument.getRoot().getChild( 0 );
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<div id="editor">
<p>Sample text</p>
<div class="raw-html-embed">
Text inside HTML embed widget
</div>
</div>
26 changes: 26 additions & 0 deletions packages/ckeditor5-html-embed/tests/manual/tickets/8328/1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

/* globals window, document, console */

import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset';
import HtmlEmbed from '../../../../src/htmlembed';

ClassicEditor
.create( document.querySelector( '#editor' ), {
plugins: [ ArticlePluginSet, HtmlEmbed ],
toolbar: [
'heading', '|', 'bold', 'italic', 'link', '|',
'bulletedList', 'numberedList', 'blockQuote', 'insertTable', '|',
'undo', 'redo', '|', 'htmlEmbed'
]
} )
.then( editor => {
window.editor = editor;
} )
.catch( err => {
console.error( err.stack );
} );
21 changes: 21 additions & 0 deletions packages/ckeditor5-html-embed/tests/manual/tickets/8328/1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
## Can't save an HTML embed after pasting a text from the editor [#8328](https://github.com/ckeditor/ckeditor5/issues/8328)

### Scenario 1
1. Click on `Edit source` button in the existing HTML embed widget.
2. Select the text above the widget - this removes the selection from the HTML embed.
3. Copy selected text.
4. Paste it to the HTML embed.
5. Save changes.

**Expected result**: Copied text is saved.

### Scenario 2
1. Insert new HTML embed widget below the existing one.
2. Click on `Edit source` button in the first HTML embed.
3. Click on `Edit source` button in the second HTML embed.
4. Make sure that the **second** HTML embed is currently selected.
5. Type some text in the **first** HTML embed and save changes.
6. Make sure that the **first** HTML embed is currently selected.
7. Type some text in the **second** HTML embed and save changes.

**Expected result**: Typed text is saved in the appropriate HTML embed.

0 comments on commit 2151b57

Please sign in to comment.