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

Fix rich text value for nested lists #10799

Merged
merged 21 commits into from
Oct 30, 2018
Merged

Fix rich text value for nested lists #10799

merged 21 commits into from
Oct 30, 2018

Conversation

ellatrix
Copy link
Member

@ellatrix ellatrix commented Oct 19, 2018

Description

  • Fixes List block: list breaks when a new subitem is added using the keyboard. #10701. Adjusts the rich text value for lists to have separators for nested lists items. This is needed to be able to split nested list items.
  • Improves the handling of multiline values in general on split and merge by manipulating the value ourselves rather than relying on browser/TinyMCE behaviour.
  • Fixes transforms to other blocks when a list has nested list items.

A big part of the added lines are unit and e2e tests.

How has this been tested?

See #10701.

Screenshots

Types of changes

Bug fix.

Checklist:

  • My code is tested.
  • My code follows the WordPress code style.
  • My code follows the accessibility standards.
  • My code has proper inline documentation.

@ellatrix ellatrix added the [Feature] Rich Text Related to the Rich Text component that allows developers to render a contenteditable label Oct 19, 2018
@ellatrix ellatrix added this to the 4.1 - UI freeze milestone Oct 19, 2018
@ellatrix ellatrix added [Type] Bug An existing feature does not function as intended [Priority] High Used to indicate top priority items that need quick attention labels Oct 19, 2018
@ellatrix ellatrix requested a review from a team October 19, 2018 14:37
@ellatrix ellatrix added the [Type] Regression Related to a regression in the latest release label Oct 19, 2018
Copy link
Member

@aduth aduth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there anywhere we've said that the multi line support is only intended for lists? Based on the prop name multilineTag, I would expect that no, it's not just for lists, but any tag. However, everything we build around it seems to be specifically only to support lists. Why is that?

@@ -110,6 +105,33 @@ function remove( node ) {
return node.parentNode.removeChild( node );
}

function createBogusBR( doc ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If 'br' is an abbreviation of "break" (unsure), correct capitalization would be createBogusBr

return br;
}

function pad( node ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is being padded, and with what. This isn't obvious.

packages/rich-text/src/create.js Show resolved Hide resolved
packages/rich-text/src/to-tree.js Show resolved Hide resolved
if ( format.type === 'ol' || format.type === 'ul' ) {
accumulator.push( format );
accumulator.push( multilineFormat );
} else if ( character !== '\u2028' ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we give this magic string a named constant? Out of context, it means nothing to me.

test/e2e/specs/blocks/list.test.js Show resolved Hide resolved
@@ -110,6 +105,33 @@ function remove( node ) {
return node.parentNode.removeChild( node );
}

function createBogusBR( doc ) {
const br = doc.createElement( 'br' );
br.setAttribute( 'data-mce-bogus', '1' );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sure it's known / been discussed before, but this is quite the implied dependency to TinyMCE.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to RichText.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the idea of a "line padding" something which is broadly applicable to support rich text editing? Still seems a bit oriented to TinyMCE specifically, even if not directly by name in the rich-text package.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it something that could be contained within the createEmpty procedure?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the idea of a "line padding" something which is broadly applicable to support rich text editing?

Yes, it's not specific to TinyMCE. It should be part of the output to edit so you can always place the caret correctly in empty lines.

function pad( node ) {
const length = node.childNodes.length;

// Optimise for speed.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As opposed to?

Copy link
Contributor

@mcsf mcsf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to do a better review of this later. It's a very domain-specific change, which makes it a bit harder to get into.

@@ -363,6 +377,7 @@ function createFromMultilineElement( {
unwrapNode,
filterString,
removeAttribute,
startSeparator = false,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New arg missing in JSDoc

@@ -391,7 +406,7 @@ function createFromMultilineElement( {
} );

// Multiline value text should be separated by a double line break.
if ( index !== 0 ) {
if ( index !== 0 || startSeparator === true ) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps shouldPrependSeparator would be a better name? should signals a boolean, and with prepend it seems clearer to me.

return br;
}

function pad( node ) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function is a little hard to decipher. Perhaps defining some terminology if terminology is needed (e.g. padding), and making the high-level purpose evident.

@ellatrix
Copy link
Member Author

Is there anywhere we've said that the multi line support is only intended for lists? Based on the prop name multilineTag, I would expect that no, it's not just for lists, but any tag. However, everything we build around it seems to be specifically only to support lists. Why is that?

It's not, its for both p and li.

@youknowriad youknowriad modified the milestones: 4.1 - UI freeze, 4.2 Oct 19, 2018
@ellatrix

This comment has been minimized.

@ellatrix ellatrix force-pushed the fix/nested-list branch 3 times, most recently from e3f9229 to c08ce51 Compare October 24, 2018 08:36
@ellatrix ellatrix requested review from mcsf, aduth and a team October 24, 2018 09:23
@ellatrix ellatrix force-pushed the fix/nested-list branch 2 times, most recently from 28df82f to 381d0c7 Compare October 24, 2018 10:38
*
* @return {string} HTML string.
*/
export function toHTMLString( value, multilineTag ) {
const tree = toTree( value, multilineTag, {
export function toHTMLString( value, multilineTag, multilineWrapperTags ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

toDOM( { value, multilineTag, multilineWrapperTags } );
toTree( { value, multilineTag, multilineWrapperTags } );
toHTMLString( value, multilineTag, multilineWrapperTags ); 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that toHTMLString is now a public API. Fine to change it though, and make sure everything keeps working.

@aduth

This comment has been minimized.

@aduth

This comment has been minimized.

@aduth

This comment has been minimized.

@aduth

This comment has been minimized.

@ellatrix
Copy link
Member Author

@aduth, thanks, looking at the issues.

@ellatrix
Copy link
Member Author

@aduth It's because we're handling ENTER behaviour, but not BACKSPACE. I pushed a fix, which I'm still polishing.

Copy link
Contributor

@youknowriad youknowriad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested and works well for me 👍

Code-wise, it's a bit complex for me to dive in. cc @aduth since you reviewed earlier.

@ellatrix
Copy link
Member Author

Thanks @youknowriad! I'll wait for a second review as well before merging.

export function toHTMLString( value, multilineTag ) {
const tree = toTree( value, multilineTag, {
export function toHTMLString( { value, multilineTag, multilineWrapperTags } ) {
// Check other arguments for back compact.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we going to deprecate?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Typo: compact -> compat (or backward compatibility since I'm not sure what we gain in abbreviating)

const isHorizontalNavigation = keyCode === LEFT || keyCode === RIGHT;
if ( isHorizontalNavigation ) {
this.onHorizontalNavigationKeyDown( event );
}

if ( keyCode === DELETE || keyCode === BACKSPACE ) {
if ( this.multilineTag ) {
const record = this.createRecord();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no idea what any of this logic is doing. I would think we're unnecessarily handling backspaces / deletes when removing and not already at the extent of where a line separator would occur. Is that not true?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what you mean? Do you mean that we're calling this when we're the selection is not at the edge of a line? We need to have the value to check if we are in the first place, so I don't see a problem with this.

* the selection if none are provided.
*
* @param {Object} value Value to modify.
* @param {number} startIndex Start index.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Mis-alignment of spaces for description (two too far).

import { LINE_SEPARATOR } from '../special-characters';
import { getSparseArrayLength } from './helpers';

describe( 'removePreviousLineSeparator', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the expected behavior of an uncollapsed selection? I'd expect it to be different, at least based on the behavior of backspace in a traditional textarea field.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add tests for it as well. I guess it shouldn't remove the line.

/**
* Internal dependencies
*/

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no consistency to the newline placement after these blocks. While technically against the standard, most else of the application omits the newline. We should at the very least be consistent within the same file.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I thought there should be a newline in between, so I try to be consistent with that. It would be good to have a linting rule for it.

packages/rich-text/src/to-dom.js Show resolved Hide resolved
packages/rich-text/src/to-tree.js Show resolved Hide resolved
test/e2e/specs/blocks/list.test.js Show resolved Hide resolved
@ellatrix ellatrix requested a review from a team October 30, 2018 09:13
@youknowriad
Copy link
Contributor

Looks like the feedback is addressed and the PR still works properly, let's merge when the tests pass.

@ellatrix
Copy link
Member Author

There's just a covecov failure, which we decided to ignore. Probably because I introduces a few files that are something like:

export function getSelectionEnd( { end } ) {
 	return end;
 }

We can add more tests separately if needed.

I think this is a big improvement for multiline values. Let's merge and iterate. I'll keep an eye on any bugs popping up.

@ellatrix ellatrix merged commit 844f0ea into master Oct 30, 2018
@ellatrix ellatrix deleted the fix/nested-list branch October 30, 2018 10:15
@mtias mtias added the [Block] List Affects the List Block label Oct 30, 2018
@talldan
Copy link
Contributor

talldan commented Nov 1, 2018

edit - Ignore this, the issue seems to have been caused by #10723, must have still had the broken build loaded when testing this PR.


This change seems to have resulted in this bug:
#11348

Looking into it now, seems a pretty urgent one to fix.

The bug seems to be caused by unstableOnFocus not triggering in RichText - the table cells are not always being selected when they're clicked on.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Block] List Affects the List Block [Feature] Rich Text Related to the Rich Text component that allows developers to render a contenteditable [Priority] High Used to indicate top priority items that need quick attention [Type] Bug An existing feature does not function as intended [Type] Regression Related to a regression in the latest release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

List block: list breaks when a new subitem is added using the keyboard.
6 participants