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

Commit

Permalink
Merge pull request #1623 from ckeditor/t/1615
Browse files Browse the repository at this point in the history
Feature: Introduced `DocumentSelection#markers` collection. Closes #1615.
  • Loading branch information
Piotr Jasiun authored Jan 4, 2019
2 parents b7b5b3b + b7f92d7 commit b2c1d72
Show file tree
Hide file tree
Showing 3 changed files with 295 additions and 1 deletion.
2 changes: 1 addition & 1 deletion src/controller/datacontroller.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export default class DataController {
* Data processor used during the conversion.
*
* @readonly
* @member {module:engine/dataProcessor~DataProcessor}
* @member {module:engine/dataprocessor/dataprocessor~DataProcessor}
*/
this.processor = dataProcessor;

Expand Down
47 changes: 47 additions & 0 deletions src/model/documentselection.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import LiveRange from './liverange';
import Text from './text';
import TextProxy from './textproxy';
import toMap from '@ckeditor/ckeditor5-utils/src/tomap';
import Collection from '@ckeditor/ckeditor5-utils/src/collection';
import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
import log from '@ckeditor/ckeditor5-utils/src/log';
import uid from '@ckeditor/ckeditor5-utils/src/uid';
Expand Down Expand Up @@ -149,6 +150,17 @@ export default class DocumentSelection {
return this._selection.isGravityOverridden;
}

/**
* A collection of selection markers.
* Marker is a selection marker when selection range is inside the marker range.
*
* @readonly
* @type {module:utils/collection~Collection.<module:engine/model/markercollection~Marker>}
*/
get markers() {
return this._selection.markers;
}

/**
* Used for the compatibility with the {@link module:engine/model/selection~Selection#isEqual} method.
*
Expand Down Expand Up @@ -526,6 +538,12 @@ class LiveSelection extends Selection {
constructor( doc ) {
super();

// List of selection markers.
// Marker is a selection marker when selection range is inside the marker range.
//
// @type {module:utils/collection~Collection}
this.markers = new Collection( { idProperty: 'name' } );

// Document which owns this selection.
//
// @protected
Expand Down Expand Up @@ -586,6 +604,9 @@ class LiveSelection extends Selection {
} );

this.listenTo( this._document, 'change', ( evt, batch ) => {
// Update selection's markers.
this._updateMarkers();

// Update selection's attributes.
this._updateAttributes( false );

Expand Down Expand Up @@ -788,6 +809,32 @@ class LiveSelection extends Selection {
return liveRange;
}

_updateMarkers() {
const markers = [];

for ( const marker of this._model.markers ) {
const markerRange = marker.getRange();

for ( const selectionRange of this.getRanges() ) {
if ( markerRange.containsRange( selectionRange, !selectionRange.isCollapsed ) ) {
markers.push( marker );
}
}
}

for ( const marker of markers ) {
if ( !this.markers.has( marker ) ) {
this.markers.add( marker );
}
}

for ( const marker of Array.from( this.markers ) ) {
if ( !markers.includes( marker ) ) {
this.markers.remove( marker );
}
}
}

// Updates this selection attributes according to its ranges and the {@link module:engine/model/document~Document model document}.
//
// @protected
Expand Down
247 changes: 247 additions & 0 deletions tests/model/documentselection.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import MoveOperation from '../../src/model/operation/moveoperation';
import AttributeOperation from '../../src/model/operation/attributeoperation';
import SplitOperation from '../../src/model/operation/splitoperation';
import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
import Collection from '@ckeditor/ckeditor5-utils/src/collection';
import count from '@ckeditor/ckeditor5-utils/src/count';
import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
import { setData, getData } from '../../src/dev-utils/model';
Expand Down Expand Up @@ -197,6 +198,252 @@ describe( 'DocumentSelection', () => {
} );
} );

describe( 'markers', () => {
it( 'should implement #markers collection', () => {
expect( selection.markers ).to.instanceof( Collection );
expect( selection.markers ).to.length( 0 );
} );

it( 'should add markers to the collection when selection is inside the marker range', () => {
model.change( writer => {
writer.setSelection( writer.createRange(
writer.createPositionFromPath( root, [ 2, 2 ] ),
writer.createPositionFromPath( root, [ 2, 4 ] )
) );

writer.addMarker( 'marker-1', {
range: writer.createRange(
writer.createPositionFromPath( root, [ 0, 0 ] ),
writer.createPositionFromPath( root, [ 2, 2 ] )
),
usingOperation: false
} );

writer.addMarker( 'marker-2', {
range: writer.createRange(
writer.createPositionFromPath( root, [ 2, 2 ] ),
writer.createPositionFromPath( root, [ 2, 4 ] )
),
usingOperation: false
} );

writer.addMarker( 'marker-3', {
range: writer.createRange(
writer.createPositionFromPath( root, [ 2, 1 ] ),
writer.createPositionFromPath( root, [ 2, 5 ] )
),
usingOperation: false
} );

writer.addMarker( 'marker-4', {
range: writer.createRange(
writer.createPositionFromPath( root, [ 2, 4 ] ),
writer.createPositionFromPath( root, [ 3, 0 ] )
),
usingOperation: false
} );
} );

expect( selection.markers.map( marker => marker.name ) ).to.have.members( [ 'marker-2', 'marker-3' ] );
} );

it( 'should update markers after selection change', () => {
model.change( writer => {
writer.setSelection( writer.createRange(
writer.createPositionFromPath( root, [ 2, 1 ] ),
writer.createPositionFromPath( root, [ 2, 2 ] )
) );

writer.addMarker( 'marker-1', {
range: writer.createRange(
writer.createPositionFromPath( root, [ 2, 0 ] ),
writer.createPositionFromPath( root, [ 2, 6 ] )
),
usingOperation: false
} );

writer.addMarker( 'marker-2', {
range: writer.createRange(
writer.createPositionFromPath( root, [ 2, 0 ] ),
writer.createPositionFromPath( root, [ 2, 3 ] )
),
usingOperation: false
} );

writer.addMarker( 'marker-3', {
range: writer.createRange(
writer.createPositionFromPath( root, [ 2, 3 ] ),
writer.createPositionFromPath( root, [ 2, 6 ] )
),
usingOperation: false
} );
} );

expect( selection.markers.map( marker => marker.name ) ).to.have.members( [ 'marker-1', 'marker-2' ] );

model.change( writer => {
writer.setSelection( writer.createRange(
writer.createPositionFromPath( root, [ 2, 4 ] ),
writer.createPositionFromPath( root, [ 2, 5 ] )
) );
} );

expect( selection.markers.map( marker => marker.name ) ).to.have.members( [ 'marker-1', 'marker-3' ] );
} );

it( 'should update markers after markers change', () => {
model.change( writer => {
writer.setSelection( writer.createRange(
writer.createPositionFromPath( root, [ 2, 1 ] ),
writer.createPositionFromPath( root, [ 2, 2 ] )
) );

writer.addMarker( 'marker-1', {
range: writer.createRange(
writer.createPositionFromPath( root, [ 2, 0 ] ),
writer.createPositionFromPath( root, [ 2, 6 ] )
),
usingOperation: false
} );

writer.addMarker( 'marker-2', {
range: writer.createRange(
writer.createPositionFromPath( root, [ 2, 0 ] ),
writer.createPositionFromPath( root, [ 2, 3 ] )
),
usingOperation: false
} );

writer.addMarker( 'marker-3', {
range: writer.createRange(
writer.createPositionFromPath( root, [ 2, 3 ] ),
writer.createPositionFromPath( root, [ 2, 6 ] )
),
usingOperation: false
} );
} );

expect( selection.markers.map( marker => marker.name ), 1 ).to.have.members( [ 'marker-1', 'marker-2' ] );

model.change( writer => {
writer.removeMarker( 'marker-1' );

writer.updateMarker( 'marker-2', {
range: writer.createRange(
writer.createPositionFromPath( root, [ 2, 3 ] ),
writer.createPositionFromPath( root, [ 2, 6 ] )
),
usingOperation: false
} );

writer.updateMarker( 'marker-3', {
range: writer.createRange(
writer.createPositionFromPath( root, [ 2, 0 ] ),
writer.createPositionFromPath( root, [ 2, 3 ] )
),
usingOperation: false
} );
} );

expect( selection.markers.map( marker => marker.name ), 2 ).to.have.members( [ 'marker-3' ] );
} );

it( 'should not add marker when collapsed selection is on the marker left bound', () => {
model.change( writer => {
writer.setSelection( writer.createRange(
writer.createPositionFromPath( root, [ 2, 2 ] ),
writer.createPositionFromPath( root, [ 2, 4 ] )
) );

writer.addMarker( 'marker', {
range: writer.createRange(
writer.createPositionFromPath( root, [ 2, 2 ] )
),
usingOperation: false
} );
} );

expect( selection.markers ).to.length( 0 );
} );

it( 'should not add marker when collapsed selection is on the marker right bound', () => {
model.change( writer => {
writer.setSelection( writer.createRange(
writer.createPositionFromPath( root, [ 2, 4 ] )
) );

writer.addMarker( 'marker', {
range: writer.createRange(
writer.createPositionFromPath( root, [ 2, 2 ] ),
writer.createPositionFromPath( root, [ 2, 4 ] )
),
usingOperation: false
} );
} );

expect( selection.markers ).to.length( 0 );
} );

it( 'should add marker when non-collapsed selection is inside a marker and touches the left bound', () => {
model.change( writer => {
writer.setSelection( writer.createRange(
writer.createPositionFromPath( root, [ 2, 1 ] ),
writer.createPositionFromPath( root, [ 2, 3 ] )
) );

writer.addMarker( 'marker', {
range: writer.createRange(
writer.createPositionFromPath( root, [ 2, 1 ] ),
writer.createPositionFromPath( root, [ 2, 5 ] )
),
usingOperation: false
} );
} );

expect( selection.markers.map( marker => marker.name ) ).to.have.members( [ 'marker' ] );
} );

it( 'should add marker when non-collapsed selection is inside a marker and touches the right bound', () => {
model.change( writer => {
writer.setSelection( writer.createRange(
writer.createPositionFromPath( root, [ 2, 2 ] ),
writer.createPositionFromPath( root, [ 2, 5 ] )
) );

writer.addMarker( 'marker', {
range: writer.createRange(
writer.createPositionFromPath( root, [ 2, 1 ] ),
writer.createPositionFromPath( root, [ 2, 5 ] )
),
usingOperation: false
} );
} );

expect( selection.markers.map( marker => marker.name ) ).to.have.members( [ 'marker' ] );
} );

it( 'should add marker of selected widget', () => {
root._insertChild( 0, new Element( 'widget' ) );

model.change( writer => {
writer.setSelection( writer.createRange(
writer.createPositionFromPath( root, [ 0 ] ),
writer.createPositionFromPath( root, [ 1 ] )
) );

writer.addMarker( 'marker', {
range: writer.createRange(
writer.createPositionFromPath( root, [ 0 ] ),
writer.createPositionFromPath( root, [ 1 ] )
),
usingOperation: false
} );
} );

expect( selection.markers.map( marker => marker.name ) ).to.have.members( [ 'marker' ] );
} );
} );

describe( 'destroy()', () => {
it( 'should unbind all events', () => {
selection._setTo( [ range, liveRange ] );
Expand Down

0 comments on commit b2c1d72

Please sign in to comment.