-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversion refactor #4202
Comments
Some notes from today's meeting:
|
Idea: currently |
I was thinking about this concept of conversion on Consider this example:
If you indent item This is why I proposed remove+insert approach. But I have to think whether it will resolve our problems. |
BTW. I know that you (@pjasiun) already wrote about the problem with mismatched model and view, but that was different case. Inserting and removing is doable, as long as you don't have to do a lot of magic connected with inserting itself. Lists converters do a lot of magic, unfortunately, that's why it got complicated. |
A quick summary of pros and cons of different approaches: Postponed changes Conception:
Pros:
Cons:
Remove + insert Conception:
Pros:
Cons:
Generate new view and diff Conception:
Pros:
Cons:
|
First of all, I don't think we should go with the totally new concept, because of 2 reasons. First, the current concept works pretty well for many of cases, we don't think at this point because they just work. The most complex thing is to design conversion pluggable. It needs to be possible to convert element in one plugin and its attribute in another or convert the same attribute or element by various plugins, depending on the context. It works well with the current approach. We also handle selection attributes conversion and changes done directly in the view, without conversion, which should not be removed. Also, note that the current concept is very elastic and let you convert changes in any way, not necessarily element->element. If, for instance, we will have to transform paragraphs to Second, it would be far more time-consuming than it seems to be. Note that there are whole mechanisms, like view writers or selection with attributes designed to work with the current approach. If we will bring a totally new concept, we would have to rewrite more and more. This is why I think we should focus on fixing what is the problem. And I see a few problems here. First is the fact that we execute converters before all changes in the model are applied and before post-fixer are called. I agree that it might be tricky, but I believe that we should try to fix it, we may get a better result. Second is that writing converters without the builder is far more complicated than it should be. We see here a big space for improvements. Third, is that we (I) focused on making converter pluggable so much, that forget about cases when the whole big part of the document should be converted together (for instance image with attributes and children). Another problem is the fact, that it's very hard to debug converters. We may rethink this too bringing a betters structure or some tools. I think that what we do: pluggable conversion is a very complicated task. I don't think we will bring a perfect solution. We should do our best to fix problems we know about. |
What about selection attributes and markers?
Unfortunately, it is. Typing in the bolded text is the case.
Not really. If the model will be changed when the conversion starts we will be able to see that it's list indentation what changed and fix it for the whole list at once.
I know that I'm the one who suggested it, but more and more I think that some form of consumables is needed. You need to be able to mark that you converted more than a single change, for instance, you converted also attributes, children or the whole list. However, we may rethink then it makes them simpler to use. Maybe, it's enough to cancel all event. |
I want to make it clear that I think that neither of suggested ideas stands against this concept. If some feature inserts, for example, and indented list item, or a blockquote with multiple paragraphs, etc., we are able to convert it now. So whatever we are able to do now, we will be if we decide for any of those approaches.
I don't understand. I guess you mean that rebuilding destroys all the changes that we do outside of conversion? Yup, well, this might be a problem. I'll return to this later at the end of post.
AFAICS nothing changes. At least this is my hope.
This is true. I just think that since we already have to support just inserting complex structures, we can do it for any conversion.
This is the point of this discussion and my more-than-enough opening post. There are two problems: problems are vaguely defined and easy solutions don't fix them.
AFAIR, they happen at different times (not in
Typing in the bolded text is, AFAIR, inserting text already with the attribute? Anyway, this doubt concerned when an attribute is changed on an element so that the whole element is re-converted. AFAICS, your case is unrelated.
I am not sure you understood this correctly. Anyway, I will just reply to the last part: "fix it for the whole list at once". See, this is the problem. We cannot "fix something for the whole list at once". Even if we postpone changes to All in all, the bottom line is: I have doubts whether we should change anything if we want to just postpone changes to the later. We won't achieve much. The whole point of this refactor was to make writing converters simpler, I am not sure we are achieving this with easy solutions. |
Sorry, I was supposed to return to something at the end of the previous post. So, there is a problem with changes done directly on the view outside of conversion. It's true. If we rebuild part of a tree, we might lose those changes. First, however, we should answer two questions:
My answers:
Do we have any cases for such elements right now, be it in CKE5 or Letters? EDIT: Actually, answer for point 2 is not entirely correct. In fact, the |
Okay, so I've been thinking more about this |
So I checked it and I was right. I've added |
We discussed with @pjasiun that diffing will be difficult to do efficiently. Consider this example: <root>
<paragraph> ... </paragraph>
<listItem indent=0> ... </listItem>
<paragrah> ... </paragraph>
<listItem indent=0> ... </listItem>
</root> Now, the second
This means that we would have to reconvert whole parent (root in the worst case) and then diff deeply and by properties (not reference). I am not saying that this kind of solution would have to be slow. Especially typing - usually typing occurs in leaves of the model tree. |
We talked and (more-less) agreed that @scofalik proposal was focused on lists conversion. When we were thinking about bolding text in the paragraph what we have now is:
What we would have after the change is:
It would not, for sure, make debugging simpler and this is why I'm against such change. Also, we realized that this concept will not work well for list merging. We would have to rebuild the whole root and do the deep diff, what would be terribly complicated. |
There was a proposal that we might enable a way to have it easier to deal with complex conversions like list conversions. When list's structure changes we are fine with rebuilding big chunks of it. However now, we have to do it step by step, and conversion on changes done will not be much help. It might be nice to have a mechanism to handle multiple changes at once. |
Okay, I came to the conclusion that I am fine with refactoring this in a way that changes are buffered until Unfortunately, we will have a limitation, that we should only look at previous elements in the model. To be safe, we should assume the same for the view. We can do something with lists later if we want (add some post-fixers that would trigger list rebuilding or something. IDK. But this can be a step 2). |
https://stackoverflow.com/questions/46970216/can-not-input-to-editableelement-of-ckeditor5 Some feedback on conversion. Or rather a case that a developer has, how they approached it and what are their expectations. I've added a very long answer. It was mostly an exercise for me too. This case might be valuable for us when thinking about more complex CKE uses and what conversion utilities we want to introduce. BTW. I think that "widgetizing" part of the view is a difficult concept at the moment. It seems like magic and I am not sure what to really expect. Maybe a guide could change it. |
As @scofalik suggest: "don't read whole just add your case". Case 1: Conditionally output I wanted to conditionally convert attribute. Lets say that Case 2: Maybe adding buildModelConverter()
.fromAttribute( 'foo' )
.toStyle( 'font-color', 'blue' );
buildModelConverter()
.fromAttribute( 'bar' )
.toStyle( 'background-color', 'white' ); should output for example <div style="background-color: white;font-color: blue;"></div> |
Other: Refactoring: Conversion refactoring. Introduced `model.Differ`. Changes now will be converted after all changes in a change block are done. Closes #1172.
I am opening this ticket to have a place for discussion about upcoming conversion refactor.
Goals
There are a few goals that we want to achieve with this.
The first goal is to stop converting an incorrect model state to the view. This happens now because we fire model-to-view conversion after each atomic change on the model.
There is also a problem with mapping during conversion. Sometimes we need to map non-existing model position/range to the view (a case for remove). Sometimes we want to map an element from the model to the view, but the element was already mapped to another element (a case for rename and move AFAIR). It would be perfect if those problems would vanish away.
The second goal is to make it easier to write converters. At the moment, converters are the most complicated part of developing a feature. For a new developer, it may be not clear what exactly they have to do to write a correct converter.
This is also connected with providing more / different / better / easier to use conversion helpers. Now we have more knowledge about how the converters actually look, so we know what kind of helpers we can introduce.
Additionally, markers conversion isn't pretty right now. It'd be nice to revisit it.
The bonus goal is to prepare an environment where we can easily hook callbacks / fixers that should react to changes in the model.
Keep in mind, that developers have their own experiences and are accustomed to some patterns. If the solution is easier for the user, it is probably better.
Think if there is anything that can be improved in view-to-model conversion. Since this topic all began from talking about "converting model changes to view on
changesDone
" it was natural, that we mostly focused on model-to-view conversion. In my opinion, view-to-model conversion is fine, but I might be missing something.Related tickets
https://github.com/ckeditor/ckeditor5-engine/issues/829 - Simplify model to view conversion
https://github.com/ckeditor/ckeditor5-engine/issues/736 - Add more conversion helpers
https://github.com/ckeditor/ckeditor5-engine/issues/778 - Change-based conversion can be really confusing
https://github.com/ckeditor/ckeditor5-engine/issues/779 - It shouldn't be needed to listen on 3 events to convert an attribute
https://github.com/ckeditor/ckeditor5-engine/issues/945 - Consider simplifying attribute conversion
Thoughts and ideas
Thinking out of the box
I'd like to make it clear, that we should try to forget about how conversion looks now. Of course, we have experience, we know what worked and what did not work. However, maybe we could come up with a totally different idea for conversion mechanism.
The only assumptions to make are:
Limit amount of change types
This already has been tweaked a bit earlier. Anyway, This is a minimal set of changes that we need to support:
Insert and remove changes handle, of course, removing and inserting elements, but also moving or renaming them.
Attribute changes are a bit more complicated. I am not sure what we need here. Unfortunately "add attribute" + "remove attribute" does not seem to be the correct way to change an attribute. Mostly because some elements require some attributes so removing attribute is forbidden. Take
listItem
for example. Changing it'stype
attribute should be converted "in one step", especially because it doesn't make sense to have alistItem
withouttype
attribute.Maybe the solution is to have just one "attribute change" change type, and the converter should handle it all: inserting, removing and changing the attribute. The question is what we gain here - other than having to attach only one callback (instead of three, like now).
Another idea is to remove the element which attributes got changed and then insert it with the changed attribute. I am not sure how this will impact view tree and then DOM rendering. I am not sure how inefficient such solution would be. Thankfully, the biggest efficiency bottleneck is typing and attribute conversion is not connected with it.
Conversion on
changesDone
This is the original idea from which it all started. It is simple - do not convert model changes right away. Instead, buffer them. When all changes are applied, all callbacks and fixers did their changes, convert the model to the view. This should happen on
changesDone
event. At this moment, all fixers should already have fixed the model.What do we gain from this solution? Mostly, the converters will be easier to write because they won't need to convert incorrect model states.
How to enable conversion on
changesDone
? There are a couple of ideas here:Text
instances. Text changes might need to be kept asLivePosition
s, especially when whole text node was removed. OnchangesDone
iterate through this data structure and apply changes to the view one by one. This is the simplest and original proposal. I'd treat this as a "fallback solution". It does not change a lot in the conversion itself, it just postpones the whole conversion to a later stage. It solves some of our problems. If we won't come up with something better, this should be okay.Do not forget about markers! Markers are also a part of the model and need to be converted on
changesDone
. We would have to store which markers have changed, and how. The exact implementation will probably depend on the chosen solution for model tree conversion.Those ideas do not touch
change
callbacks, though. Those callbacks / fixers, similarly to converters, are now a bit too complicated. At the end of this post I've included a list of all callbacks listening tochange
event.Decouple building and inserting elements
We came from an idea that we are converting changes, not model state. This is why we'd rather say "convert image insertion" rather than "convert image element". On the other hand, it is much easier to think that "
listItem
model element should be represented as<li></li>
in view". People are familiar with this kind of thinking. We should take it into consideration.Hence, I propose to think about decouple inserting elements and building elements. Inserting is same anyway for most features. For now, only lists have their own needs for inserting -- when you insert an element between
listItem
s, in view you need to break<ul>
/<ol>
element. Similarly, when you remove an element, it may happen that two lists need to be merged.The reason for this proposal is that maybe if we think about "building" elements, it will be easier to refactor conversion mechanisms and propose new conversion utilities.
This idea is strongly connected with rebuilding items a lot of time (for example, rebuild the whole item when its attribute changed). We need to think how elements change and what impact does rebuilding have on creating a view structure and then DOM.
Firing event before the change is applied to the model
This is a wild idea and I don't know what it would achieve expect of giving us more possibilities but I just wanted to throw it here and see if maybe you will find it useful.
The idea is simple: instead of applying a change to the model right away, fire an event. Listeners would be able to modify or cancel the change.
Do we need consumables?
This was brought up during talks and is not originally my idea. The question was whether we need consumables and how do we use them at the moment. The doubt was that consumables have the same role as event canceling. The bottom line was that we maybe need them in view-to-model conversion but probably are able to remove them from model-to-view conversion.
Of course, if we will go with "building" items, the model-to-view conversion will get a bit similar to the view-to-model conversion. Then we might need consumables to build elements from the model in a right way.
Use cases for
change
event other thanModelConversionDispatcher
CKE5 uses
change
event for following callbacks / fixing:change
event),image
element is detected and if theimage
does not havecaption
element, the element is added),type
andindent
attributes are detected and fixed),paragraph
element is inserted),change
event (typing),Additionally, these are Letters use cases:
I think that in overall, there are two types of callbacks:
What helpers do we need?
This is to bootstrap a work that has to be done with conversion helpers. There was an idea to drop converter builders and instead provide a set of utility functions that will be simpler to use but will produce more focused converters.
We need to study how do we use converter builders. Which cases need to be ported to utility functions. Then, we need to see what was impossible to do using builders.
A short list of helpers that we need. Was not researched much yet:
<figure>
element in image feature)Tasks
A list of tasks that we need to do no matter how we proceed with this refactoring.
view.writer
. For example, it hasrename
method and I am not sure if it is used at all.UIElement
s are created and added to the view tree. Try to come up with an API to manageUIElement
s better.The text was updated successfully, but these errors were encountered: