-
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
Editor: Make BlockListBlock extensible #3493
Conversation
Codecov Report
@@ Coverage Diff @@
## master #3493 +/- ##
==========================================
- Coverage 37.53% 35.82% -1.72%
==========================================
Files 278 269 -9
Lines 6724 6753 +29
Branches 1226 1220 -6
==========================================
- Hits 2524 2419 -105
- Misses 3539 3659 +120
- Partials 661 675 +14
Continue to review full report at Codecov.
|
// TODO: Use withHooks HOC from https://github.com/WordPress/gutenberg/pull/3321. | ||
const hooks = createHooks(); | ||
|
||
export default function withFilters( hookName ) { |
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.
Unit tests would be lovely here, but I prefer to wait for #3321 before I come up with the idea how to test it :)
51c4648
to
e2a61f5
Compare
e2a61f5
to
4806455
Compare
4806455
to
c7ce65a
Compare
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 like the higher-order components you've introduced here. 👍
To the question of moving hooks to utils
, it touches on prior discussion in Slack meetings, #3318, and #3321 (comment) which asks the question of whether it would be more sensible to have a single global hooks registry. It is starting to seem more as though it should. In general, I'm not a fan of the "utils" naming; it risks becoming a dumping ground. It might be nice if we could reuse @wordpress/hooks
(wp.hooks
) for this purpose, maybe in addition to exposing the hooks factory, it also exposes its own internal instance of one? Seems it could have some downsides, but still maybe better than moving to utils.
*/ | ||
import { startCase } from 'lodash'; | ||
|
||
export default function wrapperDisplayName( wrapperPrefix, WrappedComponent ) { |
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 not really a higher-order component, since the return value from the function is a string, not the enhanced component. I think we'd want to do one of either:
- Move this elsewhere to better reflect that it is not a higher-order component
- Make it in-fact a higher-order component, i.e. return an enhanced component with
displayName
assigned
My preference is toward the latter.
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.
True, it’s only related logic. It’s not HOC.
Just to confirm. Option 2 would be a HOC called inside another HOC that would update displaName, right?
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.
Just to confirm. Option 2 would be a HOC called inside another HOC that would update displaName, right?
Yes. Though now that I'm thinking about it, layering components like this risks being a performance hazard (reference).
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.
Unless we do perf optimization and structure like a HOC but behind the scenes only update displayName on the component:
const withDisplayName = ( wrapperPrefix, SourceComponent ) => ( WrappedComponent ) => {
WrappedComponent.displayName = ...;
return WrappedComponent;
}
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.
After some thinking, I'd rather move it to @wordpress/utils
to components.js
file and use it as it is.
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 it be a better fit in @wordpress/element
? utils
as a dumping ground is not scalable.
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.
Moved and documented.
editor/modes/visual-editor/block.js
Outdated
@@ -411,7 +412,7 @@ class VisualEditorBlock extends Component { | |||
} | |||
} | |||
|
|||
export default connect( | |||
export default withFilters( 'VisualEditorBlock' )( connect( |
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.
As we start to add multiple higher-order components, might be nice to use flow
(flowRight
) to compose them.
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.
Or expose compose as an alias of flowRight 😎
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 introduced compose
as part of @wordpress/element
.
|
||
`withFilters` is a part of [Native Gutenberg Extensibility](https://github.com/WordPress/gutenberg/issues/3330). It is also a React [higher-order component](https://facebook.github.io/react/docs/higher-order-components.html). | ||
|
||
Wrapping a component with `withFilters` provides a unique `instanceId` to serve this purpose. |
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.
Is the instance ID bit relevant to someone using this higher-order component? Is it even true (looking at the implementation)? Maybe drop this sentence.
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.
Nope, I copied it and forgot to update ...
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.
Fixed.
I totally agree that |
One hope I have is that all |
It looks like it was exposed in WP core for a short while the way @aduth described. See https://core.trac.wordpress.org/browser/trunk/tests/qunit/wp-includes/js/wp-hooks.js?rev=41375. @adamsilverstein can you confirm that it is going to be possible to use hooks with a shared global instance using: wp.hooks.applyFilters( ... );
wp.hooks.addFilter( ... ); I bet this is how it was planned to work according to tests I can see here. I think we can mirror that behavior until it lands in WP core. |
What if we couple this to a provider? Meaning we retrieve the Also, we need to make "VisualBlockEditor" extensible, but this component will be used in the future for page building, template building. We should ask the question if we want it to be extensible in the PostEditor only or in all usages and how a plugin can decide in which context (post editor, page editor) he wants to extend the component. For example: Even if it's not true but what if the collaborative editing is meant only for the post editor? |
This was something @aduth explored in #3321. It is another provider to maintain, which also needs to work properly with the newly introduced
I see the collaborative editing perfectly usable everywhere, but I understand your point 😄 The thing is that it's very hard to anticipate at the moment all the possible usages. That's why I'm proposing also return applyFilters( hookName, <WrappedComponent { ...props } />, props ); Could we pass |
Another option I can think of is to chain filters as follows: return applyFilters(
hookName,
applyFilters( `${ hookName }.${ contextName }`, <WrappedComponent { ...props } />, props ),
props
); So you could register global filter or specific to post or page editor. |
editor/modes/visual-editor/block.js
Outdated
dispatch( editPost( { meta } ) ); | ||
}, | ||
} ) | ||
export default compose( |
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 looks pretty cool with compose
in my opinion 😃
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.
As discussed with @youknowriad on Slack, I'm happy to replace all flowRight
occurrences in the existing codebase once this lands.
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 other thought I had was using a higher-order component pattern for composing, like:
withComposition( [
withFoo,
withBar
] )( OriginalComponent );
But I think I like the element-exposed export just as well.
I addressed all comments. The only part left here is to make a final call for the hooks public API. |
I do think it makes the most sense for core to expose hooks on the wp global and to use hooks here as |
No they are not, they can not be because they may have server dependencies and use globals |
They just serve to build the "bundles" corresponding to the WP registered scripts |
It really seems like we're going out of our way adding new concepts (which for other developers' sake should always be done with caution) just to avoid having a singleton instance from the module. |
@aduth Fair point, but honestly I don't think we're adding a new concept, I think we're removing an ambiguity because right now, if you import I'm convinced we'd have this issue more broadly later, but I'm ok postponing if necessary. |
Ok, fine to have a difference of opinion. 😄 My thinking is that we should want the direct mapping of WordPress namespace to WordPress global (the latter exposed in the context of WP only), and not needing to care whether the import resolves to one or the other. |
a61a944
to
67ce3cc
Compare
It's ready for a final check. In the meantime I found an easy way to integrate Another reason why I had to provide a build process for I'd like to merge it tomorrow morning and see how it works with Collaborative Editing. Let me know if you find any blockers. |
|
That would make it |
Hi @gziolo, I did some tests and it looks like this works as specified. 👍 Also, the withFilters HoC is a great addition and will for sure simplify extensibility. I just got a question that made me really curious:
Would not our current extensibility point 'BlockEdit' hook, also allow this? Instead of returning the block editor we can display frozen, right? Also extending in this point we have access to setAttributes prop and other helpers that may be useful. This question is probably trivial and I'm missing something. |
Yes, you are 100% right in the majority of cases. The thing here is we want to hide all toolbars and controls when block will be frozen when in coediting mode. It's going to be wrapped with border explaining which user is editing this block and all events need to be disabled. Another thing is that user might be editing in visual or HTML mode, but we want to display always visual mode. So there is much more going on in that case. I tried to integrate with the existing hooks, but it wasn't enough because I was still able to pick this block when doing multi-select or bring focus on it. It would be a really bad experience if you could combine and transform a few paragraphs into the list when another user is still writing their paragraph. |
@@ -2,6 +2,7 @@ | |||
build | |||
coverage | |||
cypress | |||
hooks |
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.
What is this for?
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 build wp.hooks
file here: hooks/build/index.js
as you discovered. I should mention that explicitly.
@@ -21,6 +21,11 @@ describe( 'block factory', () => { | |||
title: 'block title', | |||
}; | |||
|
|||
beforeAll( () => { | |||
// Load all hooks that modify blocks | |||
require( 'blocks/hooks' ); |
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.
Curious, would this be something we'd want to consider moving into general test setup? Seems easy to overlook when adding new tests.
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, that's a valid question. I don't have an answer yet :)
My concern is that whenever we add this kind of code, it gives me an impression that we are doing integration tests rather than unit tests. Saying that, I'm wondering if creating a new integration tests configuration which loads all blocks and hooks on startup would be a good idea here. We need to come up with a wider strategy for all the different types of testing.
@@ -14,36 +14,22 @@ import matchers from './matchers'; | |||
const hooks = createHooks(); |
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.
Will we drop block-specifics hooks registry in a future PR in favor of the new singleton 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 would make it possible to update block related hooks using both wp.blocks.addFilter
and wp.hooks.addFilter
, which I wanted to avoid. Documentation promotes the former, so that's why we might keep it as it is. I'm open for discussion about this one. It seems not consistent to register block with wp.blocks.registerBlockType
, but update it with wp.hooks.addFilter
. Hope it helps to understand the decision process here.
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 looks like we are dropping block-specific hooks with #3827.
element/index.js
Outdated
* @param {String} wrapperName Wrapper name to prepend to the display name. | ||
* @return {String} Wrapped display name. | ||
*/ | ||
export function wrapperDisplayName( BaseComponent, wrapperName ) { |
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: Something about the function name I'm not a fan of, doesn't really indicate what it's for; wondering if it's useful enough a utility to simply retrieve a usable component name i.e. getDisplayName
and concatenate manually when we need to; otherwise maybe getWrapperDisplayName
?
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.
Let's call it getWrapperDisplayName
and revisit later if necessary.
return memo; | ||
}, {} ), | ||
packageNames.reduce( ( memo, packageName ) => { | ||
memo[ packageName ] = `./node_modules/@wordpress/${ packageName }`; |
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.
Okay, I think I understand the gitignore
change now, if we're creating a custom build for hooks to expose the wp.hooks
global. I think there's an issue though, that it's not whitelisted to be included in the plugin zip (see build-plugin-zip.sh
). I think we have a few of these "list of all top-level folders" scattered throughout the codebase that might need to be audited as well.
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, I will add it to the whitelist. It seems like this is the only place to update.
67ce3cc
to
f5af472
Compare
Let's move on with this one and revisit next week if it works well for us. |
Description
This PR tries to enable another extensibility point that is required to implement Collaborative Editing as a plugin. The changes proposed here will allow to display the frozen mode for the block when it is being externally modified by one of the collaborators.
This PR introduces
withFilter
HOC that would simplify the process of exposing components for extensibility.It also proposes
wrapperDisplayName
utility method that would remove some code duplication from the existing higher-order components.I also moved all hooks and exposed them under-wp.utils.*
namespace. I'm open to suggestions here. Maybewp.editor.*
would be a better choice here, but my rationale here was that I couldn't find any reference to@wordpress/editor
from blocks or components namespace. It seems like no other namespace should depend on the editor. The same applies to the case wherecomponents
would depend onblocks
. That's whyutils
felt like a better compromise. I'm happy to iterate on that.wp.hooks
it is.How Has This Been Tested?
Manually. Added the following to verify that the component gets updated:
If you add this in the JS console, you need to click on any block to refresh rendered blocks.
Screenshots (jpeg or gifs if applicable):
Editor view all
<BlockListBlock />
components wrapped with the dummy text:Checklist: