-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Add new function to register a new block category #1732
Conversation
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.
100% support he need for devs to be able to add categories.
This doesn't allow any control over order though - I wonder what other approaches might allow for this?
It probably also makes sense to be able to remove a category - I'm not sure what should happen to any blocks in that category though?
yes. you are right. a solution could be introducing an "order" property (int value) to the category and using that to set the order of rendering.
No problem adding a new function to remove an existing category but the only issue I can see at the moment is that all the blocks filed under that category would disappear. Is that the right way of proceeding? That's how it works with WP metaboxes (e.g if we want to remove a specific metabox from wordpress UI). The alternative is to refuse to delete the category until it was empty or to display all the bocks under a generic "No category" label. Any thoughts? |
I've added two new function. Cheers, Antonio |
blocks/api/test/categories.js
Outdated
/** | ||
* External dependencies | ||
*/ | ||
import { __ } from 'i18n'; |
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.
Note with #2172, these need to be updated to prefix the dependencies with @wordpress/
. You will need to perform a rebase against the latest version of master and apply your changes:
git fetch origin
git rebase origin/master
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.
Done.
Cheers, Antonio
6c0023f
to
2497cd1
Compare
Codecov Report
@@ Coverage Diff @@
## master #1732 +/- ##
==========================================
+ Coverage 38.94% 39.27% +0.33%
==========================================
Files 289 289
Lines 6948 6986 +38
Branches 1276 1286 +10
==========================================
+ Hits 2706 2744 +38
Misses 3566 3566
Partials 676 676
Continue to review full report at Codecov.
|
blocks/api/categories.js
Outdated
* | ||
* @returns {Array} Block categories | ||
*/ | ||
export function sortCategoriesBy( key ) { |
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.
Should this be a mutative function, or should it return a new array? If any UI was to show this list, how and when would it be come updated if the sort changes?
My preference might be toward a getter returning a new array: getSortedCategories( sortProperty )
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 guess the same arguments can be made for registering in general, and they have been: #336)
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. There isn't any update. It's more a getter, so, I will change the function following your advices in the next commit.
blocks/api/categories.js
Outdated
* @return {Array} Block categories | ||
* | ||
*/ | ||
export function registerCategory( cat ) { |
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: Abbreviations are overrated in my opinion. Don't let a few characters dissuade you from clarity, especially since it's all minified in the end anyways.
Cats are for meowing 😸
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.
Hi, sorry for the delay. I was on holiday. Right, Let's use the full-lenght definition. Moreover, I'm also allergic so better don’t bother cats for this purpose. :)
blocks/api/categories.js
Outdated
* @returns {Array} Block categories | ||
*/ | ||
export function setCategoryOrder( slug, order ) { | ||
const pos = findIndex( categories, ( category ) => category.slug === slug ); |
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.
Lodash's _.find
might be closer to what you want, since you don't really care about the index aside from referencing the matched object itself.
blocks/api/categories.js
Outdated
* | ||
* @returns {Array} Block categories | ||
*/ | ||
export function setCategoryOrder( slug, order ) { |
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 we need the ability to change order after-the-fact like this?
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 function could be helpful to re-arrange the existing and new block categories. It should be used before the getter (getSortedCategories).
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.
A few documentation and lingo issues.
blocks/api/categories.js
Outdated
* Register a new block category | ||
* | ||
* @param {Array} category e.g {slug: 'custom', title: __('Custom Blocks')} | ||
* |
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.
Consistent Documentation: Remove this empty line.
blocks/api/categories.js
Outdated
* @param {Array} category e.g {slug: 'custom', title: __('Custom Blocks')} | ||
* | ||
* @return {Array} Block categories | ||
* |
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.
Consistent Documentation: Remove this empty line.
blocks/api/categories.js
Outdated
@@ -27,3 +30,105 @@ const categories = [ | |||
export function getCategories() { | |||
return categories; | |||
} | |||
|
|||
/** | |||
* Register a new block category |
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.
Consistent Documentation: Add a period at the end.
blocks/api/categories.js
Outdated
export function registerCategory( category ) { | ||
if ( ! category ) { | ||
console.error( | ||
'The Block category must be defined' |
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 block Category
must be defined'
Having
b
lowercase forblock
seems right.C
upercase forCategory
seems right.
Maybe change that everywhere?
Hi @ahmadawais, |
blocks/api/categories.js
Outdated
@@ -11,7 +14,7 @@ import { __ } from '@wordpress/i18n'; | |||
* | |||
* @var {Array} categories | |||
*/ | |||
const categories = [ | |||
let categories = [ |
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.
note that there's no need to change this to a let
just to allow for push()
and splice()
and sort()
- those are all mutating operations. not a big deal, but just pointing out that we don't need to introduce this change
blocks/api/categories.js
Outdated
); | ||
return; | ||
} | ||
if ( ! /^[a-z0-9-]+$/.test( category.slug ) ) { |
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.
If we're going to define a rule for what category names can be it could be nice to make it explicit somewhere where others can find it without having to dive deep into the code: maybe a constant in the top of the module scope?
/**
* @type {RegExp}
* @const
*
* Category names must be a combination of lower-case letters, numbers, and hypens
*/
const categoryNamePattern = /^[a-z0-9-]+$/
// or…
// ^[a-z] - must start with a letter
// (?: … )+ - any number of the following (non-capture because it's all part of a whole name)
// -* - zero or more hyphens…
// [a-z0-9] - …followed by a letter or number
//
// examples…
// 'cats', 'dogs', 'eat-a-burger', 'editor9', 'a---------pause'
//
// counter-examples…
// 'ae-', '-equi', '4u'
const categoryNamePattern = /^[a-z](?:-*[a-z0-9])+$/
blocks/api/categories.js
Outdated
); | ||
return; | ||
} | ||
if ( categories.find( x => x.slug === category.slug ) ) { |
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.
note that Array.prototype.some()
here might have a more semantic match here with what we're checking: is there any item in this collection which matches the given predicate function?
blocks/api/categories.js
Outdated
); | ||
return; | ||
} | ||
categories = sortBy( categories, sortProperty ); |
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 want to verify here that this sort sorts as expected. JavaScript's default sort doesn't always produce the results we want, but when comparing strings we are thankfully given a function to make it more reliable…
const byGivenProperty = ( a, b ) => a[ sortProperty ].localeCompare( b[ sortProperty ] );
categories.sort( byGivenProperty )
Also of note is that the built-in sort in JavaScript mutates the original array while sortBy()
returns a new array.
On one hand returning a new array is great because it doesn't mutate the old one. On the other hand, it's probably not a big deal 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.
Hi @dmsnell,
I'm using the loadash sortBy which already handles exceptions.
In this specific case it protects the sorting even if the order hasn't set for the other elements in the categories array. The built-in sort doesn't handle that exception.
Antonio
Nice work @kuoko - I don't know how to evaluate the broader role this code would provide but I gave some comments on the code at a lower-level. Hope it's useful to you; please feel free to ping me if you have comments or questions about it! |
d751a55
to
8dd16e6
Compare
@kuoko I don't know if you intended to remove the |
8dd16e6
to
ed3567c
Compare
Hi @dmsnell, |
@kuoko Need any help with this PR? I was looking for this exact functionality for Gutenberg earlier today. |
HI @travislopes, Thanks a lot, |
6d5442d
to
c78d533
Compare
|
||
categories.push( category ); | ||
return categories; | ||
} |
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.
oh typing…we either get it in a robust and helpful way or we manually force it in unreliable and costly ways…
(this is not a comment on the code submission, but just me noting a place where having static typing could save us so much effort and provide us so much help)
blocks/api/categories.js
Outdated
|
||
const sortedCategories = sortBy( categories, sortProperty ); | ||
return sortedCategories; | ||
} |
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 this function even used? I didn't see any usage other than in the tests, plus, it's not really doing much
is there a reason we would prefer to call this over directly calling sortBy()
?
I would imagine that this function probably isn't needed and we could strip it out
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.
@dmsnell Yes. The function is even more useless considering that we are not able to mutate categories. That's the reason why it was a reassignable variable in one of my previous commit. I've tried to keep it simple but I agree with you it's better to have an isolated PR for this.
It needs more discussions.
@kuoko thanks for continuing to iterate on this! I have a few very broad questions at this point, as I had a little more chance to evaluate the PR holistically.
Looking forward to the discussion! This is great stuff you are doing. |
@dmsnell Just commenting on number 2 above. From a plugin standpoint categories for blocks would be extremely useful. I do not think it will be uncommon for plugins to have multiple blocks. Especially more feature rich plugins such as WooCommerce. They will likely have a variety of blocks for different uses. Right now if WooCommerce created a suite of blocks where would they even be housed? Under Common? Format? Layout? Widgets? They would look better under a WooCommerce category grouped together. Being able to extend the categories would greatly aid in discoverability. |
…By to getSortedCategories - Use Lodash's _.find instead of _.findIndex
…, use some function instead of find, getSortedCategories doesn't need to be mutative
36a29c9
to
02a365a
Compare
I've simplified the module. |
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.
Seems reasonable what's here. I have mostly neutral feelings about it. Please let me know if there's some way I can be helpful. 😄
Looks great! Not to distract from this PR which should definitely get merged, but I'm hoping there might be a consideration for a PHP API to register new categories in a future PR. Throwing my two cents out here, registering blocks and categories via PHP is going to be necessary for wide-scale adoption and a successful rollout. |
With regards to the last extensibility work, it looks like the consensus is to avoid adding new |
We are exploring different ways to consolidate attributes definition and consider having a server-side awareness of block types as described in #2751. It is highly possible that verification for category names is going to be moved to PHP when we decide to take that route. There are also a few question we need to answer before we proceed with those changes:
|
@@ -28,3 +38,48 @@ const categories = [ | |||
export function getCategories() { | |||
return categories; | |||
} | |||
|
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's also possible to achieve a very similar result using this code:
import { applyFilters } from '@wordpress/hooks';
let cachedCategories;
export function getCategories() {
if ( ! cachedCategories ) {
const extraCategories = ...applyFilters( 'blocks.getExtraCategories', [] );
// we can perform optonal validation here and filter out all items that don't have slug and name
cachedCategories = [ ...extraCategories, ...categories ];
}
return cachedCategories;
}
Usage:
function addCategory( extraCategories ) {
return [ ...extraCategories, { slug: 'my-category', title: __( 'My category' ) } ];
}
wp.hooks.addFilter( 'blocks.getExtraCategories', 'my-plugin/extra-categories', addCategory );
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.
also, since JavaScript functions are much cheaper than PHP functions we can wrap via composition and keep different logic separate 😄
import { once } from 'lodash'
const getCategories = once( () => [
...applyFilters( 'blocks.getExtraCategories', [] ),
...categories,
] )
and without lodash
it's easy to recreate once
const once = f => {
let isPrimed = false
let value
return ( ...args ) => ! isPrimed
? ( isPrimed = true, value = f( ...args ) )
: 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.
@dmsnell thanks for making it even simpler :)
Any update on this @gziolo @youknowriad @kuoko? We are already encountering the lack of being able to customize, add, and re-order categories within the block inserter as being an issue from a development standpoint. We aren't planning a single block for Gutenberg. In order to adopt Gutenberg full bore and go all in with making our product Gutenberg friendly that is going to involve a suite of blocks. Being able to group those blocks together is going to be very important from a UX standpoint. So this is a PR that we are particularly interested in and can't wait to see merged into Gutenberg core. |
This is on the roadmap for sure. We want to tackle other issues first before handling this one. One of the examples which also fits your description is: Limit Block to Certain Post Type(s) . I don't know what use case you have in particular, but having the ability to pick which blocks are loaded for a given post type seems to be the most requested feature. We are looking how to extend Templates to support also this. This should help to decide how to tackle categories. I already shared my thought about categories in my previous comment: #1732 (comment). There are more things to take into consideration to have it implemented in a consistent way that covers the majority of cases all plugins might want. |
@gziolo Thanks for the update. We are getting ready to release our second Gutenberg block publicly for testing and it's already clear we need a category to group our blocks so not only is the user experience better but it's also easier to document and support. So we are looking forward to this one allowing us to make block discovery much more user friendly. |
@gziolo Hello, is there any progress with this feature? I have a custom blocks for my theme and I would like to distinguish them by category. I haven't found any way how to extend that so it would be great to have a filter for that. |
The internal APIs to support this are already there, we're using a store to keep track of the categories. The store already supports an |
An alternative approach was proposed in #7606. |
Thanks for all work put into this PR, it was very helpful to come up with a final proposal. Let us know if that works. |
Move DependencyGraph.js so the original file is not left over after `yarn bundle`
The new function allows to add new block categories.
I think it's very useful in order to add new blocks and display them under a custom category.
I was inspired by the following ticket: #1352