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

Commit 5106014

Browse files
authored
Merge pull request #1818 from ckeditor/i/6133
Feature: Introduced `DocumentSelection#event:change:marker`. Closes ckeditor/ckeditor5#6133.
2 parents 1c3749e + 5822fd4 commit 5106014

File tree

2 files changed

+243
-0
lines changed

2 files changed

+243
-0
lines changed

src/model/documentselection.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export default class DocumentSelection {
6363

6464
this._selection.delegate( 'change:range' ).to( this );
6565
this._selection.delegate( 'change:attribute' ).to( this );
66+
this._selection.delegate( 'change:marker' ).to( this );
6667
}
6768

6869
/**
@@ -542,6 +543,17 @@ mix( DocumentSelection, EmitterMixin );
542543
* @param {Array.<String>} attributeKeys Array containing keys of attributes that changed.
543544
*/
544545

546+
/**
547+
* Fired when selection marker(s) changed.
548+
*
549+
* @event change:marker
550+
* @param {Boolean} directChange This is always set to `false` in case of `change:marker` event as there is no possibility
551+
* to change markers directly through {@link module:engine/model/documentselection~DocumentSelection} API.
552+
* See also {@link module:engine/model/documentselection~DocumentSelection#event:change:range} and
553+
* {@link module:engine/model/documentselection~DocumentSelection#event:change:attribute}.
554+
* @param {Array.<module:engine/model/markercollection~Marker>} oldMarkers Markers in which the selection was before the change.
555+
*/
556+
545557
// `LiveSelection` is used internally by {@link module:engine/model/documentselection~DocumentSelection} and shouldn't be used directly.
546558
//
547559
// LiveSelection` is automatically updated upon changes in the {@link module:engine/model/document~Document document}
@@ -721,11 +733,13 @@ class LiveSelection extends Selection {
721733
setTo( selectable, optionsOrPlaceOrOffset, options ) {
722734
super.setTo( selectable, optionsOrPlaceOrOffset, options );
723735
this._updateAttributes( true );
736+
this._updateMarkers();
724737
}
725738

726739
setFocus( itemOrPosition, offset ) {
727740
super.setFocus( itemOrPosition, offset );
728741
this._updateAttributes( true );
742+
this._updateMarkers();
729743
}
730744

731745
setAttribute( key, value ) {
@@ -830,6 +844,7 @@ class LiveSelection extends Selection {
830844

831845
_updateMarkers() {
832846
const markers = [];
847+
let changed = false;
833848

834849
for ( const marker of this._model.markers ) {
835850
const markerRange = marker.getRange();
@@ -841,17 +856,27 @@ class LiveSelection extends Selection {
841856
}
842857
}
843858

859+
const oldMarkers = Array.from( this.markers );
860+
844861
for ( const marker of markers ) {
845862
if ( !this.markers.has( marker ) ) {
846863
this.markers.add( marker );
864+
865+
changed = true;
847866
}
848867
}
849868

850869
for ( const marker of Array.from( this.markers ) ) {
851870
if ( !markers.includes( marker ) ) {
852871
this.markers.remove( marker );
872+
873+
changed = true;
853874
}
854875
}
876+
877+
if ( changed ) {
878+
this.fire( 'change:marker', { oldMarkers, directChange: false } );
879+
}
855880
}
856881

857882
// Updates this selection attributes according to its ranges and the {@link module:engine/model/document~Document model document}.

tests/model/documentselection.js

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,224 @@ describe( 'DocumentSelection', () => {
442442

443443
expect( selection.markers.map( marker => marker.name ) ).to.have.members( [ 'marker' ] );
444444
} );
445+
446+
describe( 'should fire change:marker event when', () => {
447+
// Set marker to range 0-4.
448+
beforeEach( () => {
449+
model.change( writer => {
450+
writer.addMarker( 'marker-1', {
451+
range: writer.createRange(
452+
writer.createPositionFromPath( root, [ 2, 0 ] ),
453+
writer.createPositionFromPath( root, [ 2, 4 ] )
454+
),
455+
usingOperation: false
456+
} );
457+
} );
458+
} );
459+
460+
it( 'selection ranges change (marker added to the selection)', () => {
461+
const spy = sinon.spy();
462+
463+
model.change( writer => {
464+
// The selection has no markers before the change.
465+
model.document.selection.on( 'change:marker', ( evt, data ) => {
466+
expect( data.oldMarkers ).to.deep.equal( [] );
467+
spy();
468+
} );
469+
470+
// Move selection to 1-2, that is inside 0-4 marker.
471+
writer.setSelection( writer.createRange(
472+
writer.createPositionFromPath( root, [ 2, 1 ] ),
473+
writer.createPositionFromPath( root, [ 2, 2 ] )
474+
) );
475+
} );
476+
477+
expect( spy.calledOnce ).to.be.true;
478+
} );
479+
480+
it( 'selection ranges change (marker removed from the selection)', () => {
481+
const spy = sinon.spy();
482+
483+
model.change( writer => {
484+
writer.setSelection( writer.createRange(
485+
writer.createPositionFromPath( root, [ 2, 1 ] ),
486+
writer.createPositionFromPath( root, [ 2, 2 ] )
487+
) );
488+
489+
// The selection is in a marker before the change.
490+
model.document.selection.on( 'change:marker', ( evt, data ) => {
491+
expect( data.oldMarkers.map( marker => marker.name ) ).to.deep.equal( [ 'marker-1' ] );
492+
spy();
493+
} );
494+
495+
// Move the selection out of the marker.
496+
writer.setSelection( writer.createPositionFromPath( root, [ 2, 5 ] ) );
497+
} );
498+
499+
expect( spy.calledOnce ).to.be.true;
500+
} );
501+
502+
it( 'selection focus changes (marker removed from the selection)', () => {
503+
const spy = sinon.spy();
504+
505+
model.change( writer => {
506+
writer.setSelection( writer.createPositionFromPath( root, [ 2, 2 ] ) );
507+
508+
// The selection is in a marker before the change.
509+
model.document.selection.on( 'change:marker', ( evt, data ) => {
510+
expect( data.oldMarkers.map( marker => marker.name ) ).to.deep.equal( [ 'marker-1' ] );
511+
spy();
512+
} );
513+
514+
// Move the selection focus out of the marker.
515+
writer.setSelectionFocus( writer.createPositionFromPath( root, [ 2, 5 ] ) );
516+
} );
517+
518+
expect( spy.calledOnce ).to.be.true;
519+
} );
520+
521+
it( 'a new marker contains the selection', () => {
522+
const spy = sinon.spy();
523+
524+
model.change( writer => {
525+
writer.setSelection( writer.createPositionFromPath( root, [ 2, 5 ] ) );
526+
527+
// The selection is not in a marker before the change.
528+
model.document.selection.on( 'change:marker', ( evt, data ) => {
529+
expect( data.oldMarkers ).to.deep.equal( [] );
530+
spy();
531+
} );
532+
533+
writer.updateMarker( 'marker-1', {
534+
range: writer.createRange(
535+
writer.createPositionFromPath( root, [ 2, 0 ] ),
536+
writer.createPositionFromPath( root, [ 2, 6 ] )
537+
)
538+
} );
539+
} );
540+
541+
expect( spy.calledOnce ).to.be.true;
542+
} );
543+
544+
it( 'a marker stops contains the selection', () => {
545+
const spy = sinon.spy();
546+
547+
model.change( writer => {
548+
writer.setSelection( writer.createPositionFromPath( root, [ 2, 3 ] ) );
549+
550+
// The selection is in a marker before the change.
551+
model.document.selection.on( 'change:marker', ( evt, data ) => {
552+
expect( data.oldMarkers.map( marker => marker.name ) ).to.deep.equal( [ 'marker-1' ] );
553+
spy();
554+
} );
555+
556+
writer.updateMarker( 'marker-1', {
557+
range: writer.createRange(
558+
writer.createPositionFromPath( root, [ 2, 0 ] ),
559+
writer.createPositionFromPath( root, [ 2, 1 ] )
560+
)
561+
} );
562+
} );
563+
564+
expect( spy.calledOnce ).to.be.true;
565+
} );
566+
} );
567+
568+
describe( 'should not fire change:marker event when', () => {
569+
// Set marker to range 0-4.
570+
beforeEach( () => {
571+
model.change( writer => {
572+
writer.addMarker( 'marker-1', {
573+
range: writer.createRange(
574+
writer.createPositionFromPath( root, [ 2, 0 ] ),
575+
writer.createPositionFromPath( root, [ 2, 4 ] )
576+
),
577+
usingOperation: false
578+
} );
579+
} );
580+
} );
581+
582+
it( 'selection ranges change does not change selection markers (no markers)', () => {
583+
const spy = sinon.spy();
584+
585+
model.document.selection.on( 'change:marker', spy );
586+
587+
model.change( writer => {
588+
writer.setSelection( writer.createPositionFromPath( root, [ 2, 5 ] ) );
589+
} );
590+
591+
expect( spy.called ).to.be.false;
592+
} );
593+
594+
it( 'selection ranges change does not change selection markers (same markers)', () => {
595+
model.change( writer => {
596+
writer.setSelection( writer.createPositionFromPath( root, [ 2, 2 ] ) );
597+
} );
598+
599+
const spy = sinon.spy();
600+
601+
model.document.selection.on( 'change:marker', spy );
602+
603+
model.change( writer => {
604+
writer.setSelection( writer.createPositionFromPath( root, [ 2, 3 ] ) );
605+
} );
606+
607+
expect( spy.called ).to.be.false;
608+
} );
609+
610+
it( 'selection focus change does not change selection markers', () => {
611+
model.change( writer => {
612+
writer.setSelection( writer.createPositionFromPath( root, [ 2, 2 ] ) );
613+
} );
614+
615+
const spy = sinon.spy();
616+
617+
model.document.selection.on( 'change:marker', spy );
618+
619+
model.change( writer => {
620+
writer.setSelectionFocus( writer.createPositionFromPath( root, [ 2, 3 ] ) );
621+
} );
622+
623+
expect( spy.called ).to.be.false;
624+
} );
625+
626+
it( 'changed marker still contains the selection', () => {
627+
model.change( writer => {
628+
writer.setSelection( writer.createPositionFromPath( root, [ 2, 2 ] ) );
629+
} );
630+
631+
const spy = sinon.spy();
632+
633+
model.document.selection.on( 'change:marker', spy );
634+
635+
model.change( writer => {
636+
writer.updateMarker( 'marker-1', {
637+
range: writer.createRange(
638+
writer.createPositionFromPath( root, [ 2, 0 ] ),
639+
writer.createPositionFromPath( root, [ 2, 5 ] )
640+
)
641+
} );
642+
} );
643+
644+
expect( spy.called ).to.be.false;
645+
} );
646+
647+
it( 'removed marker did not contain the selection', () => {
648+
model.change( writer => {
649+
writer.setSelection( writer.createPositionFromPath( root, [ 2, 5 ] ) );
650+
} );
651+
652+
const spy = sinon.spy();
653+
654+
model.document.selection.on( 'change:marker', spy );
655+
656+
model.change( writer => {
657+
writer.removeMarker( 'marker-1' );
658+
} );
659+
660+
expect( spy.called ).to.be.false;
661+
} );
662+
} );
445663
} );
446664

447665
describe( 'destroy()', () => {

0 commit comments

Comments
 (0)