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

Commit

Permalink
Merge branch 'master' into t/ckeditor5-table/12
Browse files Browse the repository at this point in the history
  • Loading branch information
Reinmar committed Jun 15, 2018
2 parents e7ac943 + 3800456 commit 648cd37
Show file tree
Hide file tree
Showing 17 changed files with 545 additions and 139 deletions.
35 changes: 30 additions & 5 deletions src/model/differ.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export default class Differ {
}

/**
* Buffers a given operation. An operation has to be buffered before it is executed.
* Buffers the given operation. An operation has to be buffered before it is executed.
*
* Operation type is checked and it is checked which nodes it will affect. These nodes are then stored in `Differ`
* in the state before the operation is executed.
Expand Down Expand Up @@ -171,7 +171,7 @@ export default class Differ {
for ( const marker of this._markerCollection.getMarkersIntersectingRange( range ) ) {
const markerRange = marker.getRange();

this.bufferMarkerChange( marker.name, markerRange, markerRange );
this.bufferMarkerChange( marker.name, markerRange, markerRange, marker.affectsData );
}

break;
Expand All @@ -183,23 +183,26 @@ export default class Differ {
}

/**
* Buffers marker change.
* Buffers a marker change.
*
* @param {String} markerName The name of the marker that changed.
* @param {module:engine/model/range~Range|null} oldRange Marker range before the change or `null` if the marker has just
* been created.
* @param {module:engine/model/range~Range|null} newRange Marker range after the change or `null` if the marker was removed.
* @param {Boolean} affectsData Flag indicating whether marker affects the editor data.
*/
bufferMarkerChange( markerName, oldRange, newRange ) {
bufferMarkerChange( markerName, oldRange, newRange, affectsData ) {
const buffered = this._changedMarkers.get( markerName );

if ( !buffered ) {
this._changedMarkers.set( markerName, {
oldRange,
newRange
newRange,
affectsData
} );
} else {
buffered.newRange = newRange;
buffered.affectsData = affectsData;

if ( buffered.oldRange == null && buffered.newRange == null ) {
// The marker is going to be removed (`newRange == null`) but it did not exist before the first buffered change
Expand Down Expand Up @@ -243,6 +246,28 @@ export default class Differ {
return result;
}

/**
* Checks whether some of the buffered changes affect the editor data.
*
* Types of changes which affect the editor data:
*
* * model structure changes,
* * attribute changes,
* * changes of markers which were defined as `affectingData`.
*
* @returns {Boolean}
*/
hasDataChanges() {
for ( const [ , change ] of this._changedMarkers ) {
if ( change.affectsData ) {
return true;
}
}

// If markers do not affect the data, check whether there are some changes in elements.
return this._changesInElement.size > 0;
}

/**
* Calculates the diff between the old model tree state (the state before the first buffered operations since the last {@link #reset}
* call) and the new model tree state (actual one). It should be called after all buffered operations are executed.
Expand Down
62 changes: 48 additions & 14 deletions src/model/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,18 @@ import { isInsideSurrogatePair, isInsideCombinedSymbol } from '@ckeditor/ckedito
const graveyardName = '$graveyard';

/**
* Document tree model describes all editable data in the editor. It may contain multiple
* {@link module:engine/model/document~Document#roots root elements}. For example, if the editor has multiple editable areas,
* each area will be represented by a separate root.
* Data model's document. It contains the model's structure, its selection and the history of changes.
*
* Read more about working with the model in
* {@glink framework/guides/architecture/editing-engine#model introduction to the the editing engine's architecture}.
*
* Usually, the document contains just one {@link module:engine/model/document~Document#roots root element}, so
* you can retrieve it by just calling {@link module:engine/model/document~Document#getRoot} without specifying its name:
*
* model.document.getRoot(); // -> returns the main root
*
* However, the document may contain multiple roots – e.g. when the editor has multiple editable areas
* (e.g. a title and a body of a message).
*
* @mixes module:utils/emittermixin~EmitterMixin
*/
Expand All @@ -46,6 +55,7 @@ export default class Document {
/**
* The document version. It starts from `0` and every operation increases the version number. It is used to ensure that
* operations are applied on a proper document version.
*
* If the {@link module:engine/model/operation/operation~Operation#baseVersion base version} does not match the document version,
* a {@link module:utils/ckeditorerror~CKEditorError model-document-applyOperation-wrong-version} error is thrown.
*
Expand All @@ -65,7 +75,7 @@ export default class Document {
this.history = new History( this );

/**
* The selection done on this document.
* The selection in this document.
*
* @readonly
* @member {module:engine/model/documentselection~DocumentSelection}
Expand Down Expand Up @@ -147,11 +157,16 @@ export default class Document {
// Wait for `_change` event from model, which signalizes that outermost change block has finished.
// When this happens, check if there were any changes done on document, and if so, call post fixers,
// fire `change` event for features and conversion and then reset the differ.
// Fire `change:data` event when at least one operation or buffered marker changes the data.
this.listenTo( model, '_change', ( evt, writer ) => {
if ( !this.differ.isEmpty || hasSelectionChanged ) {
this._callPostFixers( writer );

this.fire( 'change', writer.batch );
if ( this.differ.hasDataChanges() ) {
this.fire( 'change:data', writer.batch );
} else {
this.fire( 'change', writer.batch );
}

this.differ.reset();
hasSelectionChanged = false;
Expand All @@ -163,12 +178,12 @@ export default class Document {
// are modified using `model.markers` collection, not through `MarkerOperation`).
this.listenTo( model.markers, 'update', ( evt, marker, oldRange, newRange ) => {
// Whenever marker is updated, buffer that change.
this.differ.bufferMarkerChange( marker.name, oldRange, newRange );
this.differ.bufferMarkerChange( marker.name, oldRange, newRange, marker.affectsData );

if ( oldRange === null ) {
// If this is a new marker, add a listener that will buffer change whenever marker changes.
marker.on( 'change', ( evt, oldRange ) => {
this.differ.bufferMarkerChange( marker.name, oldRange, marker.getRange() );
this.differ.bufferMarkerChange( marker.name, oldRange, marker.getRange(), marker.affectsData );
} );
}
} );
Expand Down Expand Up @@ -376,21 +391,40 @@ export default class Document {
* If you want to be notified about all these changes, then simply listen to this event like this:
*
* model.document.on( 'change', () => {
* console.log( 'The Document has changed!' );
* console.log( 'The document has changed!' );
* } );
*
* If, however, you only want to be notified about structure changes, then check whether the
* {@link module:engine/model/differ~Differ differ} contains any changes:
* If, however, you only want to be notified about the data changes, then use the
* {@link module:engine/model/document~Document#event:change:data change:data} event,
* which is fired for document structure changes and marker changes (which affects the data).
*
* model.document.on( 'change', () => {
* if ( model.document.differ.getChanges().length > 0 ) {
* console.log( 'The Document has changed!' );
* }
* model.document.on( 'change:data', () => {
* console.log( 'The data has changed!' );
* } );
*
* @event change
* @param {module:engine/model/batch~Batch} batch The batch that was used in the executed changes block.
*/

/**
* It is a narrower version of the {@link #event:change} event. It is fired for changes which
* affect the editor data. This is:
*
* * document structure changes,
* * marker changes (which affects the data).
*
* If you want to be notified about the data changes, then listen to this event:
*
* model.document.on( 'change:data', () => {
* console.log( 'The data has changed!' );
* } );
*
* If you would like to listen to all document changes, then check out the
* {@link module:engine/model/document~Document#event:change change} event.
*
* @event change:data
* @param {module:engine/model/batch~Batch} batch The batch that was used in the executed changes block.
*/
}

mix( Document, EmitterMixin );
Expand Down
51 changes: 41 additions & 10 deletions src/model/markercollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,11 @@ export default class MarkerCollection {
* @param {String|module:engine/model/markercollection~Marker} markerOrName Name of marker to set or marker instance to update.
* @param {module:engine/model/range~Range} range Marker range.
* @param {Boolean} [managedUsingOperations=false] Specifies whether the marker is managed using operations.
* @param {Boolean} [affectsData=false] Specifies whether the marker affects the data produced by the data pipeline
* (is persisted in the editor's data).
* @returns {module:engine/model/markercollection~Marker} `Marker` instance which was added or updated.
*/
_set( markerOrName, range, managedUsingOperations = false ) {
_set( markerOrName, range, managedUsingOperations = false, affectsData = false ) {
const markerName = markerOrName instanceof Marker ? markerOrName.name : markerOrName;
const oldMarker = this._markers.get( markerName );

Expand All @@ -108,6 +110,11 @@ export default class MarkerCollection {
hasChanged = true;
}

if ( typeof affectsData === 'boolean' && affectsData != oldMarker.affectsData ) {
oldMarker._affectsData = affectsData;
hasChanged = true;
}

if ( hasChanged ) {
this.fire( 'update:' + markerName, oldMarker, oldRange, range );
}
Expand All @@ -116,7 +123,7 @@ export default class MarkerCollection {
}

const liveRange = LiveRange.createFromRange( range );
const marker = new Marker( markerName, liveRange, managedUsingOperations );
const marker = new Marker( markerName, liveRange, managedUsingOperations, affectsData );

this._markers.set( markerName, marker );
this.fire( 'update:' + markerName, marker, null, range );
Expand Down Expand Up @@ -313,8 +320,10 @@ class Marker {
* @param {String} name Marker name.
* @param {module:engine/model/liverange~LiveRange} liveRange Range marked by the marker.
* @param {Boolean} managedUsingOperations Specifies whether the marker is managed using operations.
* @param {Boolean} affectsData Specifies whether the marker affects the data produced by the data pipeline
* (is persisted in the editor's data).
*/
constructor( name, liveRange, managedUsingOperations ) {
constructor( name, liveRange, managedUsingOperations, affectsData ) {
/**
* Marker's name.
*
Expand All @@ -324,24 +333,33 @@ class Marker {
this.name = name;

/**
* Flag indicates if the marker is managed using operations or not.
* Range marked by the marker.
*
* @protected
* @member {module:engine/model/liverange~LiveRange}
*/
this._liveRange = this._attachLiveRange( liveRange );

/**
* Flag indicates if the marker is managed using operations or not.
*
* @private
* @member {Boolean}
*/
this._managedUsingOperations = managedUsingOperations;

/**
* Range marked by the marker.
* Specifies whether the marker affects the data produced by the data pipeline
* (is persisted in the editor's data).
*
* @private
* @member {module:engine/model/liverange~LiveRange} #_liveRange
* @member {Boolean}
*/
this._liveRange = this._attachLiveRange( liveRange );
this._affectsData = affectsData;
}

/**
* Returns value of flag indicates if the marker is managed using operations or not.
* A value indicating if the marker is managed using operations.
* See {@link ~Marker marker class description} to learn more about marker types.
* See {@link module:engine/model/writer~Writer#addMarker}.
*
Expand All @@ -355,6 +373,19 @@ class Marker {
return this._managedUsingOperations;
}

/**
* A value indicating if the marker changes the data.
*
* @returns {Boolean}
*/
get affectsData() {
if ( !this._liveRange ) {
throw new CKEditorError( 'marker-destroyed: Cannot use a destroyed marker instance.' );
}

return this._affectsData;
}

/**
* Returns current marker start position.
*
Expand Down Expand Up @@ -382,7 +413,7 @@ class Marker {
}

/**
* Returns a range that represents current state of marker.
* Returns a range that represents the current state of the marker.
*
* Keep in mind that returned value is a {@link module:engine/model/range~Range Range}, not a
* {@link module:engine/model/liverange~LiveRange LiveRange}. This means that it is up-to-date and relevant only
Expand All @@ -402,7 +433,7 @@ class Marker {
}

/**
* Binds new live range to marker and detach the old one if is attached.
* Binds new live range to the marker and detach the old one if is attached.
*
* @protected
* @param {module:engine/model/liverange~LiveRange} liveRange Live range to attach
Expand Down
9 changes: 7 additions & 2 deletions src/model/nodelist.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,14 @@ export default class NodeList {
/**
* Given offset cannot be found in the node list.
*
* @error nodelist-offset-out-of-bounds
* @error model-nodelist-offset-out-of-bounds
* @param {Number} offset
* @param {module:engine/model/nodelist~NodeList} nodeList Stringified node list.
*/
throw new CKEditorError( 'model-nodelist-offset-out-of-bounds: Given offset cannot be found in the node list.' );
throw new CKEditorError( 'model-nodelist-offset-out-of-bounds: Given offset cannot be found in the node list.', {
offset,
nodeList: this
} );
}

return this.length;
Expand Down
Loading

0 comments on commit 648cd37

Please sign in to comment.