Skip to content
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

Block filler recognition is incorrect when converting from DOM to view #9345

Closed
scofalik opened this issue Mar 23, 2021 · 2 comments · Fixed by #9357
Closed

Block filler recognition is incorrect when converting from DOM to view #9345

scofalik opened this issue Mar 23, 2021 · 2 comments · Fixed by #9357
Assignees
Labels
package:engine squad:collaboration Issue to be handled by the Collaboration team. type:bug This issue reports a buggy (incorrect) behavior.

Comments

@scofalik
Copy link
Contributor

scofalik commented Mar 23, 2021

📝 Provide detailed reproduction steps (if any)

Filler element &nbsp; is not correctly removed when it was placed after <br /> and the data is loaded.

  1. editor.setData( '<p>A<br></p>' ) or prepare such content in the editor.
  2. editor.getData() -> '<p>A<br>&nbsp;</p>' -> this is fine, filler is needed.
  3. editor.setData( '<p>A<br>&nbsp;</p>' )

✔️  Expected result

There is no space in the editor (in the model).

❌  Actual result

The filler &nbsp; has not been removed and has been added as a space to the model.

What is wrong

Block filler element is not recognised properly. We have a mechanism that should remove this &nbsp; when it was converted from DOM to view but the mechanism didn't work.

We should start from looking at the method that adds the filler. For a container element it is:

export function getFillerOffset() {
	const children = [ ...this.getChildren() ];
	const lastChild = children[ this.childCount - 1 ];

	// Block filler is required after a `<br>` if it's the last element in its container. See #1422.
	if ( lastChild && lastChild.is( 'element', 'br' ) ) {
		return this.childCount;
	}

	for ( const child of children ) {
		// If there's any non-UI element – don't render the bogus.
		if ( !child.is( 'uiElement' ) ) {
			return null;
		}
	}

	// If there are only UI elements – render the bogus at the end of the element.
	return this.childCount;
}

As you can see, there is some logic here, but tl;dr - the filler is added at the end of the element if there are no or only UI elements in the container or if there is <br> at the end of the container.

Actually, different types of elements have different rules for that. And AFAIK, it is possible to set a custom behavior.

The logic presented above is not reflected in the function that tries to recognise and remove fillers. It looks like this:

function isNbspBlockFiller( domNode, blockElements ) {
	const isNBSP = isText( domNode ) && domNode.data == '\u00A0';

	return isNBSP && hasBlockParent( domNode, blockElements ) && domNode.parentNode.childNodes.length === 1;
}

As you can see, the logic here is simpler, it just checks if this is an &nbsp; in an empty element. It is not compatible with getFillerOffset() for container elements and it would get outdated again if getFillerOffset() changes.

Solutions

Quick solution - simply fix isNbspBlockFiller() to be more compatible with current implementation of getFillerOffset(). Since UI elements are most of the time (always?) not rendered in the data pipeline, we could just expand the check in isNbspBlockFiller() to also include a scenario when &nbsp; is after <br>. This should cover most of the cases and is very small effort.

Unfortunately, marker elements are UI elements, so &nbsp;s are inserted into elements that have only a marker (suggestions).

Good solution - change the flow of block fillers removing. Instead of skipping them when traversing converted DOM tree, convert them to view. Then use getFillerOffset() of the view elements and remove fillers from the view layer. This requires more effort and may result in some bugs.

And in this case we cannot do that because during upcast we don't have "semantic" view elements (attribute element, container element, etc.) so we cannot check getFillerOffset().

@scofalik
Copy link
Contributor Author

It seems that the only good solution will be to mark block filler spans by wrapping them in additional spans.

@scofalik
Copy link
Contributor Author

The final decision is to add additional wrapping spans only if a certain flag is turned on.

niegowski added a commit that referenced this issue Apr 14, 2021
Feature (engine): Introduced new block filler mode "markedNbsp" in `DomConverter`, in which `<span data-cke-filler="true">&nbsp;</span>` are inserted, to prevent leaking extra space characters into the data.

Feature (engine): Introduced `useFillerType()` in `HtmlDataProcessor` and `XmlDataProcessor` to switch between using marked and regular nbsp block fillers. Closes #9345.

MINOR BREAKING CHANGE (engine): Added new method `useFillerType()` in `DataProcessor` interface. Classes that base on this interface should implement `useFillerType()` to avoid errors.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
package:engine squad:collaboration Issue to be handled by the Collaboration team. type:bug This issue reports a buggy (incorrect) behavior.
Projects
None yet
1 participant