-
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
Add an API to add a plugin sidebar #4109
Conversation
Thanks for working on this @atimmer this is something we know we want as an extension point in Gutenberg :) Here are some quick remarks without going too deep into the code and testing yet.
Great to see a PR for this |
Niiice work! I was able to hackily test by checking out the Yoast origin and branch, then pasting this to my console:
It works as advertised! Very very very cool, thank you for working on this. A few thoughts: As this API exists now, it's kind of between two chairs. It's not quite the sidebar system envisioned in #3330 as it replaces the contents of the Settings sidebar, as opposed to create a new sidebar that can co-exist alongside it. But it's also not quite able to inject a custom metabox in the post settings sidebar, as it just replaces all of the existing ones. However, it does feel like this lays down the skeleton to potentially accomplish both. Is that a correct interpretation? Aside from that, I'd echo Riad's thoughts — it'd be really nice to see the API mature into being able to register an entirely new sidebar. But slick work so far! Lazy sidenote: I always fumble when I check out a branch on a fork, my Git-Fu is just weak — is it possible to branch off of master? 😇 — I know, I'm lazy, and it's okay if you prefer to work in the fork, we're still grateful for the contributions. |
@jasmussen I don't have access to push to the main Gutenberg repo 😉 |
@jasmussen @youknowriad I didn't notice that it should be a separate sidebar. I will change the PR to make it a separate sidebar. This definitely lays down a skeleton to start allowing all sorts of registrations. |
Man, we gotta address that. |
🌟 Definitely let me know if any of the mockups are unclear!
🎉 |
@atimmer great work exploring how we can add a new item to the ellipsis menu and replace Advanced settings in the sidebar. I second what @youknowriad said, this is what we want from the UX perspective, but it needs some further thinking in terms of underlying technical implementation. We need to make sure we expose API that is going to work even when we decide to move the sidebar from the editor module or when we decide to convert sidebar to a dialog or something else.
I would be in favor of using |
@youknowriadWhy wouldn't sidebar be the correct term? This is the same term that Gutenberg uses internally. Another term we could use is [1]
I have no objections to the move towards Another topic that makes me think of is exploring programmable API discoverability. In a way doing: I will address the other feedback using code changes once #4119 is merged. @gzioloIf we ever (re)move the sidebar, we deprecate the API. Having more semantic APIs makes it much more clear to people when something is not used/removed. Having this API just not do anything in the customizer shouldn't be a problem. Or it could even be rendered in the left sidebar, but I digress. About the filter part. In this current iteration, a call to
These could also be 3 filters. But then the whole "opinionated default to prevent plugin authors from having to invent an interaction model for their plugin" is not there anymore. I think we should also have 3 filters in all of these 3 locations. But that is a low-level API we shouldn't require most people to use. Error messages like |
@@ -28,6 +29,8 @@ const element = ( | |||
<ModeSwitcher onSelect={ onClose } /> | |||
<div className="editor-ellipsis-menu__separator" /> | |||
<FixedToolbarToggle onToggle={ onClose } /> | |||
<div className="editor-ellipsis-menu__separator" /> | |||
<Plugins onToggle={ onClose } /> |
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.
* @param {Object} props Props. | ||
* @param {Function} props.onSwitch Function to call when a plugin is | ||
* switched to. | ||
* @param {string} props.activePanel The currently active panel. |
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.
Even though the JSDoc documentation differentiates with lower and uppercase differences, and I'd previously made an attempt to respect them, I think we ought to just standardize on always uppercasing, to avoid needing to consistently consult with the docs (or more likely, do it wrong).
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 could be a good discussion to have in #core-js. Tentatively I would say that keeping primitives lower case has value for a parser. This is consistent with PHP, where primitives are always lowercase and objects are always spelled the way they are defined in the class
statement.
Doesn't block this PR, I think.
* Internal dependencies | ||
*/ | ||
import { getSidebars, activateSidebar } from '../../../api/sidebar'; | ||
import { MenuItemsGroup } from '../../../../components'; |
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 should not be reaching into other top-level directories. This should be @wordpress/components
(and moved under "WordPress dependencies" comment)
import { getSidebars, activateSidebar } from '../../../api/sidebar'; | ||
import { MenuItemsGroup } from '../../../../components'; | ||
import { getActivePanel, isEditorSidebarOpened } from '../../../store/selectors'; | ||
import { connect } from 'react-redux'; |
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 should be moved under "External dependencies"
editor/api/sidebar.js
Outdated
); | ||
} | ||
|
||
if ( ! ( 'title' in settings ) || settings.title === '' ) { |
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.
Could this be simplified to if ( ! settings.title ) {
?
editor/api/sidebar.js
Outdated
* | ||
* @param {string} name The name of the sidebar to retrieve. | ||
* | ||
* @returns {false|Object} The settings object of the sidebar. Or false if the |
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 "return false
if not exists" pattern never quite made sense to me in the context of JavaScript, since we already have two concepts to represent an empty/unset value: null
and undefined
. I'd rather we return one of these instead, preferably null
since we already do this in a number of selectors and it semantically better represents the "explicitly empty" use-case (vs. unset).
Aside: If we did return false, the return value type would be Boolean.
* @param {string} settings.title The name to show in the settings menu. | ||
* @param {Function} settings.render The function that renders the sidebar. | ||
* | ||
* @returns {Object} The final sidebar settings object. |
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.
TIL @returns
and @return
are equally valid and equivalent JSDoc tags. Noting that we've conventionally used @return
elsewhere.
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.
In the WordPress JavaScript documentation standards we go with @returns
.
…add/api-add-plugin-sidebar
editor/api/sidebar.js
Outdated
} | ||
if ( ! /^[a-z][a-z0-9-]*\/[a-z][a-z0-9-]*$/.test( name ) ) { | ||
console.error( | ||
'Sidebar names must contain a namespace prefix, include only lowercase alphanumeric characters or dashes, and start with a letter. Example: my-plugin/my-custom-sidebar' |
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.
Missing period
editor/api/sidebar.js
Outdated
* | ||
* @param {string} name The name of the sidebar to retrieve. | ||
* | ||
* @returns {Object} The settings object of the sidebar. Or false if the |
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 null (not false)
editor/api/sidebar.js
Outdated
} | ||
|
||
/** | ||
* Activates the gives sidebar. |
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 given sidebar
editor/api/sidebar.js
Outdated
if ( sidebars[ name ] ) { | ||
console.error( | ||
'Sidebar "' + name + '" is already registered.' | ||
); |
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.
Missing return
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 might be intentional to overwrite a plugin?
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: Normally we avoid + operator to concatenate strings Maybe we can use Sidebar "${ name }" is already registered.
.
editor/store/actions.js
Outdated
* @param {String} plugin The plugin name | ||
* @return {Object} Action object | ||
*/ | ||
export function setActivePlugin( plugin ) { |
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.
setActive(Plugin)Sidebar might be a better name, because a plugin can have multiple sidebars.
editor/store/selectors.js
Outdated
@@ -97,6 +97,16 @@ export function getActivePanel( state ) { | |||
return state.panel; | |||
} | |||
|
|||
/** | |||
* Returns the current active plugin for the plugin sidebar. |
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.
See above: a plugin can have multiple sidebars
return () => { | ||
return <Panel> | ||
<PanelBody> | ||
{ sprintf( __( 'No matching plugin sidebar found for plugin "%s"' ), plugin ) } |
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.
Please add translator comment
The issue is that this API is to use case specific. This PR addresses only the sidebar
It's explained in depth in #3330. Screens might help here, too: That's why we would rather see the public API closer to: wp.editor.registerPlugin( {
title: "My Plugin",
icon: svg | img,
render () {
return (
<SidebarPanel>
<div>My Stuff...</div>
</SidebarPanel>
);
}
}); Please also note that in this example we use |
@gziolo I'm not fond of the idea to determine where the component is rendered based on what component you choose to use. The reason for this is that the plugin has to be rendered in the same place, regardless of the type of plugin. If we were to go with a single |
This is not exactly what I proposed.
This approach is used in a few places in Gutenberg. One of the examples is block's inspector controls. They are included in the In my opinion, this is more flexible approach and allows us to skip the definition of the plugin type. In practice, you could even dynamically change plugin type based on the data that might be fetched from elsewhere. |
@gziolo Ah, I wasn't aware of the design pattern, thank you for elaborating. For now, I'll keep working on the PR as is, and the plugin API can be revised at a later date. This is something that should be discussed in the Gutenberg chat. |
This PR is ready for review @youknowriad @gziolo @aduth @jasmussen @jorgefilipecosta Some remarks:
|
Took this for a quick spin, and it looks like it's going to work really well, nice work! There are a couple of ToDo's for this feature, but probably none that have to happen in this PR — it seems like it would good to wrap this up, merge it in, and then ticket these improvements separately. Including:
Nice work! |
@jasmussen Quick consideration about pinning (I'd agree it should go in a separate issue). Have you considered what users will typically do?: (I've used random icons, just to get an idea) Lots of plugins: lots of pinned icons. This should be carefully considered, as it poses usability and accessibility issues.I'd tend to think the pinned icons should have their own separate area somewhere. |
Yes. And you're exactly right — your concept is not unlikely to happen in some situations, and the designer in me screams inside at the thought. But at the same time, I would place this in the same category as top level plugin menus — we want to allow them because they can bring huge value to plugin authors. But by not pinning these things by default, requiring the user to actively open the plugin from the ellipsis menu and then opting in by explicitly pressing the pin button, hopefully we are not only teaching the user how they can remove those icons again (untoggle the pin), but we may also be limiting the amount of icons there. At this particular stage in the process, this flow is inspired by how Chrome and Firefox do with their extensions. We believe it will work for us, but we also want to be open to adjusting our assumptions as we try this in practice. |
@jasmussen Thanks for your feedback. I agree merging this PR and iterate over it is the way to go. Btw, the icons and pinning are part of the ellipsis menu PR that @xyfi has been working on. I'm not sure what's the status of that PR. Edit: ah, here it is: #4484 |
Sorry for the conflicts, If you have trouble rebasing this, feel free to ask me. (We moved |
Hey @IreneStr If you've got time I was wondering when using the wp.editor.renderSidebar( "plugin-namespace/name", {
'title': "Name",
render: () => {
return (
<div>
My Sidebar
</div>
)
}
} ); Does this mean you're meant to call Thanks for your time. |
@jasonagnew My apologies for my late reply. Using the current structure, we're trying to work towards uniform APIs for opening a sidebar, doing a screen takeover, etc. However, you're correct that reloading with an open plugin sidebar will cause an error at this moment. That's one of the issues that still needs to be addressed. |
This PR has been replaced by #4777 |
Closed in favor of #4777 |
Description
This adds an API that plugins can use to add sidebars.
The API looks like this:
The screen takeover that is mentioned in #3330 can be implemented in a very similar way. The plugins menu should then probably be sorted alphabetically. And it should have both registered sidebars as registered screens.
How Has This Been Tested?
I've created a PR on Yoast SEO to test this with. Yoast/wordpress-seo#8531 shows how the API can be used.
Screenshots (jpeg or gifs if applicable):
Types of changes
Checklist: