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

Plugin: Quote Block Initial Attempt #327

Closed
wants to merge 6 commits into from

Conversation

BE-Webdesign
Copy link
Contributor

Here is an initial attempt at using the current API for a quote block.
I just guessed as to how this should be structured, honestly have no
idea but I tried to mimic the codebase the best I could.

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.

Thanks for helping on the blocks 👍 I left some small notes above :). The main thing we should reconsider here is avoiding creation of Editable components for each block, we should reuse the same Editable component for all blocks to ease creating blocks.

blocks/index.js Outdated
@@ -7,6 +7,7 @@ import * as query from 'hpq';

export { query };
export { default as Editable } from './components/editable';
export { default as EditableQuote } from './components/wp-quote';
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 our idea for now, is to use the Editable component in all the places we need something to be editable. Typically the Editable component could leverage TinyMCE or something. So in the context of the quote for example, you'd use the Editable twice, once for the content and once for the citation. (I think the quote block from the per-block prototype is using this approach, could be an inspiration https://github.com/WordPress/gutenberg/blob/master/docs/tinymce-per-block/src/blocks/quote-block/form.js#L95-L133)


attributes: {
value: query( 'blockquote', html( 'p' ) ),
citation: query( 'cite,footer', text() )
Copy link
Contributor

Choose a reason for hiding this comment

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

We should always use footer for the citation IMO. Also, why don't we use html instead of text. The citation could include links, formatting etc...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just did it for this go around, so the output looked nicer, when the actual component is finalized it should definitely be grabbing the html.

Copy link
Contributor Author

@BE-Webdesign BE-Webdesign Mar 25, 2017

Choose a reason for hiding this comment

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

Also on the cite vs footer issue, #311 uses cite, post-content.js uses footer, so I put in both for the time being. We will want to make a decision one way or the other and I believe mtias has already made that decision in #311, so maybe we should only parse cite.

Copy link
Contributor

Choose a reason for hiding this comment

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

Good point. Let's do cite for everything!

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 a difference. footer is a block element, which should be the wrapper of cite (if any). cite can also be used elsewhere, like in figcaption.

Copy link
Member

Choose a reason for hiding this comment

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

Here's some info: https://w3c.github.io/html/grouping-content.html#the-blockquote-element
There are lots of possibilities to format this.

},

save( attributes ) {
return wp.element.createElement( 'p', null, attributes.value );
Copy link
Contributor

Choose a reason for hiding this comment

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

We're not using it yet, but it's the responsibility of the save function to return the correct markup to be stored in postContent. Typically, this should return <blockquote><p>content</p><footer>citation</footer></blockquote>

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 figured, I was waiting on feedback before going ahead and changing this.

the responsibility of the save function to return the correct markup to be stored in postContent

Should it use renderToString(element) instead as a return value? Or what is the purpose of returning the react element itself?

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 the current idea is to use renderToString outside the block (in a global maner). Relevant to this discussion #284 (review)
#302 (comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds good, so save() will be a way to create a clone of the element basically?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yep, A clone of the output element (could be different than the edit element shown on the editor). I personally still have a preference for return a string instead of an element, but I'm ok trying this way and see how it feels, we can still reconsider later.

@mtias mtias added Framework Issues related to broader framework topics, especially as it relates to javascript [Feature] Blocks Overall functionality of blocks labels Mar 27, 2017
@nylen
Copy link
Member

nylen commented Mar 27, 2017

Rebased and changed wp/ namespace to core/ (see #332).

@BE-Webdesign BE-Webdesign force-pushed the quote-block branch 2 times, most recently from 0978081 to 7817999 Compare March 28, 2017 18:17
@BE-Webdesign
Copy link
Contributor Author

BE-Webdesign commented Mar 28, 2017

Tried rebasing and force pushing. The fork looks fine but for some reason the PR still thinks two files that were removed still exist which is causing the test suite to fail. Any ideas on how to fix?

Update: I made an error in my code, that is the reason for the tests to fail; oooops.

@BE-Webdesign
Copy link
Contributor Author

This seems to be working pretty well now.

wp.blocks.registerBlock( 'core/quote', {
title: 'Quote',
icon: 'format-quote',
category: 'quote',
Copy link
Contributor

Choose a reason for hiding this comment

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

The category should be common. see #323 (comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

woops.


attributes: {
value: query( 'blockquote', html( 'p' ) ),
citation: html( 'cite,footer' )
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't this follow the same markup we use for the save?

Copy link
Contributor Author

@BE-Webdesign BE-Webdesign Mar 29, 2017

Choose a reason for hiding this comment

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

Probably, the only problem is that this will not work for our example post-content.js if we change it to just cite. We will want to make a decision on the markup.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not against changing the post-content too. Anyway, this is not blocking for me right 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.

Do you want to do footer or cite for 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.

Going to do footer for now.

<blockquote>
<Editable
value={ combinedValue }
onChange={ ( newValue ) => onChange( { newValue } ) } />
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 should be onChange( { value: newValue } )

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That sounds right.

<cite>
<Editable
value={ citation }
onChange={ ( newValue ) => onChange( { newValue } ) } />
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 should be onChange( { citation: newValue } )

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yup, good catches all around 👍

Copy link
Contributor Author

@BE-Webdesign BE-Webdesign Mar 29, 2017

Choose a reason for hiding this comment

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

I think it is { value: newValue } again? value is a prop for the Editable component, where as citation is a prop for the quote component. onChange() is feeding the Editable component.

Copy link
Contributor

Choose a reason for hiding this comment

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

the onChange is a callback we need to call to update the block attributes parsed originally by the attributes object above. This second Editable is responsible of editing the citation block attribute, so I think we should call onChange( { citation: newValue } )

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay, I think I understand what you are saying. I'm still not 100% clear on what the role of onChange() is.

Copy link
Contributor

@youknowriad youknowriad Mar 29, 2017

Choose a reason for hiding this comment

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

Let me try to explain:

On the parent component rendering the blocks, we keep track of the current state (block list) and each time this state is updated we re-render the blocks by calling the edit method of each block.

When this parent component renders the block (calling edit) it provides an onChange callback, the block can call to update its state (its attributes), so anytime a block attribute needs to be changed, we should call onChange({ [ attributeName ]: attributeValue })

calling the onChange will update the state and triggers rendering the blocks again. This is how the unidirectional data flow of React (and similar) works.

I hope this is clear enough

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 I understand that. I am having trouble on figuring out where onChange() hooks up to in the code. And where the onChange is being passed in.

I see :

{ blocks.map( ( block, index ) =>
    <div key={ index }>
	{ wp.blocks.getBlockSettings( block.blockType ).edit( block.attributes ) }
    </div>
) }

I do not see where the onChange is passed in, in the code currently. Or is there some magic going on somewhere?

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh I think you need to rebase :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Looks like it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay it makes a lot more sense now.

category: 'quote',

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

Choose a reason for hiding this comment

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

Maybe we could write html( 'blockquote p' ) to avoid the concat in the edit method

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds good!

Here is an initial attempt at using the current API for a quote block.
I just guessed as to how this should be structured, honestly have no
idea but I tried to mimic the codebase the best I could.
@BE-Webdesign
Copy link
Contributor Author

@youknowriad

I updated everything. Let me know what you think.

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.

We'll probably need some styling to match the prototype UI, but this is good for now. 🚢 it

@BE-Webdesign
Copy link
Contributor Author

Okay, I'll add the styles real quick.

@youknowriad
Copy link
Contributor

@BE-Webdesign The text block lacks styling too, so If you want to keep this for later (maybe together with the text block, it's ok too)

@BE-Webdesign
Copy link
Contributor Author

Too late. I added it already and rebased the latest commits into one.

@@ -0,0 +1,24 @@
#editor blockquote {
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 it's preferable to add a class to the quote block (Similar to this https://github.com/WordPress/gutenberg/blob/master/docs/tinymce-per-block/src/blocks/quote-block/form.js#L95). Other blocks may use the blockquote with other styling options.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay, sounds good. core-quote?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not the best person for naming things hahaha :) Always had trouble with it. I'll choose editor-blocks-quote to match the folder structure, I guess we should define a convention somewhere

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 matching the slug for the block would be a good place to start. That way the css classes are namespaced like the blocks are.

Copy link
Member

Choose a reason for hiding this comment

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

At some point we'll also need to consider if blocks can define their own default styles when rendered on the front-end of a site.

Choose a reason for hiding this comment

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

At some point we'll also need to consider if blocks can define their own default styles when rendered on the front-end of a site.

To make it overridable, the fewer inline styles the better. The editor can't prevent the user from adding inline styles in raw mode, but it shouldn't encourage them in visual mode. The theme and plugins will be supplying the styles for the front-end look, though, so the block itself shouldn't have "default" styles beyond what the browser defines. The current method of defining editor-style.css should work the same way for this, right?

Copy link
Member

Choose a reason for hiding this comment

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

Right, I don't think we'd want to encourage any inline styles, which furthers the need to consider stylesheets distinguished between the editor and front-end versions of a block. While the blocks here are first-party, we should outline and use an approach consistent with what we expect from plugin authors who implement their own blocks (a plugin which implements a slider block will need ability to define editor and front-end baseline styles). Whatever we land on, I agree that theme styles should always take precedence when defined for a block.

Choose a reason for hiding this comment

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

To be clear, I think the editor should not define any styles for rendering the content in the blocks.
The theme and plugins can supply styles, because they can also put those on the front end. But the editor can't put any on the real front end, so to get the correct preview, it shouldn't put any in the editor either.
As for "blocks defining their own default styles", a block is either core or added by user code, so how is it "defining its own"?

@@ -0,0 +1,24 @@
#editor blockquote {
margin: 2em;
Copy link
Contributor

Choose a reason for hiding this comment

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

margin: 0 ?

Copy link
Contributor Author

@BE-Webdesign BE-Webdesign Mar 29, 2017

Choose a reason for hiding this comment

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

I was just copying styles that I found in one of the prototypes. I can change these later got to go for now.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yep, I guess the other one had a margin in all the blocks. The margin felt a bit weird while testing.

category: 'common',

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

Choose a reason for hiding this comment

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

A blockquote could contain multiple paragraphs. In my explorer demo I use text: query( 'p', html() ) instead which assigns text as an array of each paragraph's HTML.

https://aduth.github.io/hpq/

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh my bad, it was my recommendation

Copy link
Contributor

Choose a reason for hiding this comment

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

Could we join the output directly using hpq?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We will probably want to map the values in value to its of <p> component as well right?

Copy link
Member

Choose a reason for hiding this comment

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

Could we join the output directly using hpq?

Not super cleanly, but possible. Since each matcher is just a function receiving a node, you can create your own pass-through:

attributes: {
	value: ( node ) => query( 'p', html() )( node ).join()
}

Or some compose-y equivalent.

I don't know that it's necessary a big issue to need to deal with this in the render behavior though.

return (
<blockquote>
{ value }
<footer>
Copy link
Member

Choose a reason for hiding this comment

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

We may want to account for the case that citation is empty and avoid including the footer if possible. This was one of the reasons for hpq: to account for cases where the markup isn't always necessarily the same in the generated output (maybe a quote citation, maybe an image caption, but not necessarily).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yup sounds good! For some reason this slipped my brain, great catch. Does hpq return an empty value if there is nothing matched by the query?

Copy link
Member

Choose a reason for hiding this comment

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

Does hpq return an empty value if there is nothing matched by the query?

Yeah, specifically an undefined value if I recall correctly.

@aduth
Copy link
Member

aduth commented Mar 31, 2017

As of #335, a block's save and edit will receive an object akin to a React component's props where attributes and onChange are keys. The thinking being that the value of save and edit are in fact treated as components. In most usage we'll prefer the stateless function component, but you could even assign:

edit: class extends wp.element.Component {
	componentDidMount() {
		fetch( '/resource' ).then( /* ... */ );
	}

	render() {
		const { attributes } = this.props;
		// ...
	}
}

Not sure yet what naming conventions we'll want to recommend for the argument passed to the function (for ES5 implementations; ES6 can easily destructure). Maybe props is clear enough? Else block.

Just now realizing the docs example needs to be updated.

@BE-Webdesign
Copy link
Contributor Author

Closing in favor of #419.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Blocks Overall functionality of blocks 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