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

Editable Component #18361

Merged
merged 3 commits into from
Mar 19, 2020
Merged

Editable Component #18361

merged 3 commits into from
Mar 19, 2020

Conversation

ellatrix
Copy link
Member

@ellatrix ellatrix commented Nov 7, 2019

Description

Alternative to #18238.
Fixes #13570.

This PR introduces a new Editable component, which purpose is to edit text without any formatting. It won't allow any formatting, nor will it have formatting controls. If any HTML tags are present, they are just rendered as text, not as text formatting.

The behaviour is much the same as a plain textarea, but there are a few advantages:

  • This component has native autoresizing.
  • It has the same RichText APIs: onSplit, onReplace, onRemove, onMerge.
  • Autocomplete works.
  • Annotation work, and editorial comments would work.
  • Selection is stored just like RichText's, which means it could be used for collaborative editing.
  • It shares the same undo/redo logic as RichText.

Under the hood, it's just RichText where formats are disabled, since a lot of the code can be shared. In the future, we could refactor this a bit and split out all the formatting logic, but for now, this works just fine. I could see Editable become the base component for RichText.

Sharing the codebase will eventually make the experience more streamlined. API improvements for text will benefit rich text, and improvements for rich text will benefit text.

I updated the site title block to use this component.

How has this been tested?

Try out the site title block.

Screenshots

Types of changes

Checklist:

  • My code is tested.
  • My code follows the WordPress code style.
  • My code follows the accessibility standards.
  • My code has proper inline documentation.
  • I've included developer documentation if appropriate.

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.

Any thoughts about PlainText? Should we deprecate it, should we create a shortcut?

@Copons
Copy link
Contributor

Copons commented Nov 11, 2019

Any thoughts about PlainText? Should we deprecate it, should we create a shortcut?

I think with Text at this point the only use case for PlainText is as a wrapper for TextareaAutosize.

(@ellatrix I'm so sorry for not having reviewed this yet, but I've been swamped with new urgent tasks!)

@@ -65,6 +65,7 @@ class RichTextWrapper extends Component {
this.onPaste = this.onPaste.bind( this );
this.onDelete = this.onDelete.bind( this );
this.inputRule = this.inputRule.bind( this );
this.getAllowedFormats.EMPTY_ARRAY = [];
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not just a module constant

Copy link
Member Author

Choose a reason for hiding this comment

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

This empty array should be used specifically for the allowed formats state. I don't see a reason to make it a module constant. It isn't and shouldn't be reused for other state.

Copy link
Contributor

Choose a reason for hiding this comment

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

Would having a more declarative name be of any help? E.g. this.getAllowedFormats.NO_FORMATS = []

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 can also use a module variable and name it differently. I just have this habit of putting stuff close to what is using it.

const {
allowedFormats,
formattingControls,
__unstableDisableFormats,
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of having these disbleFormats props which doesn't make sense for a RichText component, ideally RichText should be a special version of Text. how feasable is that?

Copy link
Member Author

Choose a reason for hiding this comment

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

  1. Why does it not make sense for RichText? I think it's fine.
  2. It's harder right now to make RichText a wrapper around Text, but yes, in the future this could be done. To keep things simple with a minimum amount of code changes, I chose to use RichText internally in Text, but that can be refactored later.

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 guess we could just make it a stable prop and omit __unstable. Maybe I'm being too prudent in introducing new props.

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree with @ellatrix.
This is a great starting point: it makes things very straightforward, and doesn't prevent an eventual Text/RichText "switcheroo".

Copy link
Contributor

Choose a reason for hiding this comment

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

In my mind ideally disableFormats could be a valid prod for a low-level Text component that is used by higher-level PlainText and RichText components. So I think it doesn't make sense for RichText because RichText by definition is for Rich Text which means "formats".

I get that the refactor can be achieved later and is probably not that easy so unstable is better.

Copy link
Member Author

Choose a reason for hiding this comment

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

@youknowriad Oh, I'm sorry, I confused disableFormats and disableLinebreaks. I thought you were talking about the line breaks. Sorry about that. You're right, the prop doesn't make sense on RichText at all, it's just a stepping stone for now.

onChange( insert( value, '\n' ) );
if ( ! disableLineBreaks ) {
onChange( insert( value, '\n' ) );
}
Copy link
Contributor

Choose a reason for hiding this comment

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

While reading the changes here, I was wondering why these onEnter handlers are block-editor specific and not something for the regular RichText component?

Copy link
Member Author

Choose a reason for hiding this comment

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

Inserting line breaks on enter could be a default feature of RichText (in the rich text package), sure. But it should also be overridable. Right now there's just no default handling. Maybe in a future PR.

*/
import RichText from '../rich-text';

function Text( props ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we name this PlainText instead.

Copy link
Member Author

Choose a reason for hiding this comment

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

@ellatrix
Copy link
Member Author

@youknowriad @Copons PlainText is already taken. Do you see a way around that? I don't mind plainly Text. It's plain enough to me.

@youknowriad
Copy link
Contributor

If the APIs are compatible why not just replace the existing PlainText?

@ellatrix
Copy link
Member Author

@youknowriad because, until now, PlainText was just a textarea that autoresizes. I guess developers are expecting it to be a textarea now. I don't know if it's fine to change blocks currently using PlainText as it will look different (textarea style vs "invisible" style). I'm fine either way though.

@@ -422,6 +422,10 @@ _Type_

- `Object`

<a name="Text" href="#Text">#</a> **Text**

Renders an editable text input in which text formatting is not allowed.
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this might be misinterpreted as an <input type="text">.
What about being more candid and go with something like Renders a contenteditable element in which text formatting is not allowed?

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, we need better descriptions. The mobile team previously reminded me not to use contenteditable as that's not the case for the native apps. @hypest @SergioEstevao @Tug and maybe others (not sure if @WordPress/native-mobile works), what would be a good description? :)

Copy link
Member Author

Choose a reason for hiding this comment

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

We should also update the RichText description. :)

Copy link
Contributor

Choose a reason for hiding this comment

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

Also Text might be confusing because in react-native it's a display-only component. For this, react-native uses TextInput:

A foundational component for inputting text into the app via a keyboard. Props provide configurability for several features, such as auto-correction, auto-capitalization, placeholder text, and different keyboard types, such as a numeric keypad

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry I'm a bit late to the conversation, I'd vote for TextInput since it would have very similar behavior as the react-native component @koke talked about.

@youknowriad
Copy link
Contributor

youknowriad commented Nov 12, 2019

@ellatrix Good point. I'd love other thoughts here @mcsf @aduth

@youknowriad
Copy link
Contributor

If PlainText is just an autosizeable textarea, there's no need for a component for it, you can just use TextareaAutosize. That the reason I think naming this PlainText makes sense. For me PlainText has always been the equivalent of RichText with non-formatted code. I can see how this can be consider a breaking change somehow, maybe fine with a Dev Note?

@@ -0,0 +1,114 @@
# `Text`

Renders an editable text input in which text formatting is not allowed.
Copy link
Contributor

Choose a reason for hiding this comment

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

@ellatrix
Copy link
Member Author

@youknowriad Ok, sounds good to me. I was just being careful. :) It's worth noting though that other (not listed) textarea props will no longer work, for example rows (which doesn't make much sense when it autoresizes). I guess we should be extra clear about that in the dev note.

@Copons
Copy link
Contributor

Copons commented Nov 12, 2019

What about keeping PlainText as a wrapper for TextareaAutosize, in the off chance we eventually want to replace it with another component (external or not)?

@ellatrix
Copy link
Member Author

ellatrix commented Nov 12, 2019

@youknowriad @Copons Looking at PlainText again, I see that the web and native version of it have diverged so much, I'm not sure why. It has several font* props.

<TextInput
{ ...this.props }
ref={ ( x ) => this._input = x }
className={ [ styles[ 'block-editor-plain-text' ], this.props.className ] }
onChange={ ( event ) => {
this.props.onChange( event.nativeEvent.text );
} }
onFocus={ this.props.onFocus } // always assign onFocus as a props
onBlur={ this.props.onBlur } // always assign onBlur as a props
fontFamily={ this.props.fontFamily || ( styles[ 'block-editor-plain-text' ].fontFamily ) }
fontSize={ this.props.fontSize }
fontWeight={ this.props.fontWeight }
fontStyle={ this.props.fontStyle }
/>

I would feel more comfortable starting a new component.

@@ -73,6 +74,7 @@ class RichTextWrapper extends Component {
onSplit,
multiline,
markAutomaticChange,
disableLineBreaks,
Copy link
Member Author

Choose a reason for hiding this comment

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

This prop addressed #13570.

@mcsf
Copy link
Contributor

mcsf commented Nov 18, 2019

If PlainText is just an autosizeable textarea, there's no need for a component for it, you can just use TextareaAutosize. That the reason I think naming this PlainText makes sense. For me PlainText has always been the equivalent of RichText with non-formatted code. I can see how this can be consider a breaking change somehow, maybe fine with a Dev Note?

@ellatrix, @youknowriad: what about a deprecation cycle like so:

  1. Rename Text to PlainText. For new uses of PlainText (i.e. as a native component), developers should use a prop like <PlainText version={ 2 } /> or whatever. Whenever the prop is not used, we can assume it's deprecated usage, and we raise a deprecate warning telling devs to either add the prop or switch to textarea.
  2. After one WP major, we drop the requirement for the version prop and drop compatibility with textarea.

@youknowriad
Copy link
Contributor

@mcsf That sounds interesting. I think I like it. it does create a precedent in terms of backward compatibility handling. At the moment, deprecated calls are never removed. I think they should be at some point, but it's hard to have these discussions properly, how many versions should we wait before removing the deprecated usage...

This is though a very "smallish" breaking change (just a styling one), so I don't think it would be a huge deal.

@ellatrix
Copy link
Member Author

@hypest @SergioEstevao Why are there font* props on the native version of the PlainText block? Why is this not a style object?

@SergioEstevao
Copy link
Contributor

@hypest @SergioEstevao Why are there font* props on the native version of the PlainText block? Why is this not a style object?

So I had a look on this and we don't need to use props we could get them from the style object.
The style object itself can be used directly on TextInput, except for the fontFamily that needs a specific prop but this can be done like this:

render() {
		const style = this.props.style || styles[ 'block-editor-plain-text' ]
		return (
			<TextInput
				{ ...this.props }
				ref={ ( x ) => this._input = x }
				// className={ [ styles[ 'block-editor-plain-text' ], this.props.className ] }
				onChange={ ( event ) => {
					this.props.onChange( event.nativeEvent.text );
				} }
				onFocus={ this.props.onFocus } // always assign onFocus as a props
				onBlur={ this.props.onBlur } // always assign onBlur as a props
				style= { style }
				fontFamily={ style.fontFamily }
				// fontSize={ this.props.fontSize }
				// fontWeight={ this.props.fontWeight }
				// fontStyle={ this.props.fontStyle }
			/>
		);
	}

@ellatrix
Copy link
Member Author

@SergioEstevao What does font family need a separate prop?

@epiqueras
Copy link
Contributor

The site title block looks good to me.

@github-actions
Copy link

Size Change: -889 B (0%)

Total Size: 856 kB

Filename Size Change
build/block-editor/index.js 100 kB +157 B (0%)
build/block-library/index.js 110 kB -1.1 kB (1%)
build/rich-text/index.js 14.4 kB +59 B (0%)
ℹ️ View Unchanged
Filename Size Change
build/a11y/index.js 998 B 0 B
build/annotations/index.js 3.43 kB 0 B
build/api-fetch/index.js 3.39 kB 0 B
build/autop/index.js 2.58 kB 0 B
build/blob/index.js 620 B 0 B
build/block-directory/index.js 6.02 kB 0 B
build/block-directory/style-rtl.css 760 B 0 B
build/block-directory/style.css 760 B 0 B
build/block-editor/style-rtl.css 10.9 kB 0 B
build/block-editor/style.css 10.9 kB 0 B
build/block-library/editor-rtl.css 7.23 kB 0 B
build/block-library/editor.css 7.24 kB 0 B
build/block-library/style-rtl.css 7.42 kB 0 B
build/block-library/style.css 7.43 kB 0 B
build/block-library/theme-rtl.css 669 B 0 B
build/block-library/theme.css 671 B 0 B
build/block-serialization-default-parser/index.js 1.65 kB 0 B
build/block-serialization-spec-parser/index.js 3.1 kB 0 B
build/blocks/index.js 57.5 kB 0 B
build/components/index.js 191 kB 0 B
build/components/style-rtl.css 15.7 kB 0 B
build/components/style.css 15.7 kB 0 B
build/compose/index.js 6.21 kB 0 B
build/core-data/index.js 10.6 kB 0 B
build/data-controls/index.js 1.04 kB 0 B
build/data/index.js 8.2 kB 0 B
build/date/index.js 5.37 kB 0 B
build/deprecated/index.js 771 B 0 B
build/dom-ready/index.js 568 B 0 B
build/dom/index.js 3.06 kB 0 B
build/edit-post/index.js 91.2 kB 0 B
build/edit-post/style-rtl.css 8.47 kB 0 B
build/edit-post/style.css 8.46 kB 0 B
build/edit-site/index.js 5.56 kB 0 B
build/edit-site/style-rtl.css 2.62 kB 0 B
build/edit-site/style.css 2.62 kB 0 B
build/edit-widgets/index.js 4.43 kB 0 B
build/edit-widgets/style-rtl.css 2.58 kB 0 B
build/edit-widgets/style.css 2.58 kB 0 B
build/editor/editor-styles-rtl.css 381 B 0 B
build/editor/editor-styles.css 382 B 0 B
build/editor/index.js 43.8 kB 0 B
build/editor/style-rtl.css 3.97 kB 0 B
build/editor/style.css 3.96 kB 0 B
build/element/index.js 4.44 kB 0 B
build/escape-html/index.js 733 B 0 B
build/format-library/index.js 6.95 kB 0 B
build/format-library/style-rtl.css 502 B 0 B
build/format-library/style.css 502 B 0 B
build/hooks/index.js 1.93 kB 0 B
build/html-entities/index.js 621 B 0 B
build/i18n/index.js 3.49 kB 0 B
build/is-shallow-equal/index.js 711 B 0 B
build/keyboard-shortcuts/index.js 2.3 kB 0 B
build/keycodes/index.js 1.69 kB 0 B
build/list-reusable-blocks/index.js 2.99 kB 0 B
build/list-reusable-blocks/style-rtl.css 226 B 0 B
build/list-reusable-blocks/style.css 226 B 0 B
build/media-utils/index.js 4.83 kB 0 B
build/notices/index.js 1.58 kB 0 B
build/nux/index.js 3.01 kB 0 B
build/nux/style-rtl.css 616 B 0 B
build/nux/style.css 613 B 0 B
build/plugins/index.js 2.54 kB 0 B
build/primitives/index.js 1.5 kB 0 B
build/priority-queue/index.js 780 B 0 B
build/redux-routine/index.js 2.83 kB 0 B
build/server-side-render/index.js 2.55 kB 0 B
build/shortcode/index.js 1.7 kB 0 B
build/token-list/index.js 1.27 kB 0 B
build/url/index.js 4.01 kB 0 B
build/viewport/index.js 1.61 kB 0 B
build/warning/index.js 1.14 kB 0 B
build/wordcount/index.js 1.18 kB 0 B

compressed-size-action

@ellatrix
Copy link
Member Author

Let's do this and iterate.

@ellatrix ellatrix merged commit 0446ed1 into master Mar 19, 2020
@ellatrix ellatrix deleted the try/text branch March 19, 2020 09:18
@github-actions github-actions bot added this to the Gutenberg 7.8 milestone Mar 19, 2020
@youknowriad
Copy link
Contributor

I personally still have concerns on the naming, and since this is a new API, maybe we should take time to discuss it properly.

@ellatrix
Copy link
Member Author

@youknowriad Ok, sorry. It's just been sitting around for a long time and I didn't feel there were big objections. Shall I revert?

@youknowriad
Copy link
Contributor

@ellatrix funny enough I was looking at the PR today before you merge it :)

The Editable name is something we already used a long time ago in Gutenberg for RichText and it has a potential of creation confusion IMO (some plugins, docs still use it)

I'm still leaning towards PlainText replacement personally. @mcsf sounded good. but let's not rush the revert. Give it some time for folks to chime in.

@ellatrix
Copy link
Member Author

Well, if Text was not descriptive enough, and is now clashing with another component for displaying text, so can PlainText and RichText. PlainText doesn't describe something being editable. Alternatively we could have EditablePlainText or <Editable plainText> or something, but I think editable is key in communicating what the component does. I think we should eventually make an alias for RichText too.

@youknowriad
Copy link
Contributor

It seems we don't agree on the Editable name but we agree on needing a low-level component to share code between Rich and Plain (whatever you call it) component. Since PlainText is already an existing API, why not update it to use the Editable component without exposing it for now.

@mtias
Copy link
Member

mtias commented Mar 19, 2020

I don't find the Editable particularly illuminating. It's probably more confusing to me since it has no context what it is editing — in an editor, everything is assumed to be editable. For media we don't have EditableMedia components, etc.

RichText for me is a good name. What is the other use of Text that we are clashing with?

@ellatrix
Copy link
Member Author

@ZebulanStanphill
Copy link
Member

Why not call this component "EditableText"? "Editable" isn't that clear; editable what?

@youknowriad
Copy link
Contributor

Ideally, we find a solution here before the next plugin release (monday).

@ellatrix
Copy link
Member Author

ellatrix commented Mar 20, 2020 via email

@youknowriad
Copy link
Contributor

@ellatrix That works for me but it can wait until Monday, I know you're AFK today.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Block API API that allows to express the block paradigm. [Type] Enhancement A suggestion for improvement.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Rich text: Allow for multiline = false
10 participants