I’ve been heavily using marks with attributes and found the automatic joining does not always produce what you might expect. Specifically, I’ve found an edge-case where a simple replace will set all marks’ attributes inside a paragraph to their default values.
It’s pretty weird. Here’s a reproduction React App
Also when you delete regular text next to a mark and then type text again, it will not receive marks from the left side. Moving the cursor next to the mark and then typing, will on the other hand receive the marks. Not a dramatic effect but thought it’s interesting.
I recall reading somewhere that marks should only be joined if all their attributes equal each other? Was it written in the docs? And it did do deep equality checks for objects as well?
I tried to find what function handles the default keypresses and creates the ReplaceStep, can you point me in the right direction so I can write it down manually? Removing exampleSetup doesn’t change the behaviour at least. I suppose I could also add prosemirror-view or state as a submodule and add a debugger statement somewhere to be able to intercept the transaction as it’s being created.
What happens on normal text input is that the browser handles it natively, and then the code in prosemirror-view/src/domchange.js diffs the new DOM with the current document and synthesizes a transaction from it, probably going through replaceSelection.
Okay I updated the example to include console.debug statements to show the created MutationRecords. It’s a bit difficult to understand what causes this misbehaviour to occur but it seems the findDiff receives rather large fragments to diff. Which then joins the adjacent marks of same type without taking into account their different attributes.
What specifically do you want to see to verify this problem? I’m not really familiar with how these MutationRecords are handled but from the looks of it, the from & to of the change is way too large for the change that occurred.
Hey @marijn I finally came around making a simpler reproduction. I might have encountered a similar issue in different context that may or may not be related. EDIT: Oh yeah you asked for a stand-alone script. Well I can provide this as well if needed.
Aaa… I see. But I’m a bit thrown off how parseDOM is triggered all of the sudden. I mean shouldn’t the EditorState be the source of the truth? So even though I’m not persisting the attributes to the DOM it shouldn’t matter as they are still kept in the state?
Chrome appears to be doing some superfluous DOM insertions and removals on such a change. The issue doesn’t occur in Firefox. When the DOM is messed with, ProseMirror will re-parse it to figure out what the updated document looks like.
Oh yeah, now I see what you mean. Depending on have you selected/focused on the editor and what character you press (sometimes ‘m’ works, sometimes say ‘f’) it sometimes keeps the attributes, sometimes not in Firefox. But it still does fail similar to Chrome and Safari.
What a nightmare. But is there any way of detecting when these things happen?
It’s a relatively large object of data that’s not needed in DOM but sure, I suppose I should do that. It’s just this is quite unexpected behavior. Should I also add it to nodes as well? If attributes can disappear unless you explicitly persist them to DOM that should read in big letters somewhere.