Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

T/ckeditor5/1243: Unify widget insertion #1545

Merged
merged 16 commits into from
Sep 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 38 additions & 7 deletions src/model/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -275,12 +275,12 @@ export default class Model {
*
* // insertContent() doesn't have to be used in a change() block. It can, though,
* // so this code could be moved to the callback defined above.
* editor.model.insertContent( docFrag, editor.model.document.selection );
* editor.model.insertContent( docFrag );
*
* Using `insertContent()` with HTML string converted to a model document fragment (similar to the pasting mechanism):
*
* // You can create your own HtmlDataProcessor instance or use editor.data.processor
* // if you haven't overriden the default one (which is HtmlDataProcessor instance).
* // if you haven't overridden the default one (which is HtmlDataProcessor instance).
* const htmlDP = new HtmlDataProcessor();
*
* // Convert an HTML string to a view document fragment.
Expand All @@ -296,15 +296,43 @@ export default class Model {
* // which has a loosened schema.
* const modelFragment = editor.data.toModel( viewFragment );
*
* editor.model.insertContent( modelFragment, editor.model.document.selection );
* editor.model.insertContent( modelFragment );
*
* By default this method will use the document selection but it can also be used with a position, range or selection instance.
*
* // Insert text at the current document selection position.
* editor.model.change( writer => {
* editor.model.insertContent( writer.createText( 'x' ) );
* } );
*
* // Insert text at given position - document selection will not be modified.
* editor.model.change( writer => {
* editor.model.insertContent( writer.createText( 'x' ), Position.createAt( doc.getRoot(), 2 ) );
* } );
*
* If an instance of {module:engine/model/selection~Selection} is passed as `selectable`
* it will be moved to the target position (where the document selection should be moved after the insertion).
*
* // Insert text replacing given selection instance.
* const selection = new Selection( paragraph, 'in' );
*
* editor.model.change( writer => {
* editor.model.insertContent( writer.createText( 'x' ), selection );
*
* // insertContent() modifies the passed selection instance so it can be used to set the document selection.
* // Note: This is not necessary when you passed document selection to insertContent().
* writer.setSelection( selection );
* } );
*
* @fires insertContent
* @param {module:engine/model/documentfragment~DocumentFragment|module:engine/model/item~Item} content The content to insert.
* @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
* Selection into which the content should be inserted.
* @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection|
* module:engine/model/position~Position|module:engine/model/element~Element|
* Iterable.<module:engine/model/range~Range>|module:engine/model/range~Range|null} [selectable=model.document.selection]
* Selection into which the content should be inserted. If not provided the current model document selection will be used.
*/
insertContent( content, selection ) {
insertContent( this, content, selection );
insertContent( content, selectable ) {
insertContent( this, content, selectable );
}

/**
Expand Down Expand Up @@ -534,6 +562,9 @@ export default class Model {
* The {@link #insertContent default action of that method} is implemented as a
* listener to this event so it can be fully customized by the features.
*
* **Note** The `selectable` parameter for the {@link #insertContent} is optional. When `undefined` value is passed the method uses
* `model.document.selection`.
*
* @event insertContent
* @param {Array} args The arguments passed to the original method.
*/
Expand Down
32 changes: 16 additions & 16 deletions src/model/selection.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,46 +35,46 @@ export default class Selection {
* or on the given {@link module:engine/model/element~Element element},
* or creates an empty selection if no arguments were passed.
*
* // Creates empty selection without ranges.
* // Creates empty selection without ranges.
* const selection = new Selection();
*
* // Creates selection at the given range.
* const range = new Range( start, end );
* const selection = new Selection( range );
*
* // Creates selection at the given ranges
* const ranges = [ new Range( start1, end2 ), new Range( star2, end2 ) ];
* const ranges = [ new Range( start1, end2 ), new Range( star2, end2 ) ];
* const selection = new Selection( ranges );
*
* // Creates selection from the other selection.
* // Note: It doesn't copies selection attributes.
* const otherSelection = new Selection();
* const selection = new Selection( otherSelection );
*
* // Creates selection from the given document selection.
* // Creates selection from the given document selection.
* // Note: It doesn't copies selection attributes.
* const documentSelection = new DocumentSelection( doc );
* const selection = new Selection( documentSelection );
*
* // Creates selection at the given position.
* // Creates selection at the given position.
* const position = new Position( root, path );
* const selection = new Selection( position );
*
* // Creates selection at the start position of the given element.
* // Creates selection at the start position of the given element.
* const paragraph = writer.createElement( 'paragraph' );
* const selection = new Selection( paragraph, offset );
*
* // Creates a range inside an {@link module:engine/model/element~Element element} which starts before the
* // first child of that element and ends after the last child of that element.
* // Creates a range inside an {@link module:engine/model/element~Element element} which starts before the
* // first child of that element and ends after the last child of that element.
* const selection = new Selection( paragraph, 'in' );
*
* // Creates a range on an {@link module:engine/model/item~Item item} which starts before the item and ends
* // just after the item.
* // Creates a range on an {@link module:engine/model/item~Item item} which starts before the item and ends
* // just after the item.
* const selection = new Selection( paragraph, 'on' );
*
* Selection's constructor allow passing additional options (`'backward'`) as the last argument.
*
* // Creates backward selection.
* // Creates backward selection.
* const selection = new Selection( range, { backward: true } );
*
* @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection|
Expand Down Expand Up @@ -326,32 +326,32 @@ export default class Selection {
* {@link module:engine/model/element~Element element}, {@link module:engine/model/position~Position position},
* {@link module:engine/model/range~Range range}, an iterable of {@link module:engine/model/range~Range ranges} or null.
*
* // Removes all selection's ranges.
* // Removes all selection's ranges.
* selection.setTo( null );
*
* // Sets selection to the given range.
* const range = new Range( start, end );
* selection.setTo( range );
*
* // Sets selection to given ranges.
* const ranges = [ new Range( start1, end2 ), new Range( star2, end2 ) ];
* const ranges = [ new Range( start1, end2 ), new Range( star2, end2 ) ];
* selection.setTo( ranges );
*
* // Sets selection to other selection.
* // Note: It doesn't copies selection attributes.
* const otherSelection = new Selection();
* selection.setTo( otherSelection );
*
* // Sets selection to the given document selection.
* // Sets selection to the given document selection.
* // Note: It doesn't copies selection attributes.
* const documentSelection = new DocumentSelection( doc );
* selection.setTo( documentSelection );
*
* // Sets collapsed selection at the given position.
* // Sets collapsed selection at the given position.
* const position = new Position( root, path );
* selection.setTo( position );
*
* // Sets collapsed selection at the position of the given node and an offset.
* // Sets collapsed selection at the position of the given node and an offset.
* selection.setTo( paragraph, offset );
*
* Creates a range inside an {@link module:engine/model/element~Element element} which starts before the first child of
Expand All @@ -365,7 +365,7 @@ export default class Selection {
*
* `Selection#setTo()`' method allow passing additional options (`backward`) as the last argument.
*
* // Sets backward selection.
* // Sets backward selection.
* const selection = new Selection( range, { backward: true } );
*
* @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection|
Expand Down
23 changes: 19 additions & 4 deletions src/model/utils/insertcontent.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,39 @@ import Element from '../element';
import Range from '../range';
import log from '@ckeditor/ckeditor5-utils/src/log';
import DocumentSelection from '../documentselection';
import Selection from '../selection';

/**
* Inserts content into the editor (specified selection) as one would expect the paste
* functionality to work.
*
* If an instance of {module:engine/model/selection~Selection} is passed as `selectable` it will be modified
* to the insertion selection (equal to a range to be selected after insertion).
*
* **Note:** Use {@link module:engine/model/model~Model#insertContent} instead of this function.
* This function is only exposed to be reusable in algorithms
* which change the {@link module:engine/model/model~Model#insertContent}
* This function is only exposed to be reusable in algorithms which change the {@link module:engine/model/model~Model#insertContent}
* method's behavior.
*
* @param {module:engine/model/model~Model} model The model in context of which the insertion
* should be performed.
* @param {module:engine/model/documentfragment~DocumentFragment|module:engine/model/item~Item} content The content to insert.
* @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection} selection
* @param {module:engine/model/selection~Selection|module:engine/model/documentselection~DocumentSelection|
* module:engine/model/position~Position|module:engine/model/element~Element|
* Iterable.<module:engine/model/range~Range>|module:engine/model/range~Range|null} [selectable=model.document.selection]
* Selection into which the content should be inserted.
*/
export default function insertContent( model, content, selection ) {
export default function insertContent( model, content, selectable ) {
model.change( writer => {
let selection;

if ( !selectable ) {
selection = model.document.selection;
} else if ( selectable instanceof Selection || selectable instanceof DocumentSelection ) {
selection = selectable;
} else {
selection = new Selection( selectable );
}

if ( !selection.isCollapsed ) {
model.deleteContent( selection );
}
Expand Down
18 changes: 14 additions & 4 deletions tests/model/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ describe( 'Model', () => {

model.on( 'insertContent', spy );

model.insertContent( new ModelText( 'a' ), model.document.selection );
model.insertContent( new ModelText( 'a' ) );

expect( spy.calledOnce ).to.be.true;
} );
Expand All @@ -357,7 +357,7 @@ describe( 'Model', () => {

setData( model, '<paragraph>fo[]ar</paragraph>' );

model.insertContent( new ModelText( 'ob' ), model.document.selection );
model.insertContent( new ModelText( 'ob' ) );

expect( getData( model ) ).to.equal( '<paragraph>foob[]ar</paragraph>' );
} );
Expand All @@ -367,7 +367,17 @@ describe( 'Model', () => {

setData( model, '<paragraph>fo[]ar</paragraph>' );

model.insertContent( new ModelDocumentFragment( [ new ModelText( 'ob' ) ] ), model.document.selection );
model.insertContent( new ModelDocumentFragment( [ new ModelText( 'ob' ) ] ) );

expect( getData( model ) ).to.equal( '<paragraph>foob[]ar</paragraph>' );
} );

it( 'should use current model selection if no selectable passed', () => {
schema.register( 'paragraph', { inheritAllFrom: '$block' } );

setData( model, '<paragraph>fo[]ar</paragraph>' );

model.insertContent( new ModelText( 'ob' ) );

expect( getData( model ) ).to.equal( '<paragraph>foob[]ar</paragraph>' );
} );
Expand All @@ -377,7 +387,7 @@ describe( 'Model', () => {
setData( model, '<paragraph>[]</paragraph>' );

model.change( writer => {
model.insertContent( new ModelText( 'abc' ), model.document.selection );
model.insertContent( new ModelText( 'abc' ) );
expect( writer.batch.operations ).to.length( 1 );
} );
} );
Expand Down
Loading