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: implement TinyMCE on the core Editable component #330

Merged
merged 8 commits into from
Mar 28, 2017

Conversation

youknowriad
Copy link
Contributor

In this PR, I'm setting up TinyMCE in the Editable component to ease building blocks using this generic component. Details on this component on the Doc PR https://github.com/WordPress/gutenberg/pull/302/files#diff-8973b2df4ecb1bd89ade51059ee90658R157

Notes

  • I'm reusing the WordPress TinyMCE scripts (v3)
  • I had to expose the React Component class, I wonder if this should be available for block creators or not.
  • For now, the editable component is very basic. It takes a value prop and triggers onChange callback on Focus out (to avoid cursor jumping).
  • With all this .bind calls, I felt the need for the class properties babel plugin.

@youknowriad youknowriad added Framework Issues related to broader framework topics, especially as it relates to javascript TinyMCE labels Mar 27, 2017
@youknowriad youknowriad self-assigned this Mar 27, 2017
@youknowriad youknowriad requested review from mtias and aduth March 27, 2017 10:42
@youknowriad
Copy link
Contributor Author

youknowriad commented Mar 27, 2017

@aduth @nylen The tests are failing because The editable component is being loaded and it requires the wp globals.

  • I personally think we should recreate the registration.js file to isolate the block registration from the other unrelated stuff and this should resolve this issue by not loading the editbale component while testing.

  • We can also mock the globals in bootstrap-test.js

Thoughts?

index.php Outdated
@@ -40,8 +40,9 @@ function gutenberg_register_scripts() {
wp_register_script( 'react-dom', 'https://unpkg.com/react-dom@15/dist/react-dom' . $suffix . '.js', array( 'react' ) );

// Editor
wp_register_script( 'tinymce_js', includes_url( 'js/tinymce/' ) . 'wp-tinymce.php', array( 'jquery' ) );
Copy link
Member

Choose a reason for hiding this comment

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

I think we should get the trunk version, which has the link boundaries.

@mtias mtias changed the title TinyMCE: V1 of the Editable Component initializing TinyMCE Editable: implement TinyMCE on the core Editable component Mar 27, 2017
@aduth
Copy link
Member

aduth commented Mar 27, 2017

this should resolve this issue by not loading the editable component while testing.

Sounds more like a workaround than a proper fix.


export default class Editable extends wp.element.Component {
constructor( ...args ) {
super( ...args );
Copy link
Member

Choose a reason for hiding this comment

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

Minor: ...arguments works just as well and might (a) more clearly indicate you're not really using the arguments and (b) avoid needing to update the call if ever you decide to use one of the arguments.

}
this.editor.setContent( this.props.value );
if ( this.isFocused ) {
this.editor.selection.moveToBookmark( bookmark );
Copy link
Member

Choose a reason for hiding this comment

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

Out of curiosity, does it make any difference if we were to call this while the editor does not have focus?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It can cause the cursor to jump to the beginning of the content editable. I think right now, it does not happen because we're not updating the content from outside the component yet.
I've had some hard time fixing these kind of bugs on the per-block prototype.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah my thinking was if the editor wasn't focused anyways, setting content and moving bookmark wouldn't have any impact on the current active focus. I did some basic testing and this appears to be the case? I was entering this in my console at page load:

var bookmark = tinymce.editors[ 1 ].selection.getBookmark( 2, true );
tinymce.editors[ 1 ].setContent( tinymce.editors[ 0 ].getContent() );
tinymce.editors[ 1 ].selection.moveToBookmark( bookmark );

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right! Can't remember exactly why I had those on the per-block editor. Removing these checks for now


componentDidUpdate( prevProps ) {
if ( this.props.value !== prevProps.value ) {
this.updateContent( !! prevProps.focusConfig );
Copy link
Member

Choose a reason for hiding this comment

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

We're not using the argument passed?

render() {
return el( 'div', {
id: this.id,
contentEditable: true
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't need to assign this, as TinyMCE will do so on our behalf.

}

render() {
return el( 'div', {
Copy link
Member

Choose a reason for hiding this comment

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

JSX?


onChange() {
const value = this.editor.getContent();
if ( value === this.props.value ) {
Copy link
Member

Choose a reason for hiding this comment

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

  • Would isDirty work here?
  • Alternatively, would specifying format: raw to getContent (much more performant) save us at all, or would we expect most calls to this function to pass the condition anyways?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Using isDirty coupled with save is possible I think 👍
And yeah most calls will pass the condition here, I think


wp.blocks.registerBlock( 'wp/text', {
title: 'Text',
icon: 'text',

attributes: {
value: query( 'p', html() )
value: html()
Copy link
Member

Choose a reason for hiding this comment

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

Might still be value in specifying 'p' argument to at least limit value to text within the <p> element of the block?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a good question. If we limit the value to the text within the p, we should limit the Editable to create a unique p and the onChange callback of the Editable component should extract the content of the p before calling the onChange prop.

This is probably what we want, I was thinking we could leave this to a separate PR.

Choose a reason for hiding this comment

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

How does specifying p here affect wpautop() type of stuff?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this is unrelated to wpautop.

The wpautop needs to be implemented by catching the Enter key press and checking whether we should create a new block after the current one or not. (Similar behaviour can be seen on the per-block prototype)

Choose a reason for hiding this comment

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

Well, sort of. What I was interested in was what is actually saved. If I look at the post content of my current blog, there are a lot of text paragraphs separated by one blank line. There are not actual p tags. Somewhere along the way to display wpautop puts p tags? Will the new editor save the p tag that the old editor did not?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm assuming we're talking about the visual editor. Then yes, in its current form this block saves the content with the p tag. A block declares its saved markup in the save function. You can see that the current wp/text block wraps the content with a p tag.

But this is far from being the final version of the text block, it's just here to serve as an example of block while we're building the "surroundings", how we parse the content, how we save it back, how we render the editables etc...

Choose a reason for hiding this comment

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

Yes, it's an example, so I would hope you "do it right". If it saves the p to the content (where the old editor did not), does that affect wpautop?
I get that "it's not the final version" answer a lot, but my experience is that those things are then forgotten and harder to change later. I'm just trying to think of consequences down the line, instead of "building this block now".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the exact reason why we're trying to build this as a plugin and to tie it to real data (real posts), for now, my answer to this is "I don't know". We'll have to try the editor in the context of real data to figure out these consequences (and fix all the unwanted behaviour).

element/index.js Outdated
/**
* A base class to create WordPress Components (Refs, state and lifecycle hooks)
*/
export const Component = ReactComponent;
Copy link
Member

Choose a reason for hiding this comment

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

Prefixed imports bothering me a bit. We could instead do...

import { Component } from 'react';

// ...
export { Component };

What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm ok either way :)

this.onFocusIn = this.onFocusIn.bind( this );
this.onFocusOut = this.onFocusOut.bind( this );
this.id = `tinymce-instance-${ editorInstance }`;
editorInstance++;
Copy link
Member

Choose a reason for hiding this comment

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

Doesn't make a whole lot of difference, but we could assign the incremented value as a static property of the class:

export default class Editable extends wp.element.Component {
	static instance = 1;

	constructor() {
		// ...
		this.id = `tinymce-instance-${ this.constructor.instance }`;
		this.constructor.instance++;

		// or: (increment slightly less obvious)
		this.id = `tinymce-instance-${ this.constructor.instance++ }`;
	}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This will be removed since I'm updating to Tiny4 which allows us to use the dom node instead of generating a random id :)

@nylen
Copy link
Member

nylen commented Mar 27, 2017

We can also mock the globals in bootstrap-test.js

This, or mocking the editable component itself, would be my preference. I'd rather avoid structuring the code with a fairly arbitrary division imposed by the test setup.

Also worth thinking about how we will write full-browser tests including the editable component.

@youknowriad
Copy link
Contributor Author

This, or mocking the editable component itself, would be my preference.

Not sure how to do that.

I'd rather avoid structuring the code with a fairly arbitrary division imposed by the test setup.

This is true, but I was thinking we should move the registration API to its own file regardless of the way we fix the test setup

@youknowriad youknowriad force-pushed the add/setup-tinymce-in-plugin branch from 747babc to 4b7a51a Compare March 28, 2017 08:55
@@ -13,3 +13,8 @@ global.document = require( 'jsdom' ).jsdom( '', {
} );
global.window = document.defaultView;
global.navigator = window.navigator;
global.wp = {
Copy link
Member

Choose a reason for hiding this comment

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

The issue seems to be that the globals are only defined in the main bundle configuration, whereas the entry point of the tests bundle includes only test files. I might toy with the configuration a bit to see if I can fix it so we don't need to stub these out.

@aduth
Copy link
Member

aduth commented Mar 28, 2017

Should we remove contenteditable from main wrapper div at this point?

@aduth
Copy link
Member

aduth commented Mar 28, 2017

In 97ba143 I reconfigured Webpack to ensure that globals from each submodule are exposed for the tests. Unfortunately this means pulling in React again separate from the one we define in the plugin, since in the context of the Node tests it can't pull from the CDN like it does in the browser. We might want to think of a better way to avoid managing two separate copies of these vendor dependencies.

More importantly to avoid potential naming clash with existing plugins.
“Nightly” might be more accurately a version description, but this is
all temporary anyways.
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.

Looks good 👍

}

initialize() {
const config = {
Copy link
Member

Choose a reason for hiding this comment

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

To recreate default contenteditable behaviour, we should probably add browser_spellcheck: true.

@youknowriad youknowriad merged commit ca4111c into master Mar 28, 2017
@youknowriad youknowriad deleted the add/setup-tinymce-in-plugin branch March 28, 2017 14:38

componentWillUnmount() {
if ( this.editor ) {
this.editor.destroy();
Copy link

Choose a reason for hiding this comment

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

Use this.editor.remove(); destroy is more for memory cleanups on unload of the page not really a problem now days on modern browsers. Destroy is called by remove.

onSetup( editor ) {
this.editor = editor;
editor.on( 'init', this.onInit );
editor.on( 'focusout', this.onChange );
Copy link

Choose a reason for hiding this comment

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

focusout will when focus is shifted internally within nested contenteditables. Use "blur" instead or possible 'change' since that is called on every undo level add including 'blur' since that adds an undo level. Might be to frequent though.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since this is a "controlled" TinyMCE (each time the content prop changes, we update the content), I had issues while using change on the per-block prototype. I haven't tried blur though

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Framework Issues related to broader framework topics, especially as it relates to javascript
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants