Skip to content

Commit

Permalink
Plugins: Add scoping functionality to the Plugins API (#27438)
Browse files Browse the repository at this point in the history
* add named plugin areas

* Rename area to scope

* Add validation for the scope setting

* Docs: Add notes in CHANGELOG file for new functionality in Plugins API

* Plugins: Update code comments

Co-authored-by: Grzegorz Ziolkowski <[email protected]>
  • Loading branch information
senadir and gziolo authored Feb 26, 2021
1 parent 9f92d39 commit 645224d
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 34 deletions.
8 changes: 7 additions & 1 deletion packages/plugins/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

## Unreleased

### New Features

- Add optional `settings.scope` field in the `registerPlugin` function ([#27438](https://github.com/WordPress/gutenberg/pull/27438)).
- Add optional `scope` param in the `getPlugins` function([#27438](https://github.com/WordPress/gutenberg/pull/27438)).
- Add optional `scope` prop in the `PluginArea` component ([#27438](https://github.com/WordPress/gutenberg/pull/27438)).

## 2.0.10 (2019-01-03)

## 2.0.9 (2018-11-15)
Expand All @@ -20,4 +26,4 @@

### Breaking Change

- Change how required built-ins are polyfilled with Babel 7 ([#9171](https://github.com/WordPress/gutenberg/pull/9171)). If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods.
- Change how required built-ins are polyfilled with Babel 7 ([#9171](https://github.com/WordPress/gutenberg/pull/9171)). If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods.
14 changes: 10 additions & 4 deletions packages/plugins/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,15 @@ _Returns_

<a name="getPlugins" href="#getPlugins">#</a> **getPlugins**

Returns all registered plugins.
Returns all registered plugins without a scope or for a given scope.

_Parameters_

- _scope_ `[string]`: The scope to be used when rendering inside a plugin area. No scope by default.

_Returns_

- `WPPlugin[]`: Plugin settings.
- `WPPlugin[]`: The list of plugins without a scope or for a given scope.

<a name="PluginArea" href="#PluginArea">#</a> **PluginArea**

Expand All @@ -50,7 +54,7 @@ var PluginArea = wp.plugins.PluginArea;
function Layout() {
return el(
'div',
{},
{ scope: 'my-page' },
'Content of the page',
PluginArea
);
Expand All @@ -64,7 +68,7 @@ import { PluginArea } from '@wordpress/plugins';
const Layout = () => (
<div>
Content of the page
<PluginArea />
<PluginArea scope="my-page" />
</div>
);
```
Expand Down Expand Up @@ -112,6 +116,7 @@ function Component() {
registerPlugin( 'plugin-name', {
icon: moreIcon,
render: Component,
scope: 'my-page',
} );
```

Expand Down Expand Up @@ -140,6 +145,7 @@ const Component = () => (
registerPlugin( 'plugin-name', {
icon: more,
render: Component,
scope: 'my-page',
} );
```

Expand Down
56 changes: 40 additions & 16 deletions packages/plugins/src/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,16 @@ import { isFunction } from 'lodash';
*
* @typedef {Object} WPPlugin
*
* @property {string} name A string identifying the plugin. Must be
* unique across all registered plugins.
* unique across all registered plugins.
* @property {string|WPElement|Function} icon An icon to be shown in the UI. It can
* be a slug of the Dashicon, or an element
* (or function returning an element) if you
* choose to render your own SVG.
* @property {Function} render A component containing the UI elements
* to be rendered.
* @property {string} name A string identifying the plugin. Must be
* unique across all registered plugins.
* @property {string|WPElement|Function} [icon] An icon to be shown in the UI. It can
* be a slug of the Dashicon, or an element
* (or function returning an element) if you
* choose to render your own SVG.
* @property {Function} render A component containing the UI elements
* to be rendered.
* @property {string} [scope] The optional scope to be used when rendering inside
* a plugin area. No scope by default.
*/

/**
Expand Down Expand Up @@ -75,6 +76,7 @@ const plugins = {};
* registerPlugin( 'plugin-name', {
* icon: moreIcon,
* render: Component,
* scope: 'my-page',
* } );
* ```
*
Expand Down Expand Up @@ -104,6 +106,7 @@ const plugins = {};
* registerPlugin( 'plugin-name', {
* icon: more,
* render: Component,
* scope: 'my-page',
* } );
* ```
*
Expand All @@ -115,12 +118,12 @@ export function registerPlugin( name, settings ) {
return null;
}
if ( typeof name !== 'string' ) {
console.error( 'Plugin names must be strings.' );
console.error( 'Plugin name must be string.' );
return null;
}
if ( ! /^[a-z][a-z0-9-]*$/.test( name ) ) {
console.error(
'Plugin names must include only lowercase alphanumeric characters or dashes, and start with a letter. Example: "my-plugin".'
'Plugin name must include only lowercase alphanumeric characters or dashes, and start with a letter. Example: "my-plugin".'
);
return null;
}
Expand All @@ -130,13 +133,29 @@ export function registerPlugin( name, settings ) {

settings = applyFilters( 'plugins.registerPlugin', settings, name );

if ( ! isFunction( settings.render ) ) {
const { render, scope } = settings;

if ( ! isFunction( render ) ) {
console.error(
'The "render" property must be specified and must be a valid function.'
);
return null;
}

if ( scope ) {
if ( typeof scope !== 'string' ) {
console.error( 'Plugin scope must be string.' );
return null;
}

if ( ! /^[a-z][a-z0-9-]*$/.test( scope ) ) {
console.error(
'Plugin scope must include only lowercase alphanumeric characters or dashes, and start with a letter. Example: "my-page".'
);
return null;
}
}

plugins[ name ] = {
name,
icon: pluginsIcon,
Expand Down Expand Up @@ -197,10 +216,15 @@ export function getPlugin( name ) {
}

/**
* Returns all registered plugins.
* Returns all registered plugins without a scope or for a given scope.
*
* @param {string} [scope] The scope to be used when rendering inside
* a plugin area. No scope by default.
*
* @return {WPPlugin[]} Plugin settings.
* @return {WPPlugin[]} The list of plugins without a scope or for a given scope.
*/
export function getPlugins() {
return Object.values( plugins );
export function getPlugins( scope ) {
return Object.values( plugins ).filter(
( plugin ) => plugin.scope === scope
);
}
56 changes: 54 additions & 2 deletions packages/plugins/src/api/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ describe( 'registerPlugin', () => {
render: () => {},
} );
expect( console ).toHaveErroredWith(
'Plugin names must include only lowercase alphanumeric characters or dashes, and start with a letter. Example: "my-plugin".'
'Plugin name must include only lowercase alphanumeric characters or dashes, and start with a letter. Example: "my-plugin".'
);
} );

Expand All @@ -48,7 +48,7 @@ describe( 'registerPlugin', () => {
render: () => {},
}
);
expect( console ).toHaveErroredWith( 'Plugin names must be strings.' );
expect( console ).toHaveErroredWith( 'Plugin name must be string.' );
} );

it( 'fails to register a plugin without a render function', () => {
Expand All @@ -58,6 +58,24 @@ describe( 'registerPlugin', () => {
);
} );

it( 'fails to register a plugin with a non-string scope', () => {
registerPlugin( 'my-plugin', {
render: () => {},
scope: {},
} );
expect( console ).toHaveErroredWith( 'Plugin scope must be string.' );
} );

it( 'fails to register a plugin with special character in the scope', () => {
registerPlugin( 'my-plugin', {
render: () => {},
scope: 'special/characters!',
} );
expect( console ).toHaveErroredWith(
'Plugin scope must include only lowercase alphanumeric characters or dashes, and start with a letter. Example: "my-page".'
);
} );

it( 'fails to register a plugin that was already been registered', () => {
registerPlugin( 'plugin', {
render: () => 'plugin content',
Expand All @@ -70,3 +88,37 @@ describe( 'registerPlugin', () => {
);
} );
} );

describe( 'getPlugins', () => {
const scope = 'my-page';

beforeAll( () => {
const Component = () => 'plugin content';
const icon = 'smiley';

registerPlugin( 'unscoped', {
render: Component,
icon,
} );
registerPlugin( 'scoped', {
render: Component,
icon,
scope,
} );
} );

afterAll( () => {
unregisterPlugin( 'unscoped' );
unregisterPlugin( 'scoped' );
} );

it( 'returns all unscoped plugins', () => {
expect( getPlugins() ).toHaveLength( 1 );
} );

it( 'returns all plugins of a given scope', () => {
const scopedPlugins = getPlugins( scope );
expect( scopedPlugins ).toHaveLength( 1 );
expect( scopedPlugins[ 0 ].name ).toBe( 'scoped' );
} );
} );
25 changes: 14 additions & 11 deletions packages/plugins/src/components/plugin-area/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { getPlugins } from '../../api';
* function Layout() {
* return el(
* 'div',
* {},
* { scope: 'my-page' },
* 'Content of the page',
* PluginArea
* );
Expand All @@ -42,7 +42,7 @@ import { getPlugins } from '../../api';
* const Layout = () => (
* <div>
* Content of the page
* <PluginArea />
* <PluginArea scope="my-page" />
* </div>
* );
* ```
Expand All @@ -59,15 +59,18 @@ class PluginArea extends Component {

getCurrentPluginsState() {
return {
plugins: map( getPlugins(), ( { icon, name, render } ) => {
return {
Plugin: render,
context: {
name,
icon,
},
};
} ),
plugins: map(
getPlugins( this.props.scope ),
( { icon, name, render } ) => {
return {
Plugin: render,
context: {
name,
icon,
},
};
}
),
};
}

Expand Down

0 comments on commit 645224d

Please sign in to comment.