-
Notifications
You must be signed in to change notification settings - Fork 40
Conversation
src/controller/datacontroller.js
Outdated
@@ -180,7 +183,10 @@ export default class DataController { | |||
|
|||
/** | |||
* Sets input data parsed by the {@link #processor data processor} and | |||
* converted by the {@link #viewToModel view to model converters}. | |||
* converted by the {@link #viewToModel view to model converters}. When markers where converted |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If
src/controller/datacontroller.js
Outdated
@@ -180,7 +183,10 @@ export default class DataController { | |||
|
|||
/** | |||
* Sets input data parsed by the {@link #processor data processor} and | |||
* converted by the {@link #viewToModel view to model converters}. | |||
* converted by the {@link #viewToModel view to model converters}. When markers where converted | |||
* from view to model as temporary {@link module:engine/model/element/~Element model elements} then those element |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
elements
src/controller/datacontroller.js
Outdated
markerData.endPath ? new ModelPosition( modelRoot, markerData.endPath ) : null | ||
); | ||
|
||
batch.setMarker( this.model.markers.set( markerName, range ) ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAIR you can directly use setMarker
- no need to add that marker to MarkerCollection
first. MarkerOperation
, upon execution, will do it.
src/controller/datacontroller.js
Outdated
@@ -306,6 +335,57 @@ export default class DataController { | |||
|
|||
mix( DataController, EmitterMixin ); | |||
|
|||
// Traverses given DocumentFragment and searches elements which marks marker range. Founded element is removed from |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Found
src/controller/datacontroller.js
Outdated
@@ -306,6 +335,57 @@ export default class DataController { | |||
|
|||
mix( DataController, EmitterMixin ); | |||
|
|||
// Traverses given DocumentFragment and searches elements which marks marker range. Founded element is removed from | |||
// DocumentFragment but path of this element is stored in Map. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in a Map
which is then returned.
src/controller/datacontroller.js
Outdated
// | ||
// @param {module:engine/view/documentfragment~DocumentFragment} documentFragment Model DocumentFragment. | ||
// @returns {Object} Object with markers data and cleaned up document fragment. | ||
function extractMarkersFromModelFragment( documentFragment ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To simplify this function and avoid refreshing walker, you could simply first find all elements to be extracted, and then remove them one by one after tree walker already did it's job:
const markerStamps = new Set();
// ...
for ( const value of walker ) {
// ...
markerStamps.add( value.item )
// ...
}
for ( const stamp of markerStamps ) {
// ...
}
AFAIR, Set
remembers the order in which items where stored.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, since documentFragment
is passed by reference, you only need to return the markers. It will be more elegant to use.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Finally, this could be a private method too, but I also have this "problem" of using sometimes private "static-like" methods and sometimes not exported functions, so as you wish :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, since documentFragment is passed by reference, you only need to return the markers. It will be more elegant to use.
That's how it looked in my first implementation but I assumed that it'll be more readable to return data.
viewWriter.insert( mapper.toViewPosition( data.range.start ), viewElement ); | ||
|
||
if ( !data.range.isCollapsed ) { | ||
viewWriter.insert( mapper.toViewPosition( data.range.end ), viewElement.clone( true ) ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We've been discussing that it would be nice if closing element is different than opening element, so this information would have to be send to creator and two view elements would have to be created.
If that is not too complicated, it would be nice to have this, but it is not a strict requirement. Take both model->view and view->model conversion into consideration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed proposed commit message from NOTE: BuildModelConverter#fromMarker and BuildModelConverter#fromCollapsedMarker are unified, insertUIElement method of model -> view converter is enhanced to supports collapsed and non-collapsed ranges. to BREAKING CHANGES: |
I added missing empty line between both blocks :D |
src/controller/datacontroller.js
Outdated
} | ||
|
||
// Walk through collected marker elements store its path and remove its from the DocumentFragment. | ||
return Array.from( markerStamps ).reduce( ( result, stamp ) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Iterators FTW!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For var i = 0; i < length; ++i FTW!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was really curious what will you say guys about this reduce
:)
src/controller/datacontroller.js
Outdated
} | ||
|
||
// Walk through collected marker elements store its path and remove its from the DocumentFragment. | ||
return Array.from( markerStamps ).reduce( ( result, stamp ) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Loops FTW!
On serious note, this looks overcomplicated. Simple loop over markerStamps
would be okay too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Anyway, the whole function should be moved from the DataController
to conversion view dispatcher or view converter helper.
src/controller/datacontroller.js
Outdated
* converted by the {@link #viewToModel view to model converters}. If markers where converted | ||
* from view to model as temporary {@link module:engine/model/element/~Element model elements} then those elements | ||
* will be removed from parsed {@link module:engine/model/element/~DocumentFragment} and added to the | ||
* {@link module:engine/model/document~Document#markers markers collection}. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should not be here. All what DataController
is, is a facade for the complex API. I should not have any complex logic because quickly it became uber-class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Especially since now parse
does not return any useful data, and the whole processing is done outside this function.
src/controller/datacontroller.js
Outdated
* @fires insertContent | ||
* @param {module:engine/model/documentfragment~DocumentFragment} content The content to insert. | ||
* @param {module:engine/model/selection~Selection} selection Selection into which the content should be inserted. | ||
* @param {module:engine/model/batch~Batch} [batch] Batch to which deltas will be added. If not specified, then | ||
* changes will be added to a new batch. | ||
*/ | ||
insertContent( content, selection, batch ) { | ||
extractMarkersFromModelFragment( content ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to extract anything on insert content?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is it here? Any clean up should be done beforehand, using the clipboard events (#paste
or #clipboardInput
).
This branch has conflicts! Git FTW! |
src/controller/datacontroller.js
Outdated
@@ -198,10 +198,28 @@ export default class DataController { | |||
this.model.selection.removeAllRanges(); | |||
this.model.selection.clearAttributes(); | |||
|
|||
// Parse data to model and extract markers from parsed document fragment. | |||
const { modelItem, markersData } = this.parse( data ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking a while about the return format. I don't like the code bellow needed to translate markersData
to document.makers
. It's not short nor simple, and you will have to write it every time you need a custom handling of parsed data. Of course, we could have a helper function, but where-ever we put such function it will be ugly. Then I thought that maybe markers should be returned as MarkersCollection
.
At the same time, it's strange that you can not return one object representing a parsed fragment of the document, something like... DocumentFragment
?
Markers are integrated part of the document data. They are not like selection or history, they are more like attributes or children, so maybe we should have DocumentFragment.makers
? Then, when you add document fragment to another document, makers from it should be taken and added to the parent document, the same way like children are moved to the parent element. It could be done by the model.writer
or element.insert
(I hope soon we will get rid off model.writer
anyway).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After this change, the code here will not change much.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know I am usually the one who is against magic, but the possibility of having all data in one object is worth trying.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this idea. Converter still will be returning Object
with conversion result and markers data, but parser will merge it together into DocumentFragment
with its own MarkersCollection
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Creating the real MarkersCollection
on DocumentFragment
might be more compilated because Marker
keeps LiveRange
inside but LiveRange
work only for Document
. We can:
- try to improve
DocumentFragment
andLiveRange
to work each other but it might be overengineering. - don't create the real
MarkersCollection
onDocumentFragment
but only something to store markers data. I don't like it because every change on DocFrag will make markers positions out of date.
src/conversion/buildviewconverter.js
Outdated
* @param {Function} [creator] Creator function. | ||
*/ | ||
toMarker( creator ) { | ||
const eventCallbackGen = function( from ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not function eventCallbackGen( from )
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm also for named function instead of anonymous function assigned to const, but this is consistent with the other methods. I'll fix it.
const walker = new ModelTreeWalker( { | ||
startPosition: ModelPosition.createAt( modelItem, 0 ), | ||
ignoreElementEnd: true, | ||
shallow: false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAIR shallow is false by default.
src/controller/datacontroller.js
Outdated
@@ -236,7 +237,19 @@ export default class DataController { | |||
* @returns {module:engine/model/documentfragment~DocumentFragment} Output document fragment. | |||
*/ | |||
toModel( viewElementOrFragment, context = '$root' ) { | |||
return this.viewToModel.convert( viewElementOrFragment, { context: [ context ] } ); | |||
const { conversionResult, markers } = this.viewToModel.convert( viewElementOrFragment, { context: [ context ] } ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think convert
should simply return documentFragmet
with markers which can be added instead of transforming it here. DataController
should not contain any transformation logic, it should be as simple as possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
* buildModelConverter().for( dispatcher ) | ||
* .fromMarker( 'search' ) | ||
* .toStamp( ( data, isOpening ) => { | ||
* if ( isOpening ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isOpening
should be a part of the data (data.isOpening
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
*/ | ||
toStamp( element ) { | ||
for ( let dispatcher of this._dispatchers ) { | ||
if ( this._from.type == 'element' || this._from.type == 'attribute' ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not if ( this._from.type != 'marker' )
? If we will introduce a new type it most probably should not be transformed to stamp as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
} else { | ||
log.warn( 'model-writer-insert-lose-markers: Element containing markers is set to element without MarkersCollection.' ); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will it work fine if I insert DocumentFragment
with markers to another DocumentFragment
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did a small fix and wrote test for it and it works.
// When conversion output is not a Node or Element we just return it. | ||
if ( !( conversionResult instanceof ModelNode || conversionResult instanceof ModelDocumentFragment ) ) { | ||
return conversionResult; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't get this condition. Is it for text nodes? I think that there is nothing wrong if we will wrap everything in the document fragment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea, I know. It was even my idea, but it was a very theoretical idea. You can remove these tests because at this point it's pretty clear that we will not use dispatcher this way, soon.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
||
return this._convertItem( viewItem, consumable, additionalData ); | ||
// When conversion output is not a Node or Element we just return it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment is useless. It tells exactly what the next line do. I can read code instead. What comment should do is tell me why this code is needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
* {@link module:engine/view/documentfragment~DocumentFragment view document fragment} converted by the | ||
* {@link #viewToModel view to model converters} to a | ||
* {@link module:engine/model/documentfragment~DocumentFragment model document fragment}. | ||
* {@link #viewToModel view to model converters}. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could add a note to toModel
and viewToModel.convert
that the document fragment may contain markers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/model/documentfragment.js
Outdated
@@ -25,6 +29,15 @@ export default class DocumentFragment { | |||
*/ | |||
constructor( children ) { | |||
/** | |||
* DocumentFragment static markers list. This is a list of names and {@link module:engine/model/range~Range ranges} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's map, not list.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
Suggested merge commit message (convention)
Feature: Introduced markers serialization. Closes ckeditor/ckeditor5#3961. Closes ckeditor/ckeditor5#4003.
BREAKING CHANGES:
BuildModelConverter#fromMarkerCollapsed
is removed. UseBuildModelConverter#fromMarker
instead. Format of data returned byViewConversionDispatcher#convert
has changed from single model item to object with model item and list of static markers.NOTE:
insertUIElement
model to view converter now supports collapsed and non-collapsed ranges.Related to: ckeditor/ckeditor5-clipboard#15
Follow-ups: