-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Conversation
@aduth @nylen The tests are failing because The
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' ) ); |
There was a problem hiding this comment.
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.
Sounds more like a workaround than a proper fix. |
blocks/components/editable/index.js
Outdated
|
||
export default class Editable extends wp.element.Component { | ||
constructor( ...args ) { | ||
super( ...args ); |
There was a problem hiding this comment.
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.
blocks/components/editable/index.js
Outdated
} | ||
this.editor.setContent( this.props.value ); | ||
if ( this.isFocused ) { | ||
this.editor.selection.moveToBookmark( bookmark ); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 );
There was a problem hiding this comment.
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
blocks/components/editable/index.js
Outdated
|
||
componentDidUpdate( prevProps ) { | ||
if ( this.props.value !== prevProps.value ) { | ||
this.updateContent( !! prevProps.focusConfig ); |
There was a problem hiding this comment.
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?
blocks/components/editable/index.js
Outdated
render() { | ||
return el( 'div', { | ||
id: this.id, | ||
contentEditable: true |
There was a problem hiding this comment.
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.
blocks/components/editable/index.js
Outdated
} | ||
|
||
render() { | ||
return el( 'div', { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JSX?
blocks/components/editable/index.js
Outdated
|
||
onChange() { | ||
const value = this.editor.getContent(); | ||
if ( value === this.props.value ) { |
There was a problem hiding this comment.
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
togetContent
(much more performant) save us at all, or would we expect most calls to this function to pass the condition anyways?
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
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".
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 :)
blocks/components/editable/index.js
Outdated
this.onFocusIn = this.onFocusIn.bind( this ); | ||
this.onFocusOut = this.onFocusOut.bind( this ); | ||
this.id = `tinymce-instance-${ editorInstance }`; | ||
editorInstance++; |
There was a problem hiding this comment.
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++ }`;
}
There was a problem hiding this comment.
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 :)
This, or mocking the Also worth thinking about how we will write full-browser tests including the |
Not sure how to do that.
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 |
747babc
to
4b7a51a
Compare
bootstrap-test.js
Outdated
@@ -13,3 +13,8 @@ global.document = require( 'jsdom' ).jsdom( '', { | |||
} ); | |||
global.window = document.defaultView; | |||
global.navigator = window.navigator; | |||
global.wp = { |
There was a problem hiding this comment.
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.
Should we remove |
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.
There was a problem hiding this 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 = { |
There was a problem hiding this comment.
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
.
|
||
componentWillUnmount() { | ||
if ( this.editor ) { | ||
this.editor.destroy(); |
There was a problem hiding this comment.
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 ); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
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-8973b2df4ecb1bd89ade51059ee90658R157Notes
Component
class, I wonder if this should be available for block creators or not.value
prop and triggersonChange
callback on Focus out (to avoid cursor jumping)..bind
calls, I felt the need for the class properties babel plugin.