From 142f7b1e9a3aba21b0a4eee501b8dc148a2afdac Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Fri, 2 Mar 2018 15:56:55 +0100 Subject: [PATCH 01/83] Initial implementation of slot-fill sidebar API --- edit-post/api/index.js | 3 + edit-post/api/plugin.js | 68 ++++++++++++++++++++ edit-post/components/layout/index.js | 2 + edit-post/components/plugin-sidebar/index.js | 51 +++++++++++++++ edit-post/components/plugins-panel/index.js | 3 +- edit-post/index.js | 1 + 6 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 edit-post/api/plugin.js create mode 100644 edit-post/components/plugin-sidebar/index.js diff --git a/edit-post/api/index.js b/edit-post/api/index.js index 3284d43eacacf8..6a3caf47d19374 100644 --- a/edit-post/api/index.js +++ b/edit-post/api/index.js @@ -2,3 +2,6 @@ export { registerSidebar as __experimentalRegisterSidebar, activateSidebar as __experimentalActivateSidebar, } from './sidebar'; +export { + registerPlugin, +} from './plugin'; diff --git a/edit-post/api/plugin.js b/edit-post/api/plugin.js new file mode 100644 index 00000000000000..61a48b0f7264ab --- /dev/null +++ b/edit-post/api/plugin.js @@ -0,0 +1,68 @@ +/* eslint no-console: [ 'error', { allow: [ 'error' ] } ] */ + +/** + * WordPress dependencies + */ +import { applyFilters } from '@wordpress/hooks'; +import { Fragment } from '@wordpress/element'; + +/* External dependencies */ +import { isFunction, map, isEmpty } from 'lodash'; + +/* Internal dependencies */ +// import store from '../store'; +// import { setGeneralSidebarActivePanel, openGeneralSidebar } from '../store/actions'; + +const plugins = {}; + +/** + * Registers a plugin to the editor. + * + * @param {Object} settings The settings for this plugin. + * @param {string} settings.name The name of the plugin. + * @param {Function} settings.render The function that renders the plugin. + * + * @return {Object} The final plugin settings object. + */ +export function registerPlugin( settings ) { + settings = applyFilters( 'editPost.registerPlugin', settings, settings.name ); + + if ( typeof settings.name !== 'string' ) { + console.error( + 'Plugin names must be strings.' + ); + return null; + } + if ( ! /^[a-z][a-z0-9-]*$/.test( settings.name ) ) { + console.error( + 'Plugin names must include only lowercase alphanumeric characters or dashes, and start with a letter. Example: "my-plugin".' + ); + return null; + } + if ( plugins[ settings.name ] ) { + console.error( + `Plugin "${ settings.name }" is already registered.` + ); + } + if ( ! settings || ! isFunction( settings.render ) ) { + console.error( + 'The "render" property must be specified and must be a valid function.' + ); + return null; + } + + return plugins[ settings.name ] = settings; +} + +export function Plugins() { + return ( +
+ + { map( plugins, plugin => { + console.log( plugin.render() ); + return plugin.render(); + } ) } + +
+ ); +} diff --git a/edit-post/components/layout/index.js b/edit-post/components/layout/index.js index e1daf676225092..7e0b049a2b104a 100644 --- a/edit-post/components/layout/index.js +++ b/edit-post/components/layout/index.js @@ -41,6 +41,7 @@ import { import { closePublishSidebar } from '../../store/actions'; import PluginsPanel from '../../components/plugins-panel/index.js'; import { getSidebarSettings } from '../../api/sidebar'; +import { Plugins } from '../../api/plugin'; function GeneralSidebar( { openedGeneralSidebar } ) { switch ( openedGeneralSidebar ) { @@ -101,6 +102,7 @@ function Layout( { openedGeneralSidebar={ openedGeneralSidebar } /> } + ); } diff --git a/edit-post/components/plugin-sidebar/index.js b/edit-post/components/plugin-sidebar/index.js new file mode 100644 index 00000000000000..b6399b688df52e --- /dev/null +++ b/edit-post/components/plugin-sidebar/index.js @@ -0,0 +1,51 @@ +/** + * External dependencies + */ +import { noop } from 'lodash'; +import { connect } from 'react-redux'; + +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; +import { Slot, Fill } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import { getActivePlugin } from '../../store/selectors'; + +/** + * Name of slot in which popover should fill. + * + * @type {String} + */ +const SLOT_NAME = 'PluginSidebar'; + +class PluginSidebar extends Component { + constructor( props ) { + super( props ); + + if ( typeof props.name !== 'string' ) { + throw 'Sidebar names must be strings.'; + } + if ( ! /^[a-z][a-z0-9-]*$/.test( props.name ) ) { + throw 'Sidebar names must include only lowercase alphanumeric characters or dashes, and start with a letter. Example: "my-sidebar".'; + } + } + + render() { + const { title, name, children, activePlugin } = this.props; + return { children }; + } +} + +PluginSidebar.contextTypes = { + getSlot: noop, +}; + +PluginSidebar.Slot = () => ; + +export default connect( ( state ) => ( { + activePlugins: getActivePlugin( state ), +} ) )( PluginSidebar ); diff --git a/edit-post/components/plugins-panel/index.js b/edit-post/components/plugins-panel/index.js index 57be1622457e96..38d84559e4652c 100644 --- a/edit-post/components/plugins-panel/index.js +++ b/edit-post/components/plugins-panel/index.js @@ -16,6 +16,7 @@ import './style.scss'; import { getSidebarSettings } from '../../api/sidebar'; import { getActivePlugin } from '../../store/selectors'; import { closeGeneralSidebar } from '../../store/actions'; +import PluginSidebar from '../plugin-sidebar'; function PluginsPanel( { onClose, plugin } ) { const pluginSidebar = getSidebarSettings( plugin ); @@ -44,7 +45,7 @@ function PluginsPanel( { onClose, plugin } ) { />
- { render() } +
); diff --git a/edit-post/index.js b/edit-post/index.js index ef9bf85f6463f6..1f4ba5fe5085fb 100644 --- a/edit-post/index.js +++ b/edit-post/index.js @@ -21,6 +21,7 @@ import Layout from './components/layout'; import store from './store'; import { initializeMetaBoxState } from './store/actions'; +export { default as PluginSidebar } from './components/plugin-sidebar'; export * from './api'; // Configure moment globally From 65af7e64f1e68ec1a5f82c8752ddcbbc91e99f29 Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Tue, 6 Mar 2018 10:16:16 +0100 Subject: [PATCH 02/83] Attempt to implement createContext api for plugin slot fill API --- edit-post/api/plugin.js | 51 +++++++++++++++----- edit-post/components/plugin-sidebar/index.js | 6 ++- element/index.js | 3 ++ package.json | 1 + 4 files changed, 48 insertions(+), 13 deletions(-) diff --git a/edit-post/api/plugin.js b/edit-post/api/plugin.js index 61a48b0f7264ab..630713cc085c41 100644 --- a/edit-post/api/plugin.js +++ b/edit-post/api/plugin.js @@ -4,10 +4,10 @@ * WordPress dependencies */ import { applyFilters } from '@wordpress/hooks'; -import { Fragment } from '@wordpress/element'; +import { createContext, Component } from '@wordpress/element'; /* External dependencies */ -import { isFunction, map, isEmpty } from 'lodash'; +import { isFunction, map, noop } from 'lodash'; /* Internal dependencies */ // import store from '../store'; @@ -51,18 +51,47 @@ export function registerPlugin( settings ) { return null; } + plugins.context = createContext(); + return plugins[ settings.name ] = settings; } -export function Plugins() { - return ( -
- +class ContextProvider extends Component { + getChildContext() { + return { + plugin: this.props.plugin, + }; + } + + render() { + return this.props.children; + } +} + +ContextProvider.childContextTypes = { + plugin: noop, +}; + +class Plugins extends Component { + render() { + return ( +
{ map( plugins, plugin => { - console.log( plugin.render() ); - return plugin.render(); + const Context = plugin.context; + return ( + + { plugin.render() } + + ); + // return ( + // + // { plugin.render() } + // + // ); } ) } - -
- ); +
+ ); + } } + +export { Plugins }; diff --git a/edit-post/components/plugin-sidebar/index.js b/edit-post/components/plugin-sidebar/index.js index b6399b688df52e..c115c85f978e57 100644 --- a/edit-post/components/plugin-sidebar/index.js +++ b/edit-post/components/plugin-sidebar/index.js @@ -22,6 +22,8 @@ import { getActivePlugin } from '../../store/selectors'; */ const SLOT_NAME = 'PluginSidebar'; +//TODO Error boundaries + class PluginSidebar extends Component { constructor( props ) { super( props ); @@ -35,13 +37,13 @@ class PluginSidebar extends Component { } render() { - const { title, name, children, activePlugin } = this.props; + const { children } = this.props; return { children }; } } PluginSidebar.contextTypes = { - getSlot: noop, + plugin: noop, }; PluginSidebar.Slot = () => ; diff --git a/element/index.js b/element/index.js index e35c715b077386..17ee2f7c7c6adb 100644 --- a/element/index.js +++ b/element/index.js @@ -11,6 +11,7 @@ import { upperFirst, isEmpty, } from 'lodash'; +import createContext from 'create-react-context'; /** * Returns a new element of given type. Type can be either a string tag name or @@ -26,6 +27,8 @@ import { */ export { createElement }; +export { createContext }; + /** * Renders a given element into the target DOM node. * diff --git a/package.json b/package.json index d2fcd1e37b8328..f5244724e477e8 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@wordpress/url": "1.0.3", "classnames": "2.2.5", "clipboard": "1.7.1", + "create-react-context": "0.2.1", "dom-react": "2.2.0", "dom-scroll-into-view": "1.2.1", "element-closest": "2.0.2", From 0e921fef03aa39cda74a8529ef4fdd5489c5a2c1 Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Tue, 6 Mar 2018 10:59:35 +0100 Subject: [PATCH 03/83] Fixed error on start up --- edit-post/api/plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edit-post/api/plugin.js b/edit-post/api/plugin.js index 630713cc085c41..3ba2fd61d2ef61 100644 --- a/edit-post/api/plugin.js +++ b/edit-post/api/plugin.js @@ -51,7 +51,7 @@ export function registerPlugin( settings ) { return null; } - plugins.context = createContext(); + settings.context = createContext(); return plugins[ settings.name ] = settings; } From 2b022f7e0c52b704751ad084828d14ac32fb4544 Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Tue, 6 Mar 2018 14:13:20 +0100 Subject: [PATCH 04/83] Implemented way to pass props from plugin API via context to PluginSidebar component --- edit-post/api/plugin.js | 21 ++++------ edit-post/components/plugin-sidebar/index.js | 42 ++++++++++++++++---- 2 files changed, 42 insertions(+), 21 deletions(-) diff --git a/edit-post/api/plugin.js b/edit-post/api/plugin.js index 3ba2fd61d2ef61..908e8b8bce16ec 100644 --- a/edit-post/api/plugin.js +++ b/edit-post/api/plugin.js @@ -4,10 +4,11 @@ * WordPress dependencies */ import { applyFilters } from '@wordpress/hooks'; -import { createContext, Component } from '@wordpress/element'; +import { Component } from '@wordpress/element'; /* External dependencies */ -import { isFunction, map, noop } from 'lodash'; +import { isFunction, map } from 'lodash'; +import PropTypes from 'prop-types'; /* Internal dependencies */ // import store from '../store'; @@ -51,15 +52,13 @@ export function registerPlugin( settings ) { return null; } - settings.context = createContext(); - return plugins[ settings.name ] = settings; } class ContextProvider extends Component { getChildContext() { return { - plugin: this.props.plugin, + namespace: this.props.namespace, }; } @@ -69,7 +68,7 @@ class ContextProvider extends Component { } ContextProvider.childContextTypes = { - plugin: noop, + namespace: PropTypes.string.isRequired, }; class Plugins extends Component { @@ -77,17 +76,11 @@ class Plugins extends Component { return (
{ map( plugins, plugin => { - const Context = plugin.context; return ( - + { plugin.render() } - + ); - // return ( - // - // { plugin.render() } - // - // ); } ) }
); diff --git a/edit-post/components/plugin-sidebar/index.js b/edit-post/components/plugin-sidebar/index.js index c115c85f978e57..8b95c063fc703b 100644 --- a/edit-post/components/plugin-sidebar/index.js +++ b/edit-post/components/plugin-sidebar/index.js @@ -1,14 +1,15 @@ /** * External dependencies */ -import { noop } from 'lodash'; import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; /** * WordPress dependencies */ -import { Component } from '@wordpress/element'; +import { Component, Children, cloneElement } from '@wordpress/element'; import { Slot, Fill } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies @@ -22,7 +23,25 @@ import { getActivePlugin } from '../../store/selectors'; */ const SLOT_NAME = 'PluginSidebar'; -//TODO Error boundaries +class SidebarErrorBoundary extends Component { + constructor( props ) { + super( props ); + this.state = { hasError: false }; + } + + componentDidCatch() { + this.setState( { hasError: true } ); + } + + render() { + if ( this.state.hasError ) { + return

+ { __( 'An error occurred rendering the plugin sidebar.' ) } +

; + } + return this.props.children; + } +} class PluginSidebar extends Component { constructor( props ) { @@ -37,16 +56,25 @@ class PluginSidebar extends Component { } render() { - const { children } = this.props; - return { children }; + const { children, ...props } = this.props; + const newProps = { + ...props, + namespacedName: `${ this.context.namespace }/${ this.props.name }`, + }; + + return ( + + { cloneElement( Children.only( children ), newProps ) } + + ); } } PluginSidebar.contextTypes = { - plugin: noop, + namespace: PropTypes.string.isRequired, }; -PluginSidebar.Slot = () => ; +PluginSidebar.Slot = () => ( ); export default connect( ( state ) => ( { activePlugins: getActivePlugin( state ), From 78623ad5bc34083e705332037adeca6e413c2e07 Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Tue, 6 Mar 2018 16:26:23 +0100 Subject: [PATCH 05/83] Finalize first pass of registerPlugin API --- edit-post/api/index.js | 2 +- edit-post/api/plugin.js | 16 ++++- edit-post/api/sidebar.js | 27 -------- edit-post/components/layout/index.js | 12 +--- edit-post/components/plugin-sidebar/index.js | 54 +++++++++++++--- .../style.scss | 0 edit-post/components/plugins-panel/index.js | 64 ------------------- edit-post/index.js | 2 +- 8 files changed, 64 insertions(+), 113 deletions(-) rename edit-post/components/{plugins-panel => plugin-sidebar}/style.scss (100%) delete mode 100644 edit-post/components/plugins-panel/index.js diff --git a/edit-post/api/index.js b/edit-post/api/index.js index 6a3caf47d19374..93913d8e66a86c 100644 --- a/edit-post/api/index.js +++ b/edit-post/api/index.js @@ -1,7 +1,7 @@ export { registerSidebar as __experimentalRegisterSidebar, - activateSidebar as __experimentalActivateSidebar, } from './sidebar'; export { registerPlugin, + activateSidebar, } from './plugin'; diff --git a/edit-post/api/plugin.js b/edit-post/api/plugin.js index 908e8b8bce16ec..2952ee5960a5bb 100644 --- a/edit-post/api/plugin.js +++ b/edit-post/api/plugin.js @@ -11,8 +11,10 @@ import { isFunction, map } from 'lodash'; import PropTypes from 'prop-types'; /* Internal dependencies */ -// import store from '../store'; -// import { setGeneralSidebarActivePanel, openGeneralSidebar } from '../store/actions'; +import store from '../store'; +import { + openGeneralSidebar, +} from '../store/actions'; const plugins = {}; @@ -87,4 +89,14 @@ class Plugins extends Component { } } +/** + * Activates the given sidebar. + * + * @param {string} name The name of the sidebar to activate. + * @return {void} + */ +export function activateSidebar( name ) { + store.dispatch( openGeneralSidebar( 'plugin', name ) ); +} + export { Plugins }; diff --git a/edit-post/api/sidebar.js b/edit-post/api/sidebar.js index b2d52152d421e2..3226b17f41bff9 100644 --- a/edit-post/api/sidebar.js +++ b/edit-post/api/sidebar.js @@ -4,8 +4,6 @@ import { isFunction } from 'lodash'; /* Internal dependencies */ -import store from '../store'; -import { setGeneralSidebarActivePanel, openGeneralSidebar } from '../store/actions'; import { applyFilters } from '@wordpress/hooks'; const sidebars = {}; @@ -71,28 +69,3 @@ export function registerSidebar( name, settings ) { return sidebars[ name ] = settings; } - -/** - * Retrieves the sidebar settings object. - * - * @param {string} name The name of the sidebar to retrieve the settings for. - * - * @return {Object} The settings object of the sidebar. Or null if the - * sidebar doesn't exist. - */ -export function getSidebarSettings( name ) { - if ( ! sidebars.hasOwnProperty( name ) ) { - return null; - } - return sidebars[ name ]; -} -/** - * Activates the given sidebar. - * - * @param {string} name The name of the sidebar to activate. - * @return {void} - */ -export function activateSidebar( name ) { - store.dispatch( openGeneralSidebar( 'plugin' ) ); - store.dispatch( setGeneralSidebarActivePanel( 'plugin', name ) ); -} diff --git a/edit-post/components/layout/index.js b/edit-post/components/layout/index.js index 7e0b049a2b104a..7f83e121c5b2cb 100644 --- a/edit-post/components/layout/index.js +++ b/edit-post/components/layout/index.js @@ -35,12 +35,10 @@ import { isFeatureActive, getOpenedGeneralSidebar, isPublishSidebarOpened, - getActivePlugin, getMetaBoxes, } from '../../store/selectors'; import { closePublishSidebar } from '../../store/actions'; -import PluginsPanel from '../../components/plugins-panel/index.js'; -import { getSidebarSettings } from '../../api/sidebar'; +import { PluginSidebarSlot } from '../../components/plugin-sidebar/index.js'; import { Plugins } from '../../api/plugin'; function GeneralSidebar( { openedGeneralSidebar } ) { @@ -48,7 +46,7 @@ function GeneralSidebar( { openedGeneralSidebar } ) { case 'editor': return ; case 'plugin': - return ; + return ; default: } return null; @@ -61,13 +59,10 @@ function Layout( { openedGeneralSidebar, hasFixedToolbar, onClosePublishSidebar, - plugin, metaBoxes, } ) { - const isSidebarOpened = layoutHasOpenSidebar && - ( openedGeneralSidebar !== 'plugin' || getSidebarSettings( plugin ) ); const className = classnames( 'edit-post-layout', { - 'is-sidebar-opened': isSidebarOpened, + 'is-sidebar-opened': layoutHasOpenSidebar, 'has-fixed-toolbar': hasFixedToolbar, } ); @@ -114,7 +109,6 @@ export default connect( openedGeneralSidebar: getOpenedGeneralSidebar( state ), publishSidebarOpen: isPublishSidebarOpened( state ), hasFixedToolbar: isFeatureActive( state, 'fixedToolbar' ), - plugin: getActivePlugin( state ), metaBoxes: getMetaBoxes( state ), } ), { diff --git a/edit-post/components/plugin-sidebar/index.js b/edit-post/components/plugin-sidebar/index.js index 8b95c063fc703b..6c34baacc1338c 100644 --- a/edit-post/components/plugin-sidebar/index.js +++ b/edit-post/components/plugin-sidebar/index.js @@ -7,14 +7,16 @@ import PropTypes from 'prop-types'; /** * WordPress dependencies */ -import { Component, Children, cloneElement } from '@wordpress/element'; -import { Slot, Fill } from '@wordpress/components'; +import { Component, Children, cloneElement, compose } from '@wordpress/element'; +import { Slot, Fill, IconButton, withFocusReturn } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import { getActivePlugin } from '../../store/selectors'; +import './style.scss'; +import { getActivePlugin, getOpenedGeneralSidebar } from '../../store/selectors'; +import { closeGeneralSidebar } from '../../store/actions'; /** * Name of slot in which popover should fill. @@ -56,15 +58,38 @@ class PluginSidebar extends Component { } render() { + if ( ! this.namespacedName ) { + this.namespacedName = `${ this.context.namespace }/${ this.props.name }`; + } + if ( this.props.activePlugin !== this.namespacedName ) { + return null; + } + const { children, ...props } = this.props; const newProps = { ...props, - namespacedName: `${ this.context.namespace }/${ this.props.name }`, + namespacedName: this.namespacedName, }; return ( - { cloneElement( Children.only( children ), newProps ) } +
+
+

{ this.props.title }

+ +
+
+ { cloneElement( Children.only( children ), newProps ) } +
+
); } @@ -74,8 +99,19 @@ PluginSidebar.contextTypes = { namespace: PropTypes.string.isRequired, }; -PluginSidebar.Slot = () => ( ); +const PluginSidebarSlot = () => ( ); + +const PluginSidebarFill = compose( [ + connect( + ( state ) => ( { + activePlugin: getActivePlugin( state ), + openedGeneralSidebar: getOpenedGeneralSidebar( state ), + } ), { + onClose: closeGeneralSidebar, + }, + null, + { storeKey: 'edit-post' } ), + withFocusReturn, +] )( PluginSidebar ); -export default connect( ( state ) => ( { - activePlugins: getActivePlugin( state ), -} ) )( PluginSidebar ); +export { PluginSidebarFill, PluginSidebarSlot }; diff --git a/edit-post/components/plugins-panel/style.scss b/edit-post/components/plugin-sidebar/style.scss similarity index 100% rename from edit-post/components/plugins-panel/style.scss rename to edit-post/components/plugin-sidebar/style.scss diff --git a/edit-post/components/plugins-panel/index.js b/edit-post/components/plugins-panel/index.js deleted file mode 100644 index 38d84559e4652c..00000000000000 --- a/edit-post/components/plugins-panel/index.js +++ /dev/null @@ -1,64 +0,0 @@ -/** - * External dependencies - */ -import { connect } from 'react-redux'; - -/** - * WordPress dependencies - */ -import { __ } from '@wordpress/i18n'; -import { IconButton, withFocusReturn } from '@wordpress/components'; - -/** - * Internal Dependencies - */ -import './style.scss'; -import { getSidebarSettings } from '../../api/sidebar'; -import { getActivePlugin } from '../../store/selectors'; -import { closeGeneralSidebar } from '../../store/actions'; -import PluginSidebar from '../plugin-sidebar'; - -function PluginsPanel( { onClose, plugin } ) { - const pluginSidebar = getSidebarSettings( plugin ); - - if ( ! pluginSidebar ) { - return null; - } - - const { - title, - render, - } = pluginSidebar; - - return ( -
-
-

{ title }

- -
-
- -
-
- ); -} - -export default connect( - ( state ) => { - return { - plugin: getActivePlugin( state ), - }; - }, { - onClose: closeGeneralSidebar, - }, - undefined, - { storeKey: 'edit-post' } -)( withFocusReturn( PluginsPanel ) ); diff --git a/edit-post/index.js b/edit-post/index.js index 1f4ba5fe5085fb..759d578c6f9732 100644 --- a/edit-post/index.js +++ b/edit-post/index.js @@ -21,7 +21,7 @@ import Layout from './components/layout'; import store from './store'; import { initializeMetaBoxState } from './store/actions'; -export { default as PluginSidebar } from './components/plugin-sidebar'; +export { PluginSidebarFill as PluginSidebar } from './components/plugin-sidebar'; export * from './api'; // Configure moment globally From 0beaa4b0010382b4b8f28aa13e66e4dd11bc37fb Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Tue, 6 Mar 2018 18:15:49 +0100 Subject: [PATCH 06/83] Remove create-react-context --- element/index.js | 3 --- package.json | 1 - 2 files changed, 4 deletions(-) diff --git a/element/index.js b/element/index.js index 17ee2f7c7c6adb..e35c715b077386 100644 --- a/element/index.js +++ b/element/index.js @@ -11,7 +11,6 @@ import { upperFirst, isEmpty, } from 'lodash'; -import createContext from 'create-react-context'; /** * Returns a new element of given type. Type can be either a string tag name or @@ -27,8 +26,6 @@ import createContext from 'create-react-context'; */ export { createElement }; -export { createContext }; - /** * Renders a given element into the target DOM node. * diff --git a/package.json b/package.json index f5244724e477e8..d2fcd1e37b8328 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,6 @@ "@wordpress/url": "1.0.3", "classnames": "2.2.5", "clipboard": "1.7.1", - "create-react-context": "0.2.1", "dom-react": "2.2.0", "dom-scroll-into-view": "1.2.1", "element-closest": "2.0.2", From 75e35ba53779f256cb74241fc2c0ebe4106cfaee Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Wed, 7 Mar 2018 11:21:30 +0100 Subject: [PATCH 07/83] Removed old sidebar and refactored the way plugin context is handled --- edit-post/api/components/context.js | 48 +++++++++++++ edit-post/api/index.js | 4 -- edit-post/api/plugin.js | 48 ++++--------- edit-post/api/sidebar.js | 71 -------------------- edit-post/components/plugin-sidebar/index.js | 8 +-- 5 files changed, 63 insertions(+), 116 deletions(-) create mode 100644 edit-post/api/components/context.js delete mode 100644 edit-post/api/sidebar.js diff --git a/edit-post/api/components/context.js b/edit-post/api/components/context.js new file mode 100644 index 00000000000000..720370c6e1c9dd --- /dev/null +++ b/edit-post/api/components/context.js @@ -0,0 +1,48 @@ +/** + * External dependencies + */ +import PropTypes from 'prop-types'; + +/** + * WordPress dependencies + */ +import { Component, cloneElement, Children } from '@wordpress/element'; + +class PluginContextProvider extends Component { + getChildContext() { + return { + namespace: this.props.namespace, + }; + } + + render() { + return this.props.children; + } +} + +PluginContextProvider.childContextTypes = { + namespace: PropTypes.string.isRequired, +}; + +class PluginContextConsumer extends Component { + render() { + return cloneElement( Children.only( this.props.children ), this.context ); + } +} + +PluginContextConsumer.contextTypes = { + namespace: PropTypes.string.isRequired, +}; + +function withPluginContext( WrappedComponent ) { + return ( props ) => ( + + + + ); +} + +export { + PluginContextProvider, + withPluginContext, +}; diff --git a/edit-post/api/index.js b/edit-post/api/index.js index 93913d8e66a86c..34eee0f07b3868 100644 --- a/edit-post/api/index.js +++ b/edit-post/api/index.js @@ -1,7 +1,3 @@ -export { - registerSidebar as __experimentalRegisterSidebar, -} from './sidebar'; export { registerPlugin, - activateSidebar, } from './plugin'; diff --git a/edit-post/api/plugin.js b/edit-post/api/plugin.js index 2952ee5960a5bb..63757ad23e83c4 100644 --- a/edit-post/api/plugin.js +++ b/edit-post/api/plugin.js @@ -8,13 +8,11 @@ import { Component } from '@wordpress/element'; /* External dependencies */ import { isFunction, map } from 'lodash'; -import PropTypes from 'prop-types'; -/* Internal dependencies */ -import store from '../store'; -import { - openGeneralSidebar, -} from '../store/actions'; +/** + * Internal dependencies + */ +import { PluginContextProvider, withPluginContext } from './components/context'; const plugins = {}; @@ -27,7 +25,7 @@ const plugins = {}; * * @return {Object} The final plugin settings object. */ -export function registerPlugin( settings ) { +function registerPlugin( settings ) { settings = applyFilters( 'editPost.registerPlugin', settings, settings.name ); if ( typeof settings.name !== 'string' ) { @@ -57,31 +55,15 @@ export function registerPlugin( settings ) { return plugins[ settings.name ] = settings; } -class ContextProvider extends Component { - getChildContext() { - return { - namespace: this.props.namespace, - }; - } - - render() { - return this.props.children; - } -} - -ContextProvider.childContextTypes = { - namespace: PropTypes.string.isRequired, -}; - class Plugins extends Component { render() { return (
{ map( plugins, plugin => { return ( - + { plugin.render() } - + ); } ) }
@@ -89,14 +71,8 @@ class Plugins extends Component { } } -/** - * Activates the given sidebar. - * - * @param {string} name The name of the sidebar to activate. - * @return {void} - */ -export function activateSidebar( name ) { - store.dispatch( openGeneralSidebar( 'plugin', name ) ); -} - -export { Plugins }; +export { + Plugins, + withPluginContext, + registerPlugin, +}; diff --git a/edit-post/api/sidebar.js b/edit-post/api/sidebar.js deleted file mode 100644 index 3226b17f41bff9..00000000000000 --- a/edit-post/api/sidebar.js +++ /dev/null @@ -1,71 +0,0 @@ -/* eslint no-console: [ 'error', { allow: [ 'error' ] } ] */ - -/* External dependencies */ -import { isFunction } from 'lodash'; - -/* Internal dependencies */ -import { applyFilters } from '@wordpress/hooks'; - -const sidebars = {}; - -/** - * Registers a sidebar to the editor. - * - * A button will be shown in the settings menu to open the sidebar. The sidebar - * can be manually opened by calling the `activateSidebar` function. - * - * @param {string} name The name of the sidebar. Should be in - * `[plugin]/[sidebar]` format. - * @param {Object} settings The settings for this sidebar. - * @param {string} settings.title The name to show in the settings menu. - * @param {Function} settings.render The function that renders the sidebar. - * - * @return {Object} The final sidebar settings object. - */ -export function registerSidebar( name, settings ) { - settings = { - name, - ...settings, - }; - - if ( typeof name !== 'string' ) { - console.error( - 'Sidebar names must be strings.' - ); - return null; - } - 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.' - ); - return null; - } - if ( ! settings || ! isFunction( settings.render ) ) { - console.error( - 'The "render" property must be specified and must be a valid function.' - ); - return null; - } - if ( sidebars[ name ] ) { - console.error( - `Sidebar ${ name } is already registered.` - ); - } - - if ( ! settings.title ) { - console.error( - `The sidebar ${ name } must have a title.` - ); - return null; - } - if ( typeof settings.title !== 'string' ) { - console.error( - 'Sidebar titles must be strings.' - ); - return null; - } - - settings = applyFilters( 'editor.registerSidebar', settings, name ); - - return sidebars[ name ] = settings; -} diff --git a/edit-post/components/plugin-sidebar/index.js b/edit-post/components/plugin-sidebar/index.js index 6c34baacc1338c..8e1e7b3a8fc135 100644 --- a/edit-post/components/plugin-sidebar/index.js +++ b/edit-post/components/plugin-sidebar/index.js @@ -17,6 +17,7 @@ import { __ } from '@wordpress/i18n'; import './style.scss'; import { getActivePlugin, getOpenedGeneralSidebar } from '../../store/selectors'; import { closeGeneralSidebar } from '../../store/actions'; +import { withPluginContext } from '../../api/plugin'; /** * Name of slot in which popover should fill. @@ -59,7 +60,7 @@ class PluginSidebar extends Component { render() { if ( ! this.namespacedName ) { - this.namespacedName = `${ this.context.namespace }/${ this.props.name }`; + this.namespacedName = `${ this.props.namespace }/${ this.props.name }`; } if ( this.props.activePlugin !== this.namespacedName ) { return null; @@ -95,10 +96,6 @@ class PluginSidebar extends Component { } } -PluginSidebar.contextTypes = { - namespace: PropTypes.string.isRequired, -}; - const PluginSidebarSlot = () => ( ); const PluginSidebarFill = compose( [ @@ -112,6 +109,7 @@ const PluginSidebarFill = compose( [ null, { storeKey: 'edit-post' } ), withFocusReturn, + withPluginContext, ] )( PluginSidebar ); export { PluginSidebarFill, PluginSidebarSlot }; From 1ae8632e399382f54dcaee0a7fbd7bc7c5f3f46c Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Wed, 7 Mar 2018 11:38:06 +0100 Subject: [PATCH 08/83] Improved withPluginContext HOC to prevent usage of cloneElement --- edit-post/api/components/context.js | 27 +++++++++++++-------------- edit-post/api/plugin.js | 4 ++-- edit-post/components/layout/index.js | 4 ++-- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/edit-post/api/components/context.js b/edit-post/api/components/context.js index 720370c6e1c9dd..44727f6ac51368 100644 --- a/edit-post/api/components/context.js +++ b/edit-post/api/components/context.js @@ -6,7 +6,7 @@ import PropTypes from 'prop-types'; /** * WordPress dependencies */ -import { Component, cloneElement, Children } from '@wordpress/element'; +import { Component } from '@wordpress/element'; class PluginContextProvider extends Component { getChildContext() { @@ -24,22 +24,21 @@ PluginContextProvider.childContextTypes = { namespace: PropTypes.string.isRequired, }; -class PluginContextConsumer extends Component { - render() { - return cloneElement( Children.only( this.props.children ), this.context ); +function withPluginContext( WrappedComponent ) { + class PluginContextConsumer extends Component { + render() { + return ; + } } -} -PluginContextConsumer.contextTypes = { - namespace: PropTypes.string.isRequired, -}; + PluginContextConsumer.contextTypes = { + namespace: PropTypes.string.isRequired, + }; -function withPluginContext( WrappedComponent ) { - return ( props ) => ( - - - - ); + return PluginContextConsumer; } export { diff --git a/edit-post/api/plugin.js b/edit-post/api/plugin.js index 63757ad23e83c4..e15b5ba619394f 100644 --- a/edit-post/api/plugin.js +++ b/edit-post/api/plugin.js @@ -55,7 +55,7 @@ function registerPlugin( settings ) { return plugins[ settings.name ] = settings; } -class Plugins extends Component { +class PluginFills extends Component { render() { return (
@@ -72,7 +72,7 @@ class Plugins extends Component { } export { - Plugins, + PluginFills, withPluginContext, registerPlugin, }; diff --git a/edit-post/components/layout/index.js b/edit-post/components/layout/index.js index 9c73c99eea4732..7abac9024f360e 100644 --- a/edit-post/components/layout/index.js +++ b/edit-post/components/layout/index.js @@ -41,7 +41,7 @@ import { } from '../../store/selectors'; import { closePublishSidebar } from '../../store/actions'; import { PluginSidebarSlot } from '../../components/plugin-sidebar/index.js'; -import { Plugins } from '../../api/plugin'; +import { PluginFills } from '../../api/plugin'; function GeneralSidebar( { openedGeneralSidebar } ) { switch ( openedGeneralSidebar ) { @@ -107,7 +107,7 @@ function Layout( { openedGeneralSidebar={ openedGeneralSidebar } /> } - +
); } From 0d3d03d01a26006b79be054bea84fe41a04f73fa Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Wed, 7 Mar 2018 12:23:11 +0100 Subject: [PATCH 09/83] Implemented tests for registerPlugin --- edit-post/api/plugin.js | 12 +++++++--- edit-post/api/test/plugin.js | 46 ++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 edit-post/api/test/plugin.js diff --git a/edit-post/api/plugin.js b/edit-post/api/plugin.js index e15b5ba619394f..750ef05a699320 100644 --- a/edit-post/api/plugin.js +++ b/edit-post/api/plugin.js @@ -26,8 +26,12 @@ const plugins = {}; * @return {Object} The final plugin settings object. */ function registerPlugin( settings ) { - settings = applyFilters( 'editPost.registerPlugin', settings, settings.name ); - + if ( typeof settings !== 'object' ) { + console.error( + 'No settings object provided!' + ); + return null; + } if ( typeof settings.name !== 'string' ) { console.error( 'Plugin names must be strings.' @@ -45,13 +49,15 @@ function registerPlugin( settings ) { `Plugin "${ settings.name }" is already registered.` ); } - if ( ! settings || ! isFunction( settings.render ) ) { + if ( ! isFunction( settings.render ) ) { console.error( 'The "render" property must be specified and must be a valid function.' ); return null; } + settings = applyFilters( 'editPost.registerPlugin', settings, settings.name ); + return plugins[ settings.name ] = settings; } diff --git a/edit-post/api/test/plugin.js b/edit-post/api/test/plugin.js new file mode 100644 index 00000000000000..abd468d28d06ad --- /dev/null +++ b/edit-post/api/test/plugin.js @@ -0,0 +1,46 @@ +import { registerPlugin } from '../plugin'; + +describe( 'registerPlugin', () => { + it( 'successfully registers a plugin', () => { + registerPlugin( { + name: 'plugin', + render: () => 'plugin content', + } ); + } ); + + it( 'fails to register a plugin without a settings object', () => { + registerPlugin(); + expect( console ).toHaveErroredWith( 'No settings object provided!' ); + } ); + + it( 'fails to register a plugin with special character in the name', () => { + registerPlugin( { + name: 'plugin/with/special/characters', + render: () => {}, + } ); + expect( console ).toHaveErroredWith( 'Plugin names must include only lowercase alphanumeric characters or dashes, and start with a letter. Example: "my-plugin".' ); + } ); + + it( 'fails to register a plugin with a non-string name', () => { + registerPlugin( { + name: () => {}, + render: () => {}, + } ); + expect( console ).toHaveErroredWith( 'Plugin names must be strings.' ); + } ); + + it( 'fails to register a plugin without a render function', () => { + registerPlugin( { + name: 'another-plugin', + } ); + expect( console ).toHaveErroredWith( 'The "render" property must be specified and must be a valid function.' ); + } ); + + it( 'fails to register a plugin that was already been registered', () => { + registerPlugin( { + name: 'plugin', + render: () => 'plugin content', + } ); + expect( console ).toHaveErroredWith( 'Plugin "plugin" is already registered.' ); + } ); +} ); \ No newline at end of file From 789e675764c8b272b370ec21d49e60c189c736e2 Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Wed, 7 Mar 2018 14:00:23 +0100 Subject: [PATCH 10/83] Added test for plugin/context components, revamped naming of passed context --- edit-post/api/components/context.js | 6 ++-- edit-post/api/components/test/context.js | 33 ++++++++++++++++++++ edit-post/api/plugin.js | 2 +- edit-post/components/plugin-sidebar/index.js | 2 +- 4 files changed, 38 insertions(+), 5 deletions(-) create mode 100644 edit-post/api/components/test/context.js diff --git a/edit-post/api/components/context.js b/edit-post/api/components/context.js index 44727f6ac51368..7c9b404d087ff7 100644 --- a/edit-post/api/components/context.js +++ b/edit-post/api/components/context.js @@ -11,7 +11,7 @@ import { Component } from '@wordpress/element'; class PluginContextProvider extends Component { getChildContext() { return { - namespace: this.props.namespace, + pluginContext: this.props.value, }; } @@ -21,7 +21,7 @@ class PluginContextProvider extends Component { } PluginContextProvider.childContextTypes = { - namespace: PropTypes.string.isRequired, + pluginContext: PropTypes.any.isRequired, }; function withPluginContext( WrappedComponent ) { @@ -35,7 +35,7 @@ function withPluginContext( WrappedComponent ) { } PluginContextConsumer.contextTypes = { - namespace: PropTypes.string.isRequired, + pluginContext: PropTypes.any.isRequired, }; return PluginContextConsumer; diff --git a/edit-post/api/components/test/context.js b/edit-post/api/components/test/context.js new file mode 100644 index 00000000000000..9a439a4c3b9f14 --- /dev/null +++ b/edit-post/api/components/test/context.js @@ -0,0 +1,33 @@ +/** + * External dependencies + */ +import ReactTestRenderer from 'react-test-renderer'; + +/** + * Internal dependencies + */ +import { PluginContextProvider, withPluginContext } from '../context'; + +describe( 'plugin/context', () => { + describe( 'withPluginContext', () => { + it( 'passed the plugin context to the decorated component', () => { + const Component = ( props ) => { + return props.pluginContext; + }; + + const WrappedComponent = withPluginContext( Component ); + + const renderer = ReactTestRenderer.create( + +
+ +
+
+ ); + + const tree = renderer.toJSON(); + + expect( tree.children[ 0 ] ).toEqual( 'plugin-namespace' ); + } ); + } ); +} ); diff --git a/edit-post/api/plugin.js b/edit-post/api/plugin.js index 750ef05a699320..0be9b737140322 100644 --- a/edit-post/api/plugin.js +++ b/edit-post/api/plugin.js @@ -67,7 +67,7 @@ class PluginFills extends Component {
{ map( plugins, plugin => { return ( - + { plugin.render() } ); diff --git a/edit-post/components/plugin-sidebar/index.js b/edit-post/components/plugin-sidebar/index.js index 8e1e7b3a8fc135..ad871e56cc9a72 100644 --- a/edit-post/components/plugin-sidebar/index.js +++ b/edit-post/components/plugin-sidebar/index.js @@ -60,7 +60,7 @@ class PluginSidebar extends Component { render() { if ( ! this.namespacedName ) { - this.namespacedName = `${ this.props.namespace }/${ this.props.name }`; + this.namespacedName = `${ this.props.pluginContext.namespace }/${ this.props.name }`; } if ( this.props.activePlugin !== this.namespacedName ) { return null; From 08f6e7b44b2e56ed86b374c6e6e02cf3354111b9 Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Wed, 7 Mar 2018 14:03:53 +0100 Subject: [PATCH 11/83] Fixed eslint issues --- edit-post/api/test/plugin.js | 2 +- edit-post/components/plugin-sidebar/index.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/edit-post/api/test/plugin.js b/edit-post/api/test/plugin.js index abd468d28d06ad..0aaa1bd35358d7 100644 --- a/edit-post/api/test/plugin.js +++ b/edit-post/api/test/plugin.js @@ -43,4 +43,4 @@ describe( 'registerPlugin', () => { } ); expect( console ).toHaveErroredWith( 'Plugin "plugin" is already registered.' ); } ); -} ); \ No newline at end of file +} ); diff --git a/edit-post/components/plugin-sidebar/index.js b/edit-post/components/plugin-sidebar/index.js index ad871e56cc9a72..fc97691282e548 100644 --- a/edit-post/components/plugin-sidebar/index.js +++ b/edit-post/components/plugin-sidebar/index.js @@ -2,7 +2,6 @@ * External dependencies */ import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; /** * WordPress dependencies From ffe08706cc295b405b882ca0cf384b28321689aa Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Wed, 7 Mar 2018 14:39:49 +0100 Subject: [PATCH 12/83] Refactored plugin-sidebar to use withSelect/withDispatch and created separate sidebar layout component --- edit-post/components/plugin-sidebar/index.js | 52 +++++++------------ .../plugin-sidebar/sidebar-layout.js | 31 +++++++++++ 2 files changed, 49 insertions(+), 34 deletions(-) create mode 100644 edit-post/components/plugin-sidebar/sidebar-layout.js diff --git a/edit-post/components/plugin-sidebar/index.js b/edit-post/components/plugin-sidebar/index.js index fc97691282e548..886dd75694d381 100644 --- a/edit-post/components/plugin-sidebar/index.js +++ b/edit-post/components/plugin-sidebar/index.js @@ -1,22 +1,17 @@ -/** - * External dependencies - */ -import { connect } from 'react-redux'; - /** * WordPress dependencies */ import { Component, Children, cloneElement, compose } from '@wordpress/element'; -import { Slot, Fill, IconButton, withFocusReturn } from '@wordpress/components'; +import { Slot, Fill, withFocusReturn } from '@wordpress/components'; +import { withSelect, withDispatch } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ import './style.scss'; -import { getActivePlugin, getOpenedGeneralSidebar } from '../../store/selectors'; -import { closeGeneralSidebar } from '../../store/actions'; import { withPluginContext } from '../../api/plugin'; +import SidebarLayout from './sidebar-layout'; /** * Name of slot in which popover should fill. @@ -73,23 +68,11 @@ class PluginSidebar extends Component { return ( -
-
-

{ this.props.title }

- -
-
- { cloneElement( Children.only( children ), newProps ) } -
-
+ + { cloneElement( Children.only( children ), newProps ) } +
); } @@ -98,15 +81,16 @@ class PluginSidebar extends Component { const PluginSidebarSlot = () => ( ); const PluginSidebarFill = compose( [ - connect( - ( state ) => ( { - activePlugin: getActivePlugin( state ), - openedGeneralSidebar: getOpenedGeneralSidebar( state ), - } ), { - onClose: closeGeneralSidebar, - }, - null, - { storeKey: 'edit-post' } ), + withSelect( select => { + return { + activePlugin: select( 'core/edit-post' ).getActivePlugin(), + }; + } ), + withDispatch( dispatch => { + return { + onClose: dispatch( 'core/edit-post' ).closeGeneralSidebar, + }; + } ), withFocusReturn, withPluginContext, ] )( PluginSidebar ); diff --git a/edit-post/components/plugin-sidebar/sidebar-layout.js b/edit-post/components/plugin-sidebar/sidebar-layout.js new file mode 100644 index 00000000000000..edbcb5134bd666 --- /dev/null +++ b/edit-post/components/plugin-sidebar/sidebar-layout.js @@ -0,0 +1,31 @@ +import './style.scss'; + +/** + * WordPress dependencies + */ +import { IconButton } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +const SidebarLayout = ( { onClose, title, children } ) => { + return ( +
+
+

{ title }

+ +
+
+ { children } +
+
+ ); +}; + +export default SidebarLayout; From c55eaafff9ca4bc83490eb072b17c5a748170898 Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Wed, 7 Mar 2018 14:41:26 +0100 Subject: [PATCH 13/83] Removed PropTypes --- edit-post/api/components/context.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/edit-post/api/components/context.js b/edit-post/api/components/context.js index 7c9b404d087ff7..90df3a15e5ba3a 100644 --- a/edit-post/api/components/context.js +++ b/edit-post/api/components/context.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import PropTypes from 'prop-types'; +import { noop } from 'lodash'; /** * WordPress dependencies @@ -21,7 +21,7 @@ class PluginContextProvider extends Component { } PluginContextProvider.childContextTypes = { - pluginContext: PropTypes.any.isRequired, + pluginContext: noop, }; function withPluginContext( WrappedComponent ) { @@ -35,7 +35,7 @@ function withPluginContext( WrappedComponent ) { } PluginContextConsumer.contextTypes = { - pluginContext: PropTypes.any.isRequired, + pluginContext: noop, }; return PluginContextConsumer; From a3a08de4b0077455920fb20736430e198ebdc09c Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Wed, 7 Mar 2018 15:25:59 +0100 Subject: [PATCH 14/83] Used enzyme instead of react-test-renderer in plugin context tests --- edit-post/api/components/test/context.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/edit-post/api/components/test/context.js b/edit-post/api/components/test/context.js index 9a439a4c3b9f14..a5b72980c3fc75 100644 --- a/edit-post/api/components/test/context.js +++ b/edit-post/api/components/test/context.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import ReactTestRenderer from 'react-test-renderer'; +import { mount } from 'enzyme'; /** * Internal dependencies @@ -17,7 +17,7 @@ describe( 'plugin/context', () => { const WrappedComponent = withPluginContext( Component ); - const renderer = ReactTestRenderer.create( + const MountedComponent = mount(
@@ -25,9 +25,7 @@ describe( 'plugin/context', () => { ); - const tree = renderer.toJSON(); - - expect( tree.children[ 0 ] ).toEqual( 'plugin-namespace' ); + expect( MountedComponent.find( Component ).props().pluginContext ).toEqual( 'plugin-namespace' ); } ); } ); } ); From 37a41d8aca98def31e7f73961db53d1befda1f23 Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Wed, 7 Mar 2018 15:39:07 +0100 Subject: [PATCH 15/83] Hidden internal Slot and Fill components in layout --- edit-post/api/plugin.js | 7 +++++-- edit-post/components/layout/index.js | 8 ++++---- edit-post/components/plugin-sidebar/index.js | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/edit-post/api/plugin.js b/edit-post/api/plugin.js index 0be9b737140322..6df85817c541bc 100644 --- a/edit-post/api/plugin.js +++ b/edit-post/api/plugin.js @@ -61,7 +61,10 @@ function registerPlugin( settings ) { return plugins[ settings.name ] = settings; } -class PluginFills extends Component { +/** + * A component that renders all plugin fills in a hidden div. + */ +class PluginArea extends Component { render() { return (
@@ -78,7 +81,7 @@ class PluginFills extends Component { } export { - PluginFills, + PluginArea, withPluginContext, registerPlugin, }; diff --git a/edit-post/components/layout/index.js b/edit-post/components/layout/index.js index 7abac9024f360e..6b77ce2ce9eb36 100644 --- a/edit-post/components/layout/index.js +++ b/edit-post/components/layout/index.js @@ -40,15 +40,15 @@ import { isSavingMetaBoxes, } from '../../store/selectors'; import { closePublishSidebar } from '../../store/actions'; -import { PluginSidebarSlot } from '../../components/plugin-sidebar/index.js'; -import { PluginFills } from '../../api/plugin'; +import { PluginSidebar } from '../../components/plugin-sidebar/index.js'; +import { PluginArea } from '../../api/plugin'; function GeneralSidebar( { openedGeneralSidebar } ) { switch ( openedGeneralSidebar ) { case 'editor': return ; case 'plugin': - return ; + return ; default: } return null; @@ -107,7 +107,7 @@ function Layout( { openedGeneralSidebar={ openedGeneralSidebar } /> } - +
); } diff --git a/edit-post/components/plugin-sidebar/index.js b/edit-post/components/plugin-sidebar/index.js index 886dd75694d381..9b9dbe79350b15 100644 --- a/edit-post/components/plugin-sidebar/index.js +++ b/edit-post/components/plugin-sidebar/index.js @@ -95,4 +95,4 @@ const PluginSidebarFill = compose( [ withPluginContext, ] )( PluginSidebar ); -export { PluginSidebarFill, PluginSidebarSlot }; +export { PluginSidebarFill, PluginSidebarSlot as PluginSidebar }; From ab5df9785153d089f36016d67703d7bb82dbcb8c Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Wed, 7 Mar 2018 17:13:05 +0100 Subject: [PATCH 16/83] Moved plugin-sidebar error-boundary to separate file --- .../plugin-sidebar/error-boundary.js | 27 ++++++++++++++++++ edit-post/components/plugin-sidebar/index.js | 28 ++++--------------- 2 files changed, 33 insertions(+), 22 deletions(-) create mode 100644 edit-post/components/plugin-sidebar/error-boundary.js diff --git a/edit-post/components/plugin-sidebar/error-boundary.js b/edit-post/components/plugin-sidebar/error-boundary.js new file mode 100644 index 00000000000000..9593596b32ebed --- /dev/null +++ b/edit-post/components/plugin-sidebar/error-boundary.js @@ -0,0 +1,27 @@ +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +class SidebarErrorBoundary extends Component { + constructor( props ) { + super( props ); + this.state = { hasError: false }; + } + + componentDidCatch() { + this.setState( { hasError: true } ); + } + + render() { + if ( this.state.hasError ) { + return

+ { __( 'An error occurred rendering the plugin sidebar.' ) } +

; + } + return this.props.children; + } +} + +export default SidebarErrorBoundary; diff --git a/edit-post/components/plugin-sidebar/index.js b/edit-post/components/plugin-sidebar/index.js index 9b9dbe79350b15..418fdf1269b4ef 100644 --- a/edit-post/components/plugin-sidebar/index.js +++ b/edit-post/components/plugin-sidebar/index.js @@ -4,7 +4,6 @@ import { Component, Children, cloneElement, compose } from '@wordpress/element'; import { Slot, Fill, withFocusReturn } from '@wordpress/components'; import { withSelect, withDispatch } from '@wordpress/data'; -import { __ } from '@wordpress/i18n'; /** * Internal dependencies @@ -12,6 +11,7 @@ import { __ } from '@wordpress/i18n'; import './style.scss'; import { withPluginContext } from '../../api/plugin'; import SidebarLayout from './sidebar-layout'; +import ErrorBoundary from './error-boundary'; /** * Name of slot in which popover should fill. @@ -20,26 +20,6 @@ import SidebarLayout from './sidebar-layout'; */ const SLOT_NAME = 'PluginSidebar'; -class SidebarErrorBoundary extends Component { - constructor( props ) { - super( props ); - this.state = { hasError: false }; - } - - componentDidCatch() { - this.setState( { hasError: true } ); - } - - render() { - if ( this.state.hasError ) { - return

- { __( 'An error occurred rendering the plugin sidebar.' ) } -

; - } - return this.props.children; - } -} - class PluginSidebar extends Component { constructor( props ) { super( props ); @@ -78,7 +58,11 @@ class PluginSidebar extends Component { } } -const PluginSidebarSlot = () => ( ); +const PluginSidebarSlot = () => ( + + + +); const PluginSidebarFill = compose( [ withSelect( select => { From 8fe79d04bbb1215ce090a42f519a3ec750333a81 Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Wed, 7 Mar 2018 17:54:06 +0100 Subject: [PATCH 17/83] Moved sidebar error boundary around the component only so the error can be rendered within the sidebar --- edit-post/components/plugin-sidebar/error-boundary.js | 4 ++-- edit-post/components/plugin-sidebar/index.js | 8 ++++---- edit-post/components/plugin-sidebar/style.scss | 4 ++++ 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/edit-post/components/plugin-sidebar/error-boundary.js b/edit-post/components/plugin-sidebar/error-boundary.js index 9593596b32ebed..3cce18b752cfb3 100644 --- a/edit-post/components/plugin-sidebar/error-boundary.js +++ b/edit-post/components/plugin-sidebar/error-boundary.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { Component } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; class SidebarErrorBoundary extends Component { constructor( props ) { @@ -17,7 +17,7 @@ class SidebarErrorBoundary extends Component { render() { if ( this.state.hasError ) { return

- { __( 'An error occurred rendering the plugin sidebar.' ) } + { sprintf( __( 'An error occurred rendering the plugin sidebar with id "%s".' ), this.props.pluginName ) }

; } return this.props.children; diff --git a/edit-post/components/plugin-sidebar/index.js b/edit-post/components/plugin-sidebar/index.js index 418fdf1269b4ef..ba890704e03c3b 100644 --- a/edit-post/components/plugin-sidebar/index.js +++ b/edit-post/components/plugin-sidebar/index.js @@ -51,7 +51,9 @@ class PluginSidebar extends Component { - { cloneElement( Children.only( children ), newProps ) } + + { cloneElement( Children.only( children ), newProps ) } + ); @@ -59,9 +61,7 @@ class PluginSidebar extends Component { } const PluginSidebarSlot = () => ( - - - + ); const PluginSidebarFill = compose( [ diff --git a/edit-post/components/plugin-sidebar/style.scss b/edit-post/components/plugin-sidebar/style.scss index 9a1e1dfb5a4f80..0b8372a48714ad 100644 --- a/edit-post/components/plugin-sidebar/style.scss +++ b/edit-post/components/plugin-sidebar/style.scss @@ -18,4 +18,8 @@ } } +.plugin-sidebar-error { + padding: 16px; + color: red; +} From 8c2930cc37fb194543bfd19340ca73c1d70d0fd8 Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Wed, 7 Mar 2018 18:31:53 +0100 Subject: [PATCH 18/83] Updated documentation --- edit-post/api/README.md | 69 +++++++++++++------- edit-post/components/plugin-sidebar/index.js | 10 ++- 2 files changed, 56 insertions(+), 23 deletions(-) diff --git a/edit-post/api/README.md b/edit-post/api/README.md index 2d3c14bd1ccbc1..f904a0e66406f1 100644 --- a/edit-post/api/README.md +++ b/edit-post/api/README.md @@ -3,35 +3,60 @@ Edit post API The edit post API contains the following methods: -### `wp.editPost.__experimentalRegisterSidebar( name: string, settings: { title: string, render: function } )` +## `wp.editPost.registerPlugin( { name: string, render: function } )` -**Warning:** This is an experimental API, and is subject to change or even removal. - -This method takes two arguments: -- a `name` to identify the sidebar. This name should contain a namespace prefix, followed by a slash and a sidebar name. The name should include only lowercase alphanumeric characters or dashes, and start with a letter. Example: `my-plugin/my-custom-sidebar`. -- a `settings` object, containing a title and a render function. - -This method only registers a sidebar. To open the sidebar, use the `__experimentalRegisterSidebar` method below. - -#### Example: +This method takes one argument: +- An object containing the following data: + - `name`: A string identifying the plugin. Must be unique across all registered plugins. + - `render`: A component containing the UI elements to be rendered. See the list below for all available UI elements. + +### Example ```js -wp.editPost.__experimentalRegisterSidebar( 'my-plugin/my-custom-sidebar', { - render: function mySidebar() { - return

This is an example

; - }, +const Fragment = wp.element.Fragment; +const PluginSidebar = wp.editPost.PluginSidebar; + +const Component = () => ( + + +

Content of the first sidebar

+
+ +

Content of the second sidebar

+
+
+); + +wp.editPost.registerPlugin( { + name: 'plugin-name', + render: Component, } ); ``` -### `wp.editPost.__experimentalActivateSidebar( name: string )` +You can activate the sidebars using the following line: -**Warning:** This is an experimental API, and is subject to change or even removal. +`wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/first-sidebar-name' );` +`wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/second-sidebar-name' );` -This method takes one argument: -- the `name` of the sidebar you'd like to open. That sidebar should have been registered beforehand using the `__experimentalRegisterSidebar` method. + +### Available UI components -#### Example: +The available UI components are available under `wp.editPost`, and are React components. -```js -wp.editPost.__experimentalActivateSidebar( 'my-plugin/my-custom-sidebar' ); -``` +#### PluginSidebar + +Renders a sidebar when activated. + +`{ contents }` + +- Props + - `name`: A string identifying the sidebar. Must be unique for every sidebar registered iwthin the scope of your plugin. + - `title`: Title displayed at the top of the sidebar. Must be a string. + +The contents you render within the `PluginSidebar` will show up as content within the sidebar. + +The sidebar can be activated using the data api: + +`wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/sidebar-name' );` + +Notice that you need to use both the plugin name and sidebar name separated by a `/` to show the correct sidebar. diff --git a/edit-post/components/plugin-sidebar/index.js b/edit-post/components/plugin-sidebar/index.js index ba890704e03c3b..22b9b494b9bac7 100644 --- a/edit-post/components/plugin-sidebar/index.js +++ b/edit-post/components/plugin-sidebar/index.js @@ -14,12 +14,15 @@ import SidebarLayout from './sidebar-layout'; import ErrorBoundary from './error-boundary'; /** - * Name of slot in which popover should fill. + * Name of slot in which the sidebar should fill. * * @type {String} */ const SLOT_NAME = 'PluginSidebar'; +/** + * The plugin sidebar fill. + */ class PluginSidebar extends Component { constructor( props ) { super( props ); @@ -60,6 +63,11 @@ class PluginSidebar extends Component { } } +/** + * The plugin sidebar slot. + * + * @return {ReactElement} The plugin sidebar slot. + */ const PluginSidebarSlot = () => ( ); From a3fada723650935d56331a324daf9438f8c40e5e Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Wed, 7 Mar 2018 18:59:07 +0100 Subject: [PATCH 19/83] Updated documentation --- docs/extensibility.md | 67 +++++++++++++++++++++++++++++------------ edit-post/api/README.md | 2 +- 2 files changed, 48 insertions(+), 21 deletions(-) diff --git a/docs/extensibility.md b/docs/extensibility.md index 4f64d4348c8885..352e4ebe49d164 100644 --- a/docs/extensibility.md +++ b/docs/extensibility.md @@ -120,38 +120,65 @@ wp.hooks.addFilter( _Note:_ This filter must always be run on every page load, and not in your browser's developer tools console. Otherwise, a [block validation](https://wordpress.org/gutenberg/handbook/block-api/block-edit-save/#validation) error will occur the next time the post is edited. This is due to the fact that block validation occurs by verifying that the saved output matches what is stored in the post's content during editor initialization. So, if this filter does not exist when the editor loads, the block will be marked as invalid. -## Adding a sidebar - -**Warning:** This is an experimental API, and is subject to change or even removal. - -### Registering a sidebar +## Extending the editor's UI (Slot and Fill) -`wp.editPost.__experimentalRegisterSidebar( name: string, settings: { title: string, render: function } )` +Extending the editor's UI is done with in `registerPlugin` API, and allows you to define all your plugin's UI elements in one place. -This method takes a sidebar `name` and a `settings` object, containing a title and a render function. The name should contain a namespace prefix (Example: my-plugin/my-custom-sidebar). +`wp.editPost.registerPlugin( { name: string, render: function } )` +This method takes one argument: +- An object containing the following data: + - `name`: A string identifying the plugin. Must be unique across all registered plugins. + - `render`: A component containing the UI elements to be rendered. See the list below for all available UI elements. + **Example** ```js -wp.editPost.__experimentalRegisterSidebar( 'my-plugin/my-custom-sidebar', { - render: function mySidebar() { - return

This is an example

; - }, +const Fragment = wp.element.Fragment; +const PluginSidebar = wp.editPost.PluginSidebar; + +const Component = () => ( + + +

Content of the first sidebar

+
+ +

Content of the second sidebar

+
+
+); + +wp.editPost.registerPlugin( { + name: 'plugin-name', + render: Component, } ); ``` -### Activating a sidebar +You can activate the sidebars using the following lines: -`wp.editPost.__experimentalActivateSidebar( name: string )` +`wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/first-sidebar-name' );` +`wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/second-sidebar-name' );` -This method takes the `name` of the sidebar you'd like to open. That sidebar should have been registered beforehand using the `registerSidebar` method. + +### Available UI components -**Example** +The available UI components are available under `wp.editPost`, and are React components. -```js -wp.editPost.__experimentalActivateSidebar( 'my-plugin/my-custom-sidebar' ); -``` +#### PluginSidebar -## Extending the editor's UI (Slot and Fill) +Renders a sidebar when activated. + +`{ contents }` + +- Props + - `name`: A string identifying the sidebar. Must be unique for every sidebar registered iwthin the scope of your plugin. + - `title`: Title displayed at the top of the sidebar. Must be a string. + +The contents you render within the `PluginSidebar` will show up as content within the sidebar. + +The sidebar can be activated using the data api: + +`wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/sidebar-name' );` + +Notice that you need to use both the plugin name and sidebar name separated by a `/` to show the correct sidebar. -Coming soon. diff --git a/edit-post/api/README.md b/edit-post/api/README.md index f904a0e66406f1..d8bdb5471fb5f0 100644 --- a/edit-post/api/README.md +++ b/edit-post/api/README.md @@ -33,7 +33,7 @@ wp.editPost.registerPlugin( { } ); ``` -You can activate the sidebars using the following line: +You can activate the sidebars using the following lines: `wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/first-sidebar-name' );` `wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/second-sidebar-name' );` From 83a4dd3e99ebe07bc199fb6d694082232c9898ff Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Thu, 8 Mar 2018 07:25:14 +0100 Subject: [PATCH 20/83] Updated documentation --- docs/extensibility.md | 5 ++--- edit-post/api/README.md | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/extensibility.md b/docs/extensibility.md index 352e4ebe49d164..d4a559eb54e943 100644 --- a/docs/extensibility.md +++ b/docs/extensibility.md @@ -162,7 +162,7 @@ You can activate the sidebars using the following lines: ### Available UI components -The available UI components are available under `wp.editPost`, and are React components. +The available UI components are found in the global variable `wp.editPost`, and are React components. #### PluginSidebar @@ -171,7 +171,7 @@ Renders a sidebar when activated. `{ contents }` - Props - - `name`: A string identifying the sidebar. Must be unique for every sidebar registered iwthin the scope of your plugin. + - `name`: A string identifying the sidebar. Must be unique for every sidebar registered within the scope of your plugin. - `title`: Title displayed at the top of the sidebar. Must be a string. The contents you render within the `PluginSidebar` will show up as content within the sidebar. @@ -181,4 +181,3 @@ The sidebar can be activated using the data api: `wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/sidebar-name' );` Notice that you need to use both the plugin name and sidebar name separated by a `/` to show the correct sidebar. - diff --git a/edit-post/api/README.md b/edit-post/api/README.md index d8bdb5471fb5f0..2b64867c95913d 100644 --- a/edit-post/api/README.md +++ b/edit-post/api/README.md @@ -10,7 +10,7 @@ This method takes one argument: - `name`: A string identifying the plugin. Must be unique across all registered plugins. - `render`: A component containing the UI elements to be rendered. See the list below for all available UI elements. -### Example +**Example** ```js const Fragment = wp.element.Fragment; @@ -41,7 +41,7 @@ You can activate the sidebars using the following lines: ### Available UI components -The available UI components are available under `wp.editPost`, and are React components. +The available UI components are found in the global variable `wp.editPost`, and are React components. #### PluginSidebar @@ -50,7 +50,7 @@ Renders a sidebar when activated. `{ contents }` - Props - - `name`: A string identifying the sidebar. Must be unique for every sidebar registered iwthin the scope of your plugin. + - `name`: A string identifying the sidebar. Must be unique for every sidebar registered within the scope of your plugin. - `title`: Title displayed at the top of the sidebar. Must be a string. The contents you render within the `PluginSidebar` will show up as content within the sidebar. From 908112d44641657022bed89efae092dc94aa5e8f Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Thu, 8 Mar 2018 22:41:12 +0100 Subject: [PATCH 21/83] Refactored editpost/api/context component to reparate folder --- edit-post/api/components/{context.js => context/index.js} | 0 edit-post/api/components/{ => context}/test/context.js | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename edit-post/api/components/{context.js => context/index.js} (100%) rename edit-post/api/components/{ => context}/test/context.js (100%) diff --git a/edit-post/api/components/context.js b/edit-post/api/components/context/index.js similarity index 100% rename from edit-post/api/components/context.js rename to edit-post/api/components/context/index.js diff --git a/edit-post/api/components/test/context.js b/edit-post/api/components/context/test/context.js similarity index 100% rename from edit-post/api/components/test/context.js rename to edit-post/api/components/context/test/context.js From 913415ef076eacaa3fdb3d633e421e6059533553 Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Thu, 8 Mar 2018 22:57:10 +0100 Subject: [PATCH 22/83] Moved plugin components to api folder and made API experimental --- .../components/plugin-sidebar/error-boundary.js | 0 .../{ => api}/components/plugin-sidebar/index.js | 2 +- .../components/plugin-sidebar/sidebar-layout.js | 0 .../{ => api}/components/plugin-sidebar/style.scss | 0 edit-post/api/index.js | 12 ++++++++++-- edit-post/components/layout/index.js | 2 +- edit-post/index.js | 2 +- 7 files changed, 13 insertions(+), 5 deletions(-) rename edit-post/{ => api}/components/plugin-sidebar/error-boundary.js (100%) rename edit-post/{ => api}/components/plugin-sidebar/index.js (97%) rename edit-post/{ => api}/components/plugin-sidebar/sidebar-layout.js (100%) rename edit-post/{ => api}/components/plugin-sidebar/style.scss (100%) diff --git a/edit-post/components/plugin-sidebar/error-boundary.js b/edit-post/api/components/plugin-sidebar/error-boundary.js similarity index 100% rename from edit-post/components/plugin-sidebar/error-boundary.js rename to edit-post/api/components/plugin-sidebar/error-boundary.js diff --git a/edit-post/components/plugin-sidebar/index.js b/edit-post/api/components/plugin-sidebar/index.js similarity index 97% rename from edit-post/components/plugin-sidebar/index.js rename to edit-post/api/components/plugin-sidebar/index.js index 22b9b494b9bac7..de8b3c96c4ed5c 100644 --- a/edit-post/components/plugin-sidebar/index.js +++ b/edit-post/api/components/plugin-sidebar/index.js @@ -9,7 +9,7 @@ import { withSelect, withDispatch } from '@wordpress/data'; * Internal dependencies */ import './style.scss'; -import { withPluginContext } from '../../api/plugin'; +import { withPluginContext } from '../../plugin'; import SidebarLayout from './sidebar-layout'; import ErrorBoundary from './error-boundary'; diff --git a/edit-post/components/plugin-sidebar/sidebar-layout.js b/edit-post/api/components/plugin-sidebar/sidebar-layout.js similarity index 100% rename from edit-post/components/plugin-sidebar/sidebar-layout.js rename to edit-post/api/components/plugin-sidebar/sidebar-layout.js diff --git a/edit-post/components/plugin-sidebar/style.scss b/edit-post/api/components/plugin-sidebar/style.scss similarity index 100% rename from edit-post/components/plugin-sidebar/style.scss rename to edit-post/api/components/plugin-sidebar/style.scss diff --git a/edit-post/api/index.js b/edit-post/api/index.js index 34eee0f07b3868..113b013e880695 100644 --- a/edit-post/api/index.js +++ b/edit-post/api/index.js @@ -1,3 +1,11 @@ -export { +import { registerPlugin } from './plugin'; +import { PluginSidebarFill as PluginSidebar } from './components/plugin-sidebar'; + +const __experimental = { registerPlugin, -} from './plugin'; + PluginSidebar, +}; + +export { + __experimental, +}; diff --git a/edit-post/components/layout/index.js b/edit-post/components/layout/index.js index 6b77ce2ce9eb36..5b956f95fa9d59 100644 --- a/edit-post/components/layout/index.js +++ b/edit-post/components/layout/index.js @@ -40,7 +40,7 @@ import { isSavingMetaBoxes, } from '../../store/selectors'; import { closePublishSidebar } from '../../store/actions'; -import { PluginSidebar } from '../../components/plugin-sidebar/index.js'; +import { PluginSidebar } from '../../api/components/plugin-sidebar'; import { PluginArea } from '../../api/plugin'; function GeneralSidebar( { openedGeneralSidebar } ) { diff --git a/edit-post/index.js b/edit-post/index.js index 759d578c6f9732..13a72075090f04 100644 --- a/edit-post/index.js +++ b/edit-post/index.js @@ -21,7 +21,7 @@ import Layout from './components/layout'; import store from './store'; import { initializeMetaBoxState } from './store/actions'; -export { PluginSidebarFill as PluginSidebar } from './components/plugin-sidebar'; +export { PluginSidebarFill as PluginSidebar } from './api/components/plugin-sidebar'; export * from './api'; // Configure moment globally From 2dc4a7829691eb9d2580514d85d3b7d503f9afd2 Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Thu, 8 Mar 2018 23:00:28 +0100 Subject: [PATCH 23/83] Updated docs to reflect experimental status --- docs/extensibility.md | 8 ++++---- edit-post/api/README.md | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/extensibility.md b/docs/extensibility.md index d4a559eb54e943..a94bf2a0b261dd 100644 --- a/docs/extensibility.md +++ b/docs/extensibility.md @@ -124,7 +124,7 @@ _Note:_ This filter must always be run on every page load, and not in your brows Extending the editor's UI is done with in `registerPlugin` API, and allows you to define all your plugin's UI elements in one place. -`wp.editPost.registerPlugin( { name: string, render: function } )` +`wp.editPost.__experimental.registerPlugin( { name: string, render: function } )` This method takes one argument: - An object containing the following data: @@ -135,7 +135,7 @@ This method takes one argument: ```js const Fragment = wp.element.Fragment; -const PluginSidebar = wp.editPost.PluginSidebar; +const PluginSidebar = wp.editPost.__experimental.PluginSidebar; const Component = () => ( @@ -148,7 +148,7 @@ const Component = () => ( ); -wp.editPost.registerPlugin( { +wp.editPost.__experimental.registerPlugin( { name: 'plugin-name', render: Component, } ); @@ -162,7 +162,7 @@ You can activate the sidebars using the following lines: ### Available UI components -The available UI components are found in the global variable `wp.editPost`, and are React components. +The available UI components are found in the global variable `wp.editPost.__experimental`, and are React components. #### PluginSidebar diff --git a/edit-post/api/README.md b/edit-post/api/README.md index 2b64867c95913d..d127d64511135d 100644 --- a/edit-post/api/README.md +++ b/edit-post/api/README.md @@ -3,7 +3,7 @@ Edit post API The edit post API contains the following methods: -## `wp.editPost.registerPlugin( { name: string, render: function } )` +## `wp.editPost.__experimental.registerPlugin( { name: string, render: function } )` This method takes one argument: - An object containing the following data: @@ -14,7 +14,7 @@ This method takes one argument: ```js const Fragment = wp.element.Fragment; -const PluginSidebar = wp.editPost.PluginSidebar; +const PluginSidebar = wp.editPost.__experimental.PluginSidebar; const Component = () => ( @@ -27,7 +27,7 @@ const Component = () => ( ); -wp.editPost.registerPlugin( { +wp.editPost.__experimental.registerPlugin( { name: 'plugin-name', render: Component, } ); @@ -41,7 +41,7 @@ You can activate the sidebars using the following lines: ### Available UI components -The available UI components are found in the global variable `wp.editPost`, and are React components. +The available UI components are found in the global variable `wp.editPost.__experimental`, and are React components. #### PluginSidebar From a4ce7d2fb3b68331d5b549ac0d087a8f126d9ded Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Thu, 8 Mar 2018 23:24:18 +0100 Subject: [PATCH 24/83] Updated documentation --- docs/extensibility.md | 29 +++++++++++++---------------- edit-post/api/README.md | 27 ++++++++++++--------------- 2 files changed, 25 insertions(+), 31 deletions(-) diff --git a/docs/extensibility.md b/docs/extensibility.md index a94bf2a0b261dd..85f7d420de6033 100644 --- a/docs/extensibility.md +++ b/docs/extensibility.md @@ -120,11 +120,11 @@ wp.hooks.addFilter( _Note:_ This filter must always be run on every page load, and not in your browser's developer tools console. Otherwise, a [block validation](https://wordpress.org/gutenberg/handbook/block-api/block-edit-save/#validation) error will occur the next time the post is edited. This is due to the fact that block validation occurs by verifying that the saved output matches what is stored in the post's content during editor initialization. So, if this filter does not exist when the editor loads, the block will be marked as invalid. -## Extending the editor's UI (Slot and Fill) +## Extending the editor's UI (Experimental) Extending the editor's UI is done with in `registerPlugin` API, and allows you to define all your plugin's UI elements in one place. -`wp.editPost.__experimental.registerPlugin( { name: string, render: function } )` +### `wp.editPost.__experimental.registerPlugin( { name: string, render: function } )` This method takes one argument: - An object containing the following data: @@ -140,10 +140,10 @@ const PluginSidebar = wp.editPost.__experimental.PluginSidebar; const Component = () => ( -

Content of the first sidebar

+

Content of the first sidebar

-

Content of the second sidebar

+

Content of the second sidebar

); @@ -156,9 +156,10 @@ wp.editPost.__experimental.registerPlugin( { You can activate the sidebars using the following lines: -`wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/first-sidebar-name' );` -`wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/second-sidebar-name' );` - +```js +wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/first-sidebar-name' ); +wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/second-sidebar-name' ); +``` ### Available UI components @@ -167,17 +168,13 @@ The available UI components are found in the global variable `wp.editPost.__expe #### PluginSidebar Renders a sidebar when activated. - -`{ contents }` - +```js + + + +``` - Props - `name`: A string identifying the sidebar. Must be unique for every sidebar registered within the scope of your plugin. - `title`: Title displayed at the top of the sidebar. Must be a string. The contents you render within the `PluginSidebar` will show up as content within the sidebar. - -The sidebar can be activated using the data api: - -`wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/sidebar-name' );` - -Notice that you need to use both the plugin name and sidebar name separated by a `/` to show the correct sidebar. diff --git a/edit-post/api/README.md b/edit-post/api/README.md index d127d64511135d..2e5c81e7d1636d 100644 --- a/edit-post/api/README.md +++ b/edit-post/api/README.md @@ -3,7 +3,7 @@ Edit post API The edit post API contains the following methods: -## `wp.editPost.__experimental.registerPlugin( { name: string, render: function } )` +### `wp.editPost.__experimental.registerPlugin( { name: string, render: function } )` This method takes one argument: - An object containing the following data: @@ -19,10 +19,10 @@ const PluginSidebar = wp.editPost.__experimental.PluginSidebar; const Component = () => ( -

Content of the first sidebar

+

Content of the first sidebar

-

Content of the second sidebar

+

Content of the second sidebar

); @@ -35,9 +35,10 @@ wp.editPost.__experimental.registerPlugin( { You can activate the sidebars using the following lines: -`wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/first-sidebar-name' );` -`wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/second-sidebar-name' );` - +```js +wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/first-sidebar-name' ); +wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/second-sidebar-name' ); +``` ### Available UI components @@ -46,17 +47,13 @@ The available UI components are found in the global variable `wp.editPost.__expe #### PluginSidebar Renders a sidebar when activated. - -`{ contents }` - +```js + + + +``` - Props - `name`: A string identifying the sidebar. Must be unique for every sidebar registered within the scope of your plugin. - `title`: Title displayed at the top of the sidebar. Must be a string. The contents you render within the `PluginSidebar` will show up as content within the sidebar. - -The sidebar can be activated using the data api: - -`wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/sidebar-name' );` - -Notice that you need to use both the plugin name and sidebar name separated by a `/` to show the correct sidebar. From 1432bc4abb9bcaf91258294e6ea0377c22ebc0f9 Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Thu, 8 Mar 2018 23:28:36 +0100 Subject: [PATCH 25/83] Fixed failing test --- edit-post/api/components/context/test/context.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edit-post/api/components/context/test/context.js b/edit-post/api/components/context/test/context.js index a5b72980c3fc75..af864d0cdd6a93 100644 --- a/edit-post/api/components/context/test/context.js +++ b/edit-post/api/components/context/test/context.js @@ -6,7 +6,7 @@ import { mount } from 'enzyme'; /** * Internal dependencies */ -import { PluginContextProvider, withPluginContext } from '../context'; +import { PluginContextProvider, withPluginContext } from '../index'; describe( 'plugin/context', () => { describe( 'withPluginContext', () => { From ca63d6266b46a5ad7943a98f8023acf45394a802 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= Date: Fri, 9 Mar 2018 08:04:27 +0100 Subject: [PATCH 26/83] Docs: Add a few tweaks to the Extensibility doc --- docs/extensibility.md | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/docs/extensibility.md b/docs/extensibility.md index 85f7d420de6033..f8d524d6db17b6 100644 --- a/docs/extensibility.md +++ b/docs/extensibility.md @@ -137,44 +137,42 @@ This method takes one argument: const Fragment = wp.element.Fragment; const PluginSidebar = wp.editPost.__experimental.PluginSidebar; -const Component = () => ( +const MyPluginComponent = () => ( - -

Content of the first sidebar

-
- -

Content of the second sidebar

-
+ +

Content of the first sidebar

+
+ +

Content of the second sidebar

+
); wp.editPost.__experimental.registerPlugin( { - name: 'plugin-name', - render: Component, + name: 'my-plugin-name', + render: MyPluginComponent, } ); ``` - -You can activate the sidebars using the following lines: - -```js -wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/first-sidebar-name' ); -wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/second-sidebar-name' ); -``` ### Available UI components -The available UI components are found in the global variable `wp.editPost.__experimental`, and are React components. +The available UI components are found in the global variable `wp.editPost.__experimental`, and are WrordPress components. #### PluginSidebar Renders a sidebar when activated. ```js - - + +

Content of the first sidebar

``` - Props - `name`: A string identifying the sidebar. Must be unique for every sidebar registered within the scope of your plugin. - `title`: Title displayed at the top of the sidebar. Must be a string. +You can activate the sidebar using the following statement: +```js +wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'my-plugin-name/first-sidebar-name' ); +``` The contents you render within the `PluginSidebar` will show up as content within the sidebar. + From 4ac215241cce2240b63de54be266d1097e52b1b7 Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Fri, 2 Mar 2018 15:56:55 +0100 Subject: [PATCH 27/83] Initial implementation of slot-fill sidebar API --- edit-post/api/index.js | 3 + edit-post/api/plugin.js | 68 ++++++++++++++++++++ edit-post/components/layout/index.js | 2 + edit-post/components/plugin-sidebar/index.js | 44 +++++++++++++ edit-post/components/plugins-panel/index.js | 4 +- edit-post/index.js | 1 + 6 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 edit-post/api/plugin.js create mode 100644 edit-post/components/plugin-sidebar/index.js diff --git a/edit-post/api/index.js b/edit-post/api/index.js index 3284d43eacacf8..6a3caf47d19374 100644 --- a/edit-post/api/index.js +++ b/edit-post/api/index.js @@ -2,3 +2,6 @@ export { registerSidebar as __experimentalRegisterSidebar, activateSidebar as __experimentalActivateSidebar, } from './sidebar'; +export { + registerPlugin, +} from './plugin'; diff --git a/edit-post/api/plugin.js b/edit-post/api/plugin.js new file mode 100644 index 00000000000000..61a48b0f7264ab --- /dev/null +++ b/edit-post/api/plugin.js @@ -0,0 +1,68 @@ +/* eslint no-console: [ 'error', { allow: [ 'error' ] } ] */ + +/** + * WordPress dependencies + */ +import { applyFilters } from '@wordpress/hooks'; +import { Fragment } from '@wordpress/element'; + +/* External dependencies */ +import { isFunction, map, isEmpty } from 'lodash'; + +/* Internal dependencies */ +// import store from '../store'; +// import { setGeneralSidebarActivePanel, openGeneralSidebar } from '../store/actions'; + +const plugins = {}; + +/** + * Registers a plugin to the editor. + * + * @param {Object} settings The settings for this plugin. + * @param {string} settings.name The name of the plugin. + * @param {Function} settings.render The function that renders the plugin. + * + * @return {Object} The final plugin settings object. + */ +export function registerPlugin( settings ) { + settings = applyFilters( 'editPost.registerPlugin', settings, settings.name ); + + if ( typeof settings.name !== 'string' ) { + console.error( + 'Plugin names must be strings.' + ); + return null; + } + if ( ! /^[a-z][a-z0-9-]*$/.test( settings.name ) ) { + console.error( + 'Plugin names must include only lowercase alphanumeric characters or dashes, and start with a letter. Example: "my-plugin".' + ); + return null; + } + if ( plugins[ settings.name ] ) { + console.error( + `Plugin "${ settings.name }" is already registered.` + ); + } + if ( ! settings || ! isFunction( settings.render ) ) { + console.error( + 'The "render" property must be specified and must be a valid function.' + ); + return null; + } + + return plugins[ settings.name ] = settings; +} + +export function Plugins() { + return ( +
+ + { map( plugins, plugin => { + console.log( plugin.render() ); + return plugin.render(); + } ) } + +
+ ); +} diff --git a/edit-post/components/layout/index.js b/edit-post/components/layout/index.js index 764b64dc3b5581..447c4c37871519 100644 --- a/edit-post/components/layout/index.js +++ b/edit-post/components/layout/index.js @@ -32,6 +32,7 @@ import EditorModeKeyboardShortcuts from '../keyboard-shortcuts'; import MetaBoxes from '../meta-boxes'; import { getMetaBoxContainer } from '../../utils/meta-boxes'; import PluginsPanel from '../../components/plugins-panel/index.js'; +import { Plugins } from '../../api/plugin'; function Layout( { mode, @@ -83,6 +84,7 @@ function Layout( { { editorSidebarOpened && } { pluginSidebarOpened && } +
); } diff --git a/edit-post/components/plugin-sidebar/index.js b/edit-post/components/plugin-sidebar/index.js new file mode 100644 index 00000000000000..97de777fa2f246 --- /dev/null +++ b/edit-post/components/plugin-sidebar/index.js @@ -0,0 +1,44 @@ +/** + * External dependencies + */ +import { noop } from 'lodash'; +import { connect } from 'react-redux'; + +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; +import { Slot, Fill } from '@wordpress/components'; + +/** + * Name of slot in which popover should fill. + * + * @type {String} + */ +const SLOT_NAME = 'PluginSidebar'; + +class PluginSidebar extends Component { + constructor( props ) { + super( props ); + + if ( typeof props.name !== 'string' ) { + throw 'Sidebar names must be strings.'; + } + if ( ! /^[a-z][a-z0-9-]*$/.test( props.name ) ) { + throw 'Sidebar names must include only lowercase alphanumeric characters or dashes, and start with a letter. Example: "my-sidebar".'; + } + } + + render() { + const { title, name, children, activePlugin } = this.props; + return { children }; + } +} + +PluginSidebar.contextTypes = { + getSlot: noop, +}; + +PluginSidebar.Slot = () => ; + +export default PluginSidebar; diff --git a/edit-post/components/plugins-panel/index.js b/edit-post/components/plugins-panel/index.js index 3f693dcaa22bf0..14b88c40f4d684 100644 --- a/edit-post/components/plugins-panel/index.js +++ b/edit-post/components/plugins-panel/index.js @@ -11,6 +11,7 @@ import { compose } from '@wordpress/element'; */ import './style.scss'; import { getSidebarSettings } from '../../api/sidebar'; +import PluginSidebar from '../plugin-sidebar'; function PluginsPanel( { onClose, pluginSidebar } ) { if ( ! pluginSidebar ) { @@ -19,7 +20,6 @@ function PluginsPanel( { onClose, pluginSidebar } ) { const { title, - render, } = pluginSidebar; return ( @@ -37,7 +37,7 @@ function PluginsPanel( { onClose, pluginSidebar } ) { />
- { render() } +
); diff --git a/edit-post/index.js b/edit-post/index.js index ef9bf85f6463f6..1f4ba5fe5085fb 100644 --- a/edit-post/index.js +++ b/edit-post/index.js @@ -21,6 +21,7 @@ import Layout from './components/layout'; import store from './store'; import { initializeMetaBoxState } from './store/actions'; +export { default as PluginSidebar } from './components/plugin-sidebar'; export * from './api'; // Configure moment globally From fe93e2dc1898de792e1039234ee756949aa28005 Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Tue, 6 Mar 2018 10:16:16 +0100 Subject: [PATCH 28/83] Attempt to implement createContext api for plugin slot fill API --- edit-post/api/plugin.js | 51 +++++++++++++++----- edit-post/components/plugin-sidebar/index.js | 6 ++- element/index.js | 3 ++ package.json | 1 + 4 files changed, 48 insertions(+), 13 deletions(-) diff --git a/edit-post/api/plugin.js b/edit-post/api/plugin.js index 61a48b0f7264ab..630713cc085c41 100644 --- a/edit-post/api/plugin.js +++ b/edit-post/api/plugin.js @@ -4,10 +4,10 @@ * WordPress dependencies */ import { applyFilters } from '@wordpress/hooks'; -import { Fragment } from '@wordpress/element'; +import { createContext, Component } from '@wordpress/element'; /* External dependencies */ -import { isFunction, map, isEmpty } from 'lodash'; +import { isFunction, map, noop } from 'lodash'; /* Internal dependencies */ // import store from '../store'; @@ -51,18 +51,47 @@ export function registerPlugin( settings ) { return null; } + plugins.context = createContext(); + return plugins[ settings.name ] = settings; } -export function Plugins() { - return ( -
- +class ContextProvider extends Component { + getChildContext() { + return { + plugin: this.props.plugin, + }; + } + + render() { + return this.props.children; + } +} + +ContextProvider.childContextTypes = { + plugin: noop, +}; + +class Plugins extends Component { + render() { + return ( +
{ map( plugins, plugin => { - console.log( plugin.render() ); - return plugin.render(); + const Context = plugin.context; + return ( + + { plugin.render() } + + ); + // return ( + // + // { plugin.render() } + // + // ); } ) } - -
- ); +
+ ); + } } + +export { Plugins }; diff --git a/edit-post/components/plugin-sidebar/index.js b/edit-post/components/plugin-sidebar/index.js index 97de777fa2f246..d9cb61d880a55f 100644 --- a/edit-post/components/plugin-sidebar/index.js +++ b/edit-post/components/plugin-sidebar/index.js @@ -17,6 +17,8 @@ import { Slot, Fill } from '@wordpress/components'; */ const SLOT_NAME = 'PluginSidebar'; +//TODO Error boundaries + class PluginSidebar extends Component { constructor( props ) { super( props ); @@ -30,13 +32,13 @@ class PluginSidebar extends Component { } render() { - const { title, name, children, activePlugin } = this.props; + const { children } = this.props; return { children }; } } PluginSidebar.contextTypes = { - getSlot: noop, + plugin: noop, }; PluginSidebar.Slot = () => ; diff --git a/element/index.js b/element/index.js index e35c715b077386..17ee2f7c7c6adb 100644 --- a/element/index.js +++ b/element/index.js @@ -11,6 +11,7 @@ import { upperFirst, isEmpty, } from 'lodash'; +import createContext from 'create-react-context'; /** * Returns a new element of given type. Type can be either a string tag name or @@ -26,6 +27,8 @@ import { */ export { createElement }; +export { createContext }; + /** * Renders a given element into the target DOM node. * diff --git a/package.json b/package.json index 21ba6d0adaff84..79486d35bb438e 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@wordpress/url": "1.0.3", "classnames": "2.2.5", "clipboard": "1.7.1", + "create-react-context": "0.2.1", "dom-react": "2.2.0", "dom-scroll-into-view": "1.2.1", "element-closest": "2.0.2", From 0a6ee555c51b2a5f3c65ae35a3b385c2cf4c3698 Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Tue, 6 Mar 2018 10:59:35 +0100 Subject: [PATCH 29/83] Fixed error on start up --- edit-post/api/plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edit-post/api/plugin.js b/edit-post/api/plugin.js index 630713cc085c41..3ba2fd61d2ef61 100644 --- a/edit-post/api/plugin.js +++ b/edit-post/api/plugin.js @@ -51,7 +51,7 @@ export function registerPlugin( settings ) { return null; } - plugins.context = createContext(); + settings.context = createContext(); return plugins[ settings.name ] = settings; } From 499cb71fa7c38abb342a88091983245b33255e7e Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Tue, 6 Mar 2018 14:13:20 +0100 Subject: [PATCH 30/83] Implemented way to pass props from plugin API via context to PluginSidebar component --- edit-post/api/plugin.js | 21 ++++------ edit-post/components/plugin-sidebar/index.js | 42 ++++++++++++++++---- 2 files changed, 42 insertions(+), 21 deletions(-) diff --git a/edit-post/api/plugin.js b/edit-post/api/plugin.js index 3ba2fd61d2ef61..908e8b8bce16ec 100644 --- a/edit-post/api/plugin.js +++ b/edit-post/api/plugin.js @@ -4,10 +4,11 @@ * WordPress dependencies */ import { applyFilters } from '@wordpress/hooks'; -import { createContext, Component } from '@wordpress/element'; +import { Component } from '@wordpress/element'; /* External dependencies */ -import { isFunction, map, noop } from 'lodash'; +import { isFunction, map } from 'lodash'; +import PropTypes from 'prop-types'; /* Internal dependencies */ // import store from '../store'; @@ -51,15 +52,13 @@ export function registerPlugin( settings ) { return null; } - settings.context = createContext(); - return plugins[ settings.name ] = settings; } class ContextProvider extends Component { getChildContext() { return { - plugin: this.props.plugin, + namespace: this.props.namespace, }; } @@ -69,7 +68,7 @@ class ContextProvider extends Component { } ContextProvider.childContextTypes = { - plugin: noop, + namespace: PropTypes.string.isRequired, }; class Plugins extends Component { @@ -77,17 +76,11 @@ class Plugins extends Component { return (
{ map( plugins, plugin => { - const Context = plugin.context; return ( - + { plugin.render() } - + ); - // return ( - // - // { plugin.render() } - // - // ); } ) }
); diff --git a/edit-post/components/plugin-sidebar/index.js b/edit-post/components/plugin-sidebar/index.js index d9cb61d880a55f..63f612d64074f6 100644 --- a/edit-post/components/plugin-sidebar/index.js +++ b/edit-post/components/plugin-sidebar/index.js @@ -1,14 +1,15 @@ /** * External dependencies */ -import { noop } from 'lodash'; import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; /** * WordPress dependencies */ -import { Component } from '@wordpress/element'; +import { Component, Children, cloneElement } from '@wordpress/element'; import { Slot, Fill } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; /** * Name of slot in which popover should fill. @@ -17,7 +18,25 @@ import { Slot, Fill } from '@wordpress/components'; */ const SLOT_NAME = 'PluginSidebar'; -//TODO Error boundaries +class SidebarErrorBoundary extends Component { + constructor( props ) { + super( props ); + this.state = { hasError: false }; + } + + componentDidCatch() { + this.setState( { hasError: true } ); + } + + render() { + if ( this.state.hasError ) { + return

+ { __( 'An error occurred rendering the plugin sidebar.' ) } +

; + } + return this.props.children; + } +} class PluginSidebar extends Component { constructor( props ) { @@ -32,15 +51,24 @@ class PluginSidebar extends Component { } render() { - const { children } = this.props; - return { children }; + const { children, ...props } = this.props; + const newProps = { + ...props, + namespacedName: `${ this.context.namespace }/${ this.props.name }`, + }; + + return ( + + { cloneElement( Children.only( children ), newProps ) } + + ); } } PluginSidebar.contextTypes = { - plugin: noop, + namespace: PropTypes.string.isRequired, }; -PluginSidebar.Slot = () => ; +PluginSidebar.Slot = () => ( ); export default PluginSidebar; From fde16b5fc13796b12f08093c5187ab943a1eea15 Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Tue, 6 Mar 2018 16:26:23 +0100 Subject: [PATCH 31/83] Finalize first pass of registerPlugin API --- edit-post/api/index.js | 2 +- edit-post/api/plugin.js | 16 +++++- edit-post/api/sidebar.js | 29 ---------- edit-post/components/layout/index.js | 4 +- edit-post/components/plugin-sidebar/index.js | 54 ++++++++++++++++--- .../style.scss | 0 edit-post/components/plugins-panel/index.js | 54 ------------------- edit-post/index.js | 2 +- edit-post/store/selectors.js | 7 +-- 9 files changed, 67 insertions(+), 101 deletions(-) rename edit-post/components/{plugins-panel => plugin-sidebar}/style.scss (100%) delete mode 100644 edit-post/components/plugins-panel/index.js diff --git a/edit-post/api/index.js b/edit-post/api/index.js index 6a3caf47d19374..93913d8e66a86c 100644 --- a/edit-post/api/index.js +++ b/edit-post/api/index.js @@ -1,7 +1,7 @@ export { registerSidebar as __experimentalRegisterSidebar, - activateSidebar as __experimentalActivateSidebar, } from './sidebar'; export { registerPlugin, + activateSidebar, } from './plugin'; diff --git a/edit-post/api/plugin.js b/edit-post/api/plugin.js index 908e8b8bce16ec..2952ee5960a5bb 100644 --- a/edit-post/api/plugin.js +++ b/edit-post/api/plugin.js @@ -11,8 +11,10 @@ import { isFunction, map } from 'lodash'; import PropTypes from 'prop-types'; /* Internal dependencies */ -// import store from '../store'; -// import { setGeneralSidebarActivePanel, openGeneralSidebar } from '../store/actions'; +import store from '../store'; +import { + openGeneralSidebar, +} from '../store/actions'; const plugins = {}; @@ -87,4 +89,14 @@ class Plugins extends Component { } } +/** + * Activates the given sidebar. + * + * @param {string} name The name of the sidebar to activate. + * @return {void} + */ +export function activateSidebar( name ) { + store.dispatch( openGeneralSidebar( 'plugin', name ) ); +} + export { Plugins }; diff --git a/edit-post/api/sidebar.js b/edit-post/api/sidebar.js index f1f5eee0890af7..3226b17f41bff9 100644 --- a/edit-post/api/sidebar.js +++ b/edit-post/api/sidebar.js @@ -3,11 +3,6 @@ /* External dependencies */ import { isFunction } from 'lodash'; -/** - * WordPress dependencies - */ -import { dispatch } from '@wordpress/data'; - /* Internal dependencies */ import { applyFilters } from '@wordpress/hooks'; @@ -74,27 +69,3 @@ export function registerSidebar( name, settings ) { return sidebars[ name ] = settings; } - -/** - * Retrieves the sidebar settings object. - * - * @param {string} name The name of the sidebar to retrieve the settings for. - * - * @return {Object} The settings object of the sidebar. Or null if the - * sidebar doesn't exist. - */ -export function getSidebarSettings( name ) { - if ( ! sidebars.hasOwnProperty( name ) ) { - return null; - } - return sidebars[ name ]; -} -/** - * Activates the given sidebar. - * - * @param {string} name The name of the sidebar to activate. - * @return {void} - */ -export function activateSidebar( name ) { - dispatch( 'core/edit-post' ).openGeneralSidebar( name ); -} diff --git a/edit-post/components/layout/index.js b/edit-post/components/layout/index.js index 447c4c37871519..9d7e2a61c469ef 100644 --- a/edit-post/components/layout/index.js +++ b/edit-post/components/layout/index.js @@ -31,8 +31,8 @@ import VisualEditor from '../visual-editor'; import EditorModeKeyboardShortcuts from '../keyboard-shortcuts'; import MetaBoxes from '../meta-boxes'; import { getMetaBoxContainer } from '../../utils/meta-boxes'; -import PluginsPanel from '../../components/plugins-panel/index.js'; import { Plugins } from '../../api/plugin'; +import { PluginSidebarSlot } from '../plugin-sidebar'; function Layout( { mode, @@ -82,7 +82,7 @@ function Layout( { /> ) } { editorSidebarOpened && } - { pluginSidebarOpened && } + { pluginSidebarOpened && } diff --git a/edit-post/components/plugin-sidebar/index.js b/edit-post/components/plugin-sidebar/index.js index 63f612d64074f6..59dff7650a6ebc 100644 --- a/edit-post/components/plugin-sidebar/index.js +++ b/edit-post/components/plugin-sidebar/index.js @@ -7,10 +7,17 @@ import PropTypes from 'prop-types'; /** * WordPress dependencies */ -import { Component, Children, cloneElement } from '@wordpress/element'; -import { Slot, Fill } from '@wordpress/components'; +import { Component, Children, cloneElement, compose } from '@wordpress/element'; +import { Slot, Fill, IconButton, withFocusReturn } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; +/** + * Internal dependencies + */ +import './style.scss'; +import { getActiveGeneralSidebarName } from '../../store/selectors'; +import { closeGeneralSidebar } from '../../store/actions'; + /** * Name of slot in which popover should fill. * @@ -51,15 +58,38 @@ class PluginSidebar extends Component { } render() { + if ( ! this.namespacedName ) { + this.namespacedName = `${ this.context.namespace }/${ this.props.name }`; + } + if ( this.props.openedGeneralSidebar !== this.namespacedName ) { + return null; + } + const { children, ...props } = this.props; const newProps = { ...props, - namespacedName: `${ this.context.namespace }/${ this.props.name }`, + namespacedName: this.namespacedName, }; return ( - { cloneElement( Children.only( children ), newProps ) } +
+
+

{ this.props.title }

+ +
+
+ { cloneElement( Children.only( children ), newProps ) } +
+
); } @@ -69,6 +99,18 @@ PluginSidebar.contextTypes = { namespace: PropTypes.string.isRequired, }; -PluginSidebar.Slot = () => ( ); +const PluginSidebarSlot = () => ( ); + +const PluginSidebarFill = compose( [ + connect( + ( state ) => ( { + openedGeneralSidebar: getActiveGeneralSidebarName( state ), + } ), { + onClose: closeGeneralSidebar, + }, + null, + { storeKey: 'edit-post' } ), + withFocusReturn, +] )( PluginSidebar ); -export default PluginSidebar; +export { PluginSidebarFill, PluginSidebarSlot }; diff --git a/edit-post/components/plugins-panel/style.scss b/edit-post/components/plugin-sidebar/style.scss similarity index 100% rename from edit-post/components/plugins-panel/style.scss rename to edit-post/components/plugin-sidebar/style.scss diff --git a/edit-post/components/plugins-panel/index.js b/edit-post/components/plugins-panel/index.js deleted file mode 100644 index 14b88c40f4d684..00000000000000 --- a/edit-post/components/plugins-panel/index.js +++ /dev/null @@ -1,54 +0,0 @@ -/** - * WordPress dependencies - */ -import { __ } from '@wordpress/i18n'; -import { IconButton, withFocusReturn } from '@wordpress/components'; -import { withDispatch, withSelect } from '@wordpress/data'; -import { compose } from '@wordpress/element'; - -/** - * Internal Dependencies - */ -import './style.scss'; -import { getSidebarSettings } from '../../api/sidebar'; -import PluginSidebar from '../plugin-sidebar'; - -function PluginsPanel( { onClose, pluginSidebar } ) { - if ( ! pluginSidebar ) { - return null; - } - - const { - title, - } = pluginSidebar; - - return ( -
-
-

{ title }

- -
-
- -
-
- ); -} - -export default compose( - withSelect( ( select ) => ( { - pluginSidebar: getSidebarSettings( select( 'core/edit-post' ).getActiveGeneralSidebarName() ), - } ) ), - withDispatch( ( dispatch ) => ( { - onClose: dispatch( 'core/edit-post' ).closeGeneralSidebar, - } ) ), - withFocusReturn, -)( PluginsPanel ); diff --git a/edit-post/index.js b/edit-post/index.js index 1f4ba5fe5085fb..759d578c6f9732 100644 --- a/edit-post/index.js +++ b/edit-post/index.js @@ -21,7 +21,7 @@ import Layout from './components/layout'; import store from './store'; import { initializeMetaBoxState } from './store/actions'; -export { default as PluginSidebar } from './components/plugin-sidebar'; +export { PluginSidebarFill as PluginSidebar } from './components/plugin-sidebar'; export * from './api'; // Configure moment globally diff --git a/edit-post/store/selectors.js b/edit-post/store/selectors.js index ad9274af18629b..f677e1eed92650 100644 --- a/edit-post/store/selectors.js +++ b/edit-post/store/selectors.js @@ -4,11 +4,6 @@ import createSelector from 'rememo'; import { includes, some } from 'lodash'; -/** - * Internal dependencies - */ -import { getSidebarSettings } from '../api/sidebar'; - /** * Returns the current editing mode. * @@ -41,7 +36,7 @@ export function isEditorSidebarOpened( state ) { export function isPluginSidebarOpened( state ) { const activeGeneralSidebar = getPreference( state, 'activeGeneralSidebar', null ); - return Boolean( getSidebarSettings( activeGeneralSidebar ) ); + return activeGeneralSidebar ? activeGeneralSidebar.startsWith( 'plugin/' ) : false; } /** From a268de46c2a8dd70e47921890776be45eaa76e2f Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Tue, 6 Mar 2018 18:15:49 +0100 Subject: [PATCH 32/83] Remove create-react-context --- element/index.js | 3 --- package.json | 1 - 2 files changed, 4 deletions(-) diff --git a/element/index.js b/element/index.js index 17ee2f7c7c6adb..e35c715b077386 100644 --- a/element/index.js +++ b/element/index.js @@ -11,7 +11,6 @@ import { upperFirst, isEmpty, } from 'lodash'; -import createContext from 'create-react-context'; /** * Returns a new element of given type. Type can be either a string tag name or @@ -27,8 +26,6 @@ import createContext from 'create-react-context'; */ export { createElement }; -export { createContext }; - /** * Renders a given element into the target DOM node. * diff --git a/package.json b/package.json index 79486d35bb438e..21ba6d0adaff84 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,6 @@ "@wordpress/url": "1.0.3", "classnames": "2.2.5", "clipboard": "1.7.1", - "create-react-context": "0.2.1", "dom-react": "2.2.0", "dom-scroll-into-view": "1.2.1", "element-closest": "2.0.2", From 8bfcbee6e898f62f5e46e6bf1d8fddbabe4f9d3c Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Wed, 7 Mar 2018 11:21:30 +0100 Subject: [PATCH 33/83] Removed old sidebar and refactored the way plugin context is handled --- edit-post/api/components/context.js | 48 +++++++++++++ edit-post/api/index.js | 4 -- edit-post/api/plugin.js | 48 ++++--------- edit-post/api/sidebar.js | 71 -------------------- edit-post/components/plugin-sidebar/index.js | 8 +-- 5 files changed, 63 insertions(+), 116 deletions(-) create mode 100644 edit-post/api/components/context.js delete mode 100644 edit-post/api/sidebar.js diff --git a/edit-post/api/components/context.js b/edit-post/api/components/context.js new file mode 100644 index 00000000000000..720370c6e1c9dd --- /dev/null +++ b/edit-post/api/components/context.js @@ -0,0 +1,48 @@ +/** + * External dependencies + */ +import PropTypes from 'prop-types'; + +/** + * WordPress dependencies + */ +import { Component, cloneElement, Children } from '@wordpress/element'; + +class PluginContextProvider extends Component { + getChildContext() { + return { + namespace: this.props.namespace, + }; + } + + render() { + return this.props.children; + } +} + +PluginContextProvider.childContextTypes = { + namespace: PropTypes.string.isRequired, +}; + +class PluginContextConsumer extends Component { + render() { + return cloneElement( Children.only( this.props.children ), this.context ); + } +} + +PluginContextConsumer.contextTypes = { + namespace: PropTypes.string.isRequired, +}; + +function withPluginContext( WrappedComponent ) { + return ( props ) => ( + + + + ); +} + +export { + PluginContextProvider, + withPluginContext, +}; diff --git a/edit-post/api/index.js b/edit-post/api/index.js index 93913d8e66a86c..34eee0f07b3868 100644 --- a/edit-post/api/index.js +++ b/edit-post/api/index.js @@ -1,7 +1,3 @@ -export { - registerSidebar as __experimentalRegisterSidebar, -} from './sidebar'; export { registerPlugin, - activateSidebar, } from './plugin'; diff --git a/edit-post/api/plugin.js b/edit-post/api/plugin.js index 2952ee5960a5bb..63757ad23e83c4 100644 --- a/edit-post/api/plugin.js +++ b/edit-post/api/plugin.js @@ -8,13 +8,11 @@ import { Component } from '@wordpress/element'; /* External dependencies */ import { isFunction, map } from 'lodash'; -import PropTypes from 'prop-types'; -/* Internal dependencies */ -import store from '../store'; -import { - openGeneralSidebar, -} from '../store/actions'; +/** + * Internal dependencies + */ +import { PluginContextProvider, withPluginContext } from './components/context'; const plugins = {}; @@ -27,7 +25,7 @@ const plugins = {}; * * @return {Object} The final plugin settings object. */ -export function registerPlugin( settings ) { +function registerPlugin( settings ) { settings = applyFilters( 'editPost.registerPlugin', settings, settings.name ); if ( typeof settings.name !== 'string' ) { @@ -57,31 +55,15 @@ export function registerPlugin( settings ) { return plugins[ settings.name ] = settings; } -class ContextProvider extends Component { - getChildContext() { - return { - namespace: this.props.namespace, - }; - } - - render() { - return this.props.children; - } -} - -ContextProvider.childContextTypes = { - namespace: PropTypes.string.isRequired, -}; - class Plugins extends Component { render() { return (
{ map( plugins, plugin => { return ( - + { plugin.render() } - + ); } ) }
@@ -89,14 +71,8 @@ class Plugins extends Component { } } -/** - * Activates the given sidebar. - * - * @param {string} name The name of the sidebar to activate. - * @return {void} - */ -export function activateSidebar( name ) { - store.dispatch( openGeneralSidebar( 'plugin', name ) ); -} - -export { Plugins }; +export { + Plugins, + withPluginContext, + registerPlugin, +}; diff --git a/edit-post/api/sidebar.js b/edit-post/api/sidebar.js deleted file mode 100644 index 3226b17f41bff9..00000000000000 --- a/edit-post/api/sidebar.js +++ /dev/null @@ -1,71 +0,0 @@ -/* eslint no-console: [ 'error', { allow: [ 'error' ] } ] */ - -/* External dependencies */ -import { isFunction } from 'lodash'; - -/* Internal dependencies */ -import { applyFilters } from '@wordpress/hooks'; - -const sidebars = {}; - -/** - * Registers a sidebar to the editor. - * - * A button will be shown in the settings menu to open the sidebar. The sidebar - * can be manually opened by calling the `activateSidebar` function. - * - * @param {string} name The name of the sidebar. Should be in - * `[plugin]/[sidebar]` format. - * @param {Object} settings The settings for this sidebar. - * @param {string} settings.title The name to show in the settings menu. - * @param {Function} settings.render The function that renders the sidebar. - * - * @return {Object} The final sidebar settings object. - */ -export function registerSidebar( name, settings ) { - settings = { - name, - ...settings, - }; - - if ( typeof name !== 'string' ) { - console.error( - 'Sidebar names must be strings.' - ); - return null; - } - 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.' - ); - return null; - } - if ( ! settings || ! isFunction( settings.render ) ) { - console.error( - 'The "render" property must be specified and must be a valid function.' - ); - return null; - } - if ( sidebars[ name ] ) { - console.error( - `Sidebar ${ name } is already registered.` - ); - } - - if ( ! settings.title ) { - console.error( - `The sidebar ${ name } must have a title.` - ); - return null; - } - if ( typeof settings.title !== 'string' ) { - console.error( - 'Sidebar titles must be strings.' - ); - return null; - } - - settings = applyFilters( 'editor.registerSidebar', settings, name ); - - return sidebars[ name ] = settings; -} diff --git a/edit-post/components/plugin-sidebar/index.js b/edit-post/components/plugin-sidebar/index.js index 59dff7650a6ebc..ecf2a063473066 100644 --- a/edit-post/components/plugin-sidebar/index.js +++ b/edit-post/components/plugin-sidebar/index.js @@ -17,6 +17,7 @@ import { __ } from '@wordpress/i18n'; import './style.scss'; import { getActiveGeneralSidebarName } from '../../store/selectors'; import { closeGeneralSidebar } from '../../store/actions'; +import { withPluginContext } from '../../api/plugin'; /** * Name of slot in which popover should fill. @@ -59,7 +60,7 @@ class PluginSidebar extends Component { render() { if ( ! this.namespacedName ) { - this.namespacedName = `${ this.context.namespace }/${ this.props.name }`; + this.namespacedName = `${ this.props.namespace }/${ this.props.name }`; } if ( this.props.openedGeneralSidebar !== this.namespacedName ) { return null; @@ -95,10 +96,6 @@ class PluginSidebar extends Component { } } -PluginSidebar.contextTypes = { - namespace: PropTypes.string.isRequired, -}; - const PluginSidebarSlot = () => ( ); const PluginSidebarFill = compose( [ @@ -111,6 +108,7 @@ const PluginSidebarFill = compose( [ null, { storeKey: 'edit-post' } ), withFocusReturn, + withPluginContext, ] )( PluginSidebar ); export { PluginSidebarFill, PluginSidebarSlot }; From ef02a0c9521d832a9aaaab710d20f0517b4017f7 Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Wed, 7 Mar 2018 11:38:06 +0100 Subject: [PATCH 34/83] Improved withPluginContext HOC to prevent usage of cloneElement --- edit-post/api/components/context.js | 27 +++++++++++++-------------- edit-post/api/plugin.js | 4 ++-- edit-post/components/layout/index.js | 4 ++-- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/edit-post/api/components/context.js b/edit-post/api/components/context.js index 720370c6e1c9dd..44727f6ac51368 100644 --- a/edit-post/api/components/context.js +++ b/edit-post/api/components/context.js @@ -6,7 +6,7 @@ import PropTypes from 'prop-types'; /** * WordPress dependencies */ -import { Component, cloneElement, Children } from '@wordpress/element'; +import { Component } from '@wordpress/element'; class PluginContextProvider extends Component { getChildContext() { @@ -24,22 +24,21 @@ PluginContextProvider.childContextTypes = { namespace: PropTypes.string.isRequired, }; -class PluginContextConsumer extends Component { - render() { - return cloneElement( Children.only( this.props.children ), this.context ); +function withPluginContext( WrappedComponent ) { + class PluginContextConsumer extends Component { + render() { + return ; + } } -} -PluginContextConsumer.contextTypes = { - namespace: PropTypes.string.isRequired, -}; + PluginContextConsumer.contextTypes = { + namespace: PropTypes.string.isRequired, + }; -function withPluginContext( WrappedComponent ) { - return ( props ) => ( - - - - ); + return PluginContextConsumer; } export { diff --git a/edit-post/api/plugin.js b/edit-post/api/plugin.js index 63757ad23e83c4..e15b5ba619394f 100644 --- a/edit-post/api/plugin.js +++ b/edit-post/api/plugin.js @@ -55,7 +55,7 @@ function registerPlugin( settings ) { return plugins[ settings.name ] = settings; } -class Plugins extends Component { +class PluginFills extends Component { render() { return (
@@ -72,7 +72,7 @@ class Plugins extends Component { } export { - Plugins, + PluginFills, withPluginContext, registerPlugin, }; diff --git a/edit-post/components/layout/index.js b/edit-post/components/layout/index.js index 9d7e2a61c469ef..fe818f5613d18a 100644 --- a/edit-post/components/layout/index.js +++ b/edit-post/components/layout/index.js @@ -31,7 +31,7 @@ import VisualEditor from '../visual-editor'; import EditorModeKeyboardShortcuts from '../keyboard-shortcuts'; import MetaBoxes from '../meta-boxes'; import { getMetaBoxContainer } from '../../utils/meta-boxes'; -import { Plugins } from '../../api/plugin'; +import { PluginFills } from '../../api/plugin'; import { PluginSidebarSlot } from '../plugin-sidebar'; function Layout( { @@ -84,7 +84,7 @@ function Layout( { { editorSidebarOpened && } { pluginSidebarOpened && } - +
); } From c2e0f205088bf377cf97e09481967db8b7953696 Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Wed, 7 Mar 2018 12:23:11 +0100 Subject: [PATCH 35/83] Implemented tests for registerPlugin --- edit-post/api/plugin.js | 12 +++++++--- edit-post/api/test/plugin.js | 46 ++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 edit-post/api/test/plugin.js diff --git a/edit-post/api/plugin.js b/edit-post/api/plugin.js index e15b5ba619394f..750ef05a699320 100644 --- a/edit-post/api/plugin.js +++ b/edit-post/api/plugin.js @@ -26,8 +26,12 @@ const plugins = {}; * @return {Object} The final plugin settings object. */ function registerPlugin( settings ) { - settings = applyFilters( 'editPost.registerPlugin', settings, settings.name ); - + if ( typeof settings !== 'object' ) { + console.error( + 'No settings object provided!' + ); + return null; + } if ( typeof settings.name !== 'string' ) { console.error( 'Plugin names must be strings.' @@ -45,13 +49,15 @@ function registerPlugin( settings ) { `Plugin "${ settings.name }" is already registered.` ); } - if ( ! settings || ! isFunction( settings.render ) ) { + if ( ! isFunction( settings.render ) ) { console.error( 'The "render" property must be specified and must be a valid function.' ); return null; } + settings = applyFilters( 'editPost.registerPlugin', settings, settings.name ); + return plugins[ settings.name ] = settings; } diff --git a/edit-post/api/test/plugin.js b/edit-post/api/test/plugin.js new file mode 100644 index 00000000000000..abd468d28d06ad --- /dev/null +++ b/edit-post/api/test/plugin.js @@ -0,0 +1,46 @@ +import { registerPlugin } from '../plugin'; + +describe( 'registerPlugin', () => { + it( 'successfully registers a plugin', () => { + registerPlugin( { + name: 'plugin', + render: () => 'plugin content', + } ); + } ); + + it( 'fails to register a plugin without a settings object', () => { + registerPlugin(); + expect( console ).toHaveErroredWith( 'No settings object provided!' ); + } ); + + it( 'fails to register a plugin with special character in the name', () => { + registerPlugin( { + name: 'plugin/with/special/characters', + render: () => {}, + } ); + expect( console ).toHaveErroredWith( 'Plugin names must include only lowercase alphanumeric characters or dashes, and start with a letter. Example: "my-plugin".' ); + } ); + + it( 'fails to register a plugin with a non-string name', () => { + registerPlugin( { + name: () => {}, + render: () => {}, + } ); + expect( console ).toHaveErroredWith( 'Plugin names must be strings.' ); + } ); + + it( 'fails to register a plugin without a render function', () => { + registerPlugin( { + name: 'another-plugin', + } ); + expect( console ).toHaveErroredWith( 'The "render" property must be specified and must be a valid function.' ); + } ); + + it( 'fails to register a plugin that was already been registered', () => { + registerPlugin( { + name: 'plugin', + render: () => 'plugin content', + } ); + expect( console ).toHaveErroredWith( 'Plugin "plugin" is already registered.' ); + } ); +} ); \ No newline at end of file From 528bcdc4c00cb57f30baa5d4e767733d95e36441 Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Wed, 7 Mar 2018 14:00:23 +0100 Subject: [PATCH 36/83] Added test for plugin/context components, revamped naming of passed context --- edit-post/api/components/context.js | 6 ++-- edit-post/api/components/test/context.js | 33 ++++++++++++++++++++ edit-post/api/plugin.js | 2 +- edit-post/components/plugin-sidebar/index.js | 2 +- 4 files changed, 38 insertions(+), 5 deletions(-) create mode 100644 edit-post/api/components/test/context.js diff --git a/edit-post/api/components/context.js b/edit-post/api/components/context.js index 44727f6ac51368..7c9b404d087ff7 100644 --- a/edit-post/api/components/context.js +++ b/edit-post/api/components/context.js @@ -11,7 +11,7 @@ import { Component } from '@wordpress/element'; class PluginContextProvider extends Component { getChildContext() { return { - namespace: this.props.namespace, + pluginContext: this.props.value, }; } @@ -21,7 +21,7 @@ class PluginContextProvider extends Component { } PluginContextProvider.childContextTypes = { - namespace: PropTypes.string.isRequired, + pluginContext: PropTypes.any.isRequired, }; function withPluginContext( WrappedComponent ) { @@ -35,7 +35,7 @@ function withPluginContext( WrappedComponent ) { } PluginContextConsumer.contextTypes = { - namespace: PropTypes.string.isRequired, + pluginContext: PropTypes.any.isRequired, }; return PluginContextConsumer; diff --git a/edit-post/api/components/test/context.js b/edit-post/api/components/test/context.js new file mode 100644 index 00000000000000..9a439a4c3b9f14 --- /dev/null +++ b/edit-post/api/components/test/context.js @@ -0,0 +1,33 @@ +/** + * External dependencies + */ +import ReactTestRenderer from 'react-test-renderer'; + +/** + * Internal dependencies + */ +import { PluginContextProvider, withPluginContext } from '../context'; + +describe( 'plugin/context', () => { + describe( 'withPluginContext', () => { + it( 'passed the plugin context to the decorated component', () => { + const Component = ( props ) => { + return props.pluginContext; + }; + + const WrappedComponent = withPluginContext( Component ); + + const renderer = ReactTestRenderer.create( + +
+ +
+
+ ); + + const tree = renderer.toJSON(); + + expect( tree.children[ 0 ] ).toEqual( 'plugin-namespace' ); + } ); + } ); +} ); diff --git a/edit-post/api/plugin.js b/edit-post/api/plugin.js index 750ef05a699320..0be9b737140322 100644 --- a/edit-post/api/plugin.js +++ b/edit-post/api/plugin.js @@ -67,7 +67,7 @@ class PluginFills extends Component {
{ map( plugins, plugin => { return ( - + { plugin.render() } ); diff --git a/edit-post/components/plugin-sidebar/index.js b/edit-post/components/plugin-sidebar/index.js index ecf2a063473066..b21e0073b23a8b 100644 --- a/edit-post/components/plugin-sidebar/index.js +++ b/edit-post/components/plugin-sidebar/index.js @@ -60,7 +60,7 @@ class PluginSidebar extends Component { render() { if ( ! this.namespacedName ) { - this.namespacedName = `${ this.props.namespace }/${ this.props.name }`; + this.namespacedName = `${ this.props.pluginContext.namespace }/${ this.props.name }`; } if ( this.props.openedGeneralSidebar !== this.namespacedName ) { return null; From 893dff80090b82eff8ec424fcc905bf6bd36940c Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Wed, 7 Mar 2018 14:03:53 +0100 Subject: [PATCH 37/83] Fixed eslint issues --- edit-post/api/test/plugin.js | 2 +- edit-post/components/plugin-sidebar/index.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/edit-post/api/test/plugin.js b/edit-post/api/test/plugin.js index abd468d28d06ad..0aaa1bd35358d7 100644 --- a/edit-post/api/test/plugin.js +++ b/edit-post/api/test/plugin.js @@ -43,4 +43,4 @@ describe( 'registerPlugin', () => { } ); expect( console ).toHaveErroredWith( 'Plugin "plugin" is already registered.' ); } ); -} ); \ No newline at end of file +} ); diff --git a/edit-post/components/plugin-sidebar/index.js b/edit-post/components/plugin-sidebar/index.js index b21e0073b23a8b..7a9f147366cc76 100644 --- a/edit-post/components/plugin-sidebar/index.js +++ b/edit-post/components/plugin-sidebar/index.js @@ -2,7 +2,6 @@ * External dependencies */ import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; /** * WordPress dependencies From 86049a9c69f9ffe75b53cba929af078a1a74107a Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Wed, 7 Mar 2018 14:39:49 +0100 Subject: [PATCH 38/83] Refactored plugin-sidebar to use withSelect/withDispatch and created separate sidebar layout component --- edit-post/components/plugin-sidebar/index.js | 51 +++++++------------ .../plugin-sidebar/sidebar-layout.js | 31 +++++++++++ 2 files changed, 49 insertions(+), 33 deletions(-) create mode 100644 edit-post/components/plugin-sidebar/sidebar-layout.js diff --git a/edit-post/components/plugin-sidebar/index.js b/edit-post/components/plugin-sidebar/index.js index 7a9f147366cc76..33981af0a22e41 100644 --- a/edit-post/components/plugin-sidebar/index.js +++ b/edit-post/components/plugin-sidebar/index.js @@ -1,22 +1,17 @@ -/** - * External dependencies - */ -import { connect } from 'react-redux'; - /** * WordPress dependencies */ import { Component, Children, cloneElement, compose } from '@wordpress/element'; -import { Slot, Fill, IconButton, withFocusReturn } from '@wordpress/components'; +import { Slot, Fill, withFocusReturn } from '@wordpress/components'; +import { withSelect, withDispatch } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ import './style.scss'; -import { getActiveGeneralSidebarName } from '../../store/selectors'; -import { closeGeneralSidebar } from '../../store/actions'; import { withPluginContext } from '../../api/plugin'; +import SidebarLayout from './sidebar-layout'; /** * Name of slot in which popover should fill. @@ -73,23 +68,11 @@ class PluginSidebar extends Component { return ( -
-
-

{ this.props.title }

- -
-
- { cloneElement( Children.only( children ), newProps ) } -
-
+ + { cloneElement( Children.only( children ), newProps ) } +
); } @@ -98,14 +81,16 @@ class PluginSidebar extends Component { const PluginSidebarSlot = () => ( ); const PluginSidebarFill = compose( [ - connect( - ( state ) => ( { - openedGeneralSidebar: getActiveGeneralSidebarName( state ), - } ), { - onClose: closeGeneralSidebar, - }, - null, - { storeKey: 'edit-post' } ), + withSelect( select => { + return { + activePlugin: select( 'core/edit-post' ).getActiveGeneralSidebarName(), + }; + } ), + withDispatch( dispatch => { + return { + onClose: dispatch( 'core/edit-post' ).closeGeneralSidebar, + }; + } ), withFocusReturn, withPluginContext, ] )( PluginSidebar ); diff --git a/edit-post/components/plugin-sidebar/sidebar-layout.js b/edit-post/components/plugin-sidebar/sidebar-layout.js new file mode 100644 index 00000000000000..edbcb5134bd666 --- /dev/null +++ b/edit-post/components/plugin-sidebar/sidebar-layout.js @@ -0,0 +1,31 @@ +import './style.scss'; + +/** + * WordPress dependencies + */ +import { IconButton } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +const SidebarLayout = ( { onClose, title, children } ) => { + return ( +
+
+

{ title }

+ +
+
+ { children } +
+
+ ); +}; + +export default SidebarLayout; From 8d00bf4fa9c54ab6ad8bda6198631a2e75f2f287 Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Wed, 7 Mar 2018 14:41:26 +0100 Subject: [PATCH 39/83] Removed PropTypes --- edit-post/api/components/context.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/edit-post/api/components/context.js b/edit-post/api/components/context.js index 7c9b404d087ff7..90df3a15e5ba3a 100644 --- a/edit-post/api/components/context.js +++ b/edit-post/api/components/context.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import PropTypes from 'prop-types'; +import { noop } from 'lodash'; /** * WordPress dependencies @@ -21,7 +21,7 @@ class PluginContextProvider extends Component { } PluginContextProvider.childContextTypes = { - pluginContext: PropTypes.any.isRequired, + pluginContext: noop, }; function withPluginContext( WrappedComponent ) { @@ -35,7 +35,7 @@ function withPluginContext( WrappedComponent ) { } PluginContextConsumer.contextTypes = { - pluginContext: PropTypes.any.isRequired, + pluginContext: noop, }; return PluginContextConsumer; From 5b22af7f4b7141e742275e701b860cec5a2ba97c Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Wed, 7 Mar 2018 15:25:59 +0100 Subject: [PATCH 40/83] Used enzyme instead of react-test-renderer in plugin context tests --- edit-post/api/components/test/context.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/edit-post/api/components/test/context.js b/edit-post/api/components/test/context.js index 9a439a4c3b9f14..a5b72980c3fc75 100644 --- a/edit-post/api/components/test/context.js +++ b/edit-post/api/components/test/context.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import ReactTestRenderer from 'react-test-renderer'; +import { mount } from 'enzyme'; /** * Internal dependencies @@ -17,7 +17,7 @@ describe( 'plugin/context', () => { const WrappedComponent = withPluginContext( Component ); - const renderer = ReactTestRenderer.create( + const MountedComponent = mount(
@@ -25,9 +25,7 @@ describe( 'plugin/context', () => { ); - const tree = renderer.toJSON(); - - expect( tree.children[ 0 ] ).toEqual( 'plugin-namespace' ); + expect( MountedComponent.find( Component ).props().pluginContext ).toEqual( 'plugin-namespace' ); } ); } ); } ); From bb552ef0ce03cdc21e4cd5b7c985c4f91c44857f Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Wed, 7 Mar 2018 15:39:07 +0100 Subject: [PATCH 41/83] Hidden internal Slot and Fill components in layout --- edit-post/api/plugin.js | 7 +++++-- edit-post/components/layout/index.js | 8 ++++---- edit-post/components/plugin-sidebar/index.js | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/edit-post/api/plugin.js b/edit-post/api/plugin.js index 0be9b737140322..6df85817c541bc 100644 --- a/edit-post/api/plugin.js +++ b/edit-post/api/plugin.js @@ -61,7 +61,10 @@ function registerPlugin( settings ) { return plugins[ settings.name ] = settings; } -class PluginFills extends Component { +/** + * A component that renders all plugin fills in a hidden div. + */ +class PluginArea extends Component { render() { return (
@@ -78,7 +81,7 @@ class PluginFills extends Component { } export { - PluginFills, + PluginArea, withPluginContext, registerPlugin, }; diff --git a/edit-post/components/layout/index.js b/edit-post/components/layout/index.js index fe818f5613d18a..f0f77cfb99bf3c 100644 --- a/edit-post/components/layout/index.js +++ b/edit-post/components/layout/index.js @@ -31,8 +31,8 @@ import VisualEditor from '../visual-editor'; import EditorModeKeyboardShortcuts from '../keyboard-shortcuts'; import MetaBoxes from '../meta-boxes'; import { getMetaBoxContainer } from '../../utils/meta-boxes'; -import { PluginFills } from '../../api/plugin'; -import { PluginSidebarSlot } from '../plugin-sidebar'; +import { PluginArea } from '../../api/plugin'; +import { PluginSidebar } from '../plugin-sidebar'; function Layout( { mode, @@ -82,9 +82,9 @@ function Layout( { /> ) } { editorSidebarOpened && } - { pluginSidebarOpened && } + { pluginSidebarOpened && } - +
); } diff --git a/edit-post/components/plugin-sidebar/index.js b/edit-post/components/plugin-sidebar/index.js index 33981af0a22e41..3828f8e2abccfa 100644 --- a/edit-post/components/plugin-sidebar/index.js +++ b/edit-post/components/plugin-sidebar/index.js @@ -95,4 +95,4 @@ const PluginSidebarFill = compose( [ withPluginContext, ] )( PluginSidebar ); -export { PluginSidebarFill, PluginSidebarSlot }; +export { PluginSidebarFill, PluginSidebarSlot as PluginSidebar }; From d4da2c57dd9a3d8eb2ccb1ceed6c54a87b606aec Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Wed, 7 Mar 2018 17:13:05 +0100 Subject: [PATCH 42/83] Moved plugin-sidebar error-boundary to separate file --- .../plugin-sidebar/error-boundary.js | 27 ++++++++++++++++++ edit-post/components/plugin-sidebar/index.js | 28 ++++--------------- 2 files changed, 33 insertions(+), 22 deletions(-) create mode 100644 edit-post/components/plugin-sidebar/error-boundary.js diff --git a/edit-post/components/plugin-sidebar/error-boundary.js b/edit-post/components/plugin-sidebar/error-boundary.js new file mode 100644 index 00000000000000..9593596b32ebed --- /dev/null +++ b/edit-post/components/plugin-sidebar/error-boundary.js @@ -0,0 +1,27 @@ +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +class SidebarErrorBoundary extends Component { + constructor( props ) { + super( props ); + this.state = { hasError: false }; + } + + componentDidCatch() { + this.setState( { hasError: true } ); + } + + render() { + if ( this.state.hasError ) { + return

+ { __( 'An error occurred rendering the plugin sidebar.' ) } +

; + } + return this.props.children; + } +} + +export default SidebarErrorBoundary; diff --git a/edit-post/components/plugin-sidebar/index.js b/edit-post/components/plugin-sidebar/index.js index 3828f8e2abccfa..035caa67ebf30a 100644 --- a/edit-post/components/plugin-sidebar/index.js +++ b/edit-post/components/plugin-sidebar/index.js @@ -4,7 +4,6 @@ import { Component, Children, cloneElement, compose } from '@wordpress/element'; import { Slot, Fill, withFocusReturn } from '@wordpress/components'; import { withSelect, withDispatch } from '@wordpress/data'; -import { __ } from '@wordpress/i18n'; /** * Internal dependencies @@ -12,6 +11,7 @@ import { __ } from '@wordpress/i18n'; import './style.scss'; import { withPluginContext } from '../../api/plugin'; import SidebarLayout from './sidebar-layout'; +import ErrorBoundary from './error-boundary'; /** * Name of slot in which popover should fill. @@ -20,26 +20,6 @@ import SidebarLayout from './sidebar-layout'; */ const SLOT_NAME = 'PluginSidebar'; -class SidebarErrorBoundary extends Component { - constructor( props ) { - super( props ); - this.state = { hasError: false }; - } - - componentDidCatch() { - this.setState( { hasError: true } ); - } - - render() { - if ( this.state.hasError ) { - return

- { __( 'An error occurred rendering the plugin sidebar.' ) } -

; - } - return this.props.children; - } -} - class PluginSidebar extends Component { constructor( props ) { super( props ); @@ -78,7 +58,11 @@ class PluginSidebar extends Component { } } -const PluginSidebarSlot = () => ( ); +const PluginSidebarSlot = () => ( + + + +); const PluginSidebarFill = compose( [ withSelect( select => { From b110d7017b691d6fcda567f189a3b21f379e8abe Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Wed, 7 Mar 2018 17:54:06 +0100 Subject: [PATCH 43/83] Moved sidebar error boundary around the component only so the error can be rendered within the sidebar --- edit-post/components/plugin-sidebar/error-boundary.js | 4 ++-- edit-post/components/plugin-sidebar/index.js | 8 ++++---- edit-post/components/plugin-sidebar/style.scss | 4 ++++ 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/edit-post/components/plugin-sidebar/error-boundary.js b/edit-post/components/plugin-sidebar/error-boundary.js index 9593596b32ebed..3cce18b752cfb3 100644 --- a/edit-post/components/plugin-sidebar/error-boundary.js +++ b/edit-post/components/plugin-sidebar/error-boundary.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { Component } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; class SidebarErrorBoundary extends Component { constructor( props ) { @@ -17,7 +17,7 @@ class SidebarErrorBoundary extends Component { render() { if ( this.state.hasError ) { return

- { __( 'An error occurred rendering the plugin sidebar.' ) } + { sprintf( __( 'An error occurred rendering the plugin sidebar with id "%s".' ), this.props.pluginName ) }

; } return this.props.children; diff --git a/edit-post/components/plugin-sidebar/index.js b/edit-post/components/plugin-sidebar/index.js index 035caa67ebf30a..6b5613862747bb 100644 --- a/edit-post/components/plugin-sidebar/index.js +++ b/edit-post/components/plugin-sidebar/index.js @@ -51,7 +51,9 @@ class PluginSidebar extends Component { - { cloneElement( Children.only( children ), newProps ) } + + { cloneElement( Children.only( children ), newProps ) } + ); @@ -59,9 +61,7 @@ class PluginSidebar extends Component { } const PluginSidebarSlot = () => ( - - - + ); const PluginSidebarFill = compose( [ diff --git a/edit-post/components/plugin-sidebar/style.scss b/edit-post/components/plugin-sidebar/style.scss index 9a1e1dfb5a4f80..0b8372a48714ad 100644 --- a/edit-post/components/plugin-sidebar/style.scss +++ b/edit-post/components/plugin-sidebar/style.scss @@ -18,4 +18,8 @@ } } +.plugin-sidebar-error { + padding: 16px; + color: red; +} From b4dfc8eed4b45d8c9f36e8acf09d5f06be283b8a Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Wed, 7 Mar 2018 18:31:53 +0100 Subject: [PATCH 44/83] Updated documentation --- edit-post/api/README.md | 69 +++++++++++++------- edit-post/components/plugin-sidebar/index.js | 10 ++- 2 files changed, 56 insertions(+), 23 deletions(-) diff --git a/edit-post/api/README.md b/edit-post/api/README.md index 22537eab2210d6..f904a0e66406f1 100644 --- a/edit-post/api/README.md +++ b/edit-post/api/README.md @@ -3,35 +3,60 @@ Edit post API The edit post API contains the following methods: -### `wp.editPost.__experimentalRegisterSidebar( name: string, settings: { title: string, render: function } )` +## `wp.editPost.registerPlugin( { name: string, render: function } )` -**Warning:** This is an experimental API, and is subject to change or even removal. - -This method takes two arguments: -- a `name` to identify the sidebar. This name should contain a namespace prefix, followed by a slash and a sidebar name. The name should include only lowercase alphanumeric characters or dashes, and start with a letter. Example: `my-plugin/my-custom-sidebar`. -- a `settings` object, containing a title and a render function. - -This method only registers a sidebar. To open the sidebar, use the `__experimentalActivateSidebar` method below. - -#### Example: +This method takes one argument: +- An object containing the following data: + - `name`: A string identifying the plugin. Must be unique across all registered plugins. + - `render`: A component containing the UI elements to be rendered. See the list below for all available UI elements. + +### Example ```js -wp.editPost.__experimentalRegisterSidebar( 'my-plugin/my-custom-sidebar', { - render: function mySidebar() { - return

This is an example

; - }, +const Fragment = wp.element.Fragment; +const PluginSidebar = wp.editPost.PluginSidebar; + +const Component = () => ( + + +

Content of the first sidebar

+
+ +

Content of the second sidebar

+
+
+); + +wp.editPost.registerPlugin( { + name: 'plugin-name', + render: Component, } ); ``` -### `wp.editPost.__experimentalActivateSidebar( name: string )` +You can activate the sidebars using the following line: -**Warning:** This is an experimental API, and is subject to change or even removal. +`wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/first-sidebar-name' );` +`wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/second-sidebar-name' );` -This method takes one argument: -- the `name` of the sidebar you'd like to open. That sidebar should have been registered beforehand using the `__experimentalRegisterSidebar` method. + +### Available UI components -#### Example: +The available UI components are available under `wp.editPost`, and are React components. -```js -wp.editPost.__experimentalActivateSidebar( 'my-plugin/my-custom-sidebar' ); -``` +#### PluginSidebar + +Renders a sidebar when activated. + +`{ contents }` + +- Props + - `name`: A string identifying the sidebar. Must be unique for every sidebar registered iwthin the scope of your plugin. + - `title`: Title displayed at the top of the sidebar. Must be a string. + +The contents you render within the `PluginSidebar` will show up as content within the sidebar. + +The sidebar can be activated using the data api: + +`wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/sidebar-name' );` + +Notice that you need to use both the plugin name and sidebar name separated by a `/` to show the correct sidebar. diff --git a/edit-post/components/plugin-sidebar/index.js b/edit-post/components/plugin-sidebar/index.js index 6b5613862747bb..7b610b5f0a38b3 100644 --- a/edit-post/components/plugin-sidebar/index.js +++ b/edit-post/components/plugin-sidebar/index.js @@ -14,12 +14,15 @@ import SidebarLayout from './sidebar-layout'; import ErrorBoundary from './error-boundary'; /** - * Name of slot in which popover should fill. + * Name of slot in which the sidebar should fill. * * @type {String} */ const SLOT_NAME = 'PluginSidebar'; +/** + * The plugin sidebar fill. + */ class PluginSidebar extends Component { constructor( props ) { super( props ); @@ -60,6 +63,11 @@ class PluginSidebar extends Component { } } +/** + * The plugin sidebar slot. + * + * @return {ReactElement} The plugin sidebar slot. + */ const PluginSidebarSlot = () => ( ); From c173637334471622d82e4e7f014af3c9572a9f78 Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Wed, 7 Mar 2018 18:59:07 +0100 Subject: [PATCH 45/83] Updated documentation --- docs/extensibility.md | 67 +++++++++++++++++++++++++++++------------ edit-post/api/README.md | 2 +- 2 files changed, 48 insertions(+), 21 deletions(-) diff --git a/docs/extensibility.md b/docs/extensibility.md index 4f64d4348c8885..352e4ebe49d164 100644 --- a/docs/extensibility.md +++ b/docs/extensibility.md @@ -120,38 +120,65 @@ wp.hooks.addFilter( _Note:_ This filter must always be run on every page load, and not in your browser's developer tools console. Otherwise, a [block validation](https://wordpress.org/gutenberg/handbook/block-api/block-edit-save/#validation) error will occur the next time the post is edited. This is due to the fact that block validation occurs by verifying that the saved output matches what is stored in the post's content during editor initialization. So, if this filter does not exist when the editor loads, the block will be marked as invalid. -## Adding a sidebar - -**Warning:** This is an experimental API, and is subject to change or even removal. - -### Registering a sidebar +## Extending the editor's UI (Slot and Fill) -`wp.editPost.__experimentalRegisterSidebar( name: string, settings: { title: string, render: function } )` +Extending the editor's UI is done with in `registerPlugin` API, and allows you to define all your plugin's UI elements in one place. -This method takes a sidebar `name` and a `settings` object, containing a title and a render function. The name should contain a namespace prefix (Example: my-plugin/my-custom-sidebar). +`wp.editPost.registerPlugin( { name: string, render: function } )` +This method takes one argument: +- An object containing the following data: + - `name`: A string identifying the plugin. Must be unique across all registered plugins. + - `render`: A component containing the UI elements to be rendered. See the list below for all available UI elements. + **Example** ```js -wp.editPost.__experimentalRegisterSidebar( 'my-plugin/my-custom-sidebar', { - render: function mySidebar() { - return

This is an example

; - }, +const Fragment = wp.element.Fragment; +const PluginSidebar = wp.editPost.PluginSidebar; + +const Component = () => ( + + +

Content of the first sidebar

+
+ +

Content of the second sidebar

+
+
+); + +wp.editPost.registerPlugin( { + name: 'plugin-name', + render: Component, } ); ``` -### Activating a sidebar +You can activate the sidebars using the following lines: -`wp.editPost.__experimentalActivateSidebar( name: string )` +`wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/first-sidebar-name' );` +`wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/second-sidebar-name' );` -This method takes the `name` of the sidebar you'd like to open. That sidebar should have been registered beforehand using the `registerSidebar` method. + +### Available UI components -**Example** +The available UI components are available under `wp.editPost`, and are React components. -```js -wp.editPost.__experimentalActivateSidebar( 'my-plugin/my-custom-sidebar' ); -``` +#### PluginSidebar -## Extending the editor's UI (Slot and Fill) +Renders a sidebar when activated. + +`{ contents }` + +- Props + - `name`: A string identifying the sidebar. Must be unique for every sidebar registered iwthin the scope of your plugin. + - `title`: Title displayed at the top of the sidebar. Must be a string. + +The contents you render within the `PluginSidebar` will show up as content within the sidebar. + +The sidebar can be activated using the data api: + +`wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/sidebar-name' );` + +Notice that you need to use both the plugin name and sidebar name separated by a `/` to show the correct sidebar. -Coming soon. diff --git a/edit-post/api/README.md b/edit-post/api/README.md index f904a0e66406f1..d8bdb5471fb5f0 100644 --- a/edit-post/api/README.md +++ b/edit-post/api/README.md @@ -33,7 +33,7 @@ wp.editPost.registerPlugin( { } ); ``` -You can activate the sidebars using the following line: +You can activate the sidebars using the following lines: `wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/first-sidebar-name' );` `wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/second-sidebar-name' );` From 47ee1999253e4c79efc71fab7068664a47d45836 Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Thu, 8 Mar 2018 07:25:14 +0100 Subject: [PATCH 46/83] Updated documentation --- docs/extensibility.md | 5 ++--- edit-post/api/README.md | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/extensibility.md b/docs/extensibility.md index 352e4ebe49d164..d4a559eb54e943 100644 --- a/docs/extensibility.md +++ b/docs/extensibility.md @@ -162,7 +162,7 @@ You can activate the sidebars using the following lines: ### Available UI components -The available UI components are available under `wp.editPost`, and are React components. +The available UI components are found in the global variable `wp.editPost`, and are React components. #### PluginSidebar @@ -171,7 +171,7 @@ Renders a sidebar when activated. `{ contents }` - Props - - `name`: A string identifying the sidebar. Must be unique for every sidebar registered iwthin the scope of your plugin. + - `name`: A string identifying the sidebar. Must be unique for every sidebar registered within the scope of your plugin. - `title`: Title displayed at the top of the sidebar. Must be a string. The contents you render within the `PluginSidebar` will show up as content within the sidebar. @@ -181,4 +181,3 @@ The sidebar can be activated using the data api: `wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/sidebar-name' );` Notice that you need to use both the plugin name and sidebar name separated by a `/` to show the correct sidebar. - diff --git a/edit-post/api/README.md b/edit-post/api/README.md index d8bdb5471fb5f0..2b64867c95913d 100644 --- a/edit-post/api/README.md +++ b/edit-post/api/README.md @@ -10,7 +10,7 @@ This method takes one argument: - `name`: A string identifying the plugin. Must be unique across all registered plugins. - `render`: A component containing the UI elements to be rendered. See the list below for all available UI elements. -### Example +**Example** ```js const Fragment = wp.element.Fragment; @@ -41,7 +41,7 @@ You can activate the sidebars using the following lines: ### Available UI components -The available UI components are available under `wp.editPost`, and are React components. +The available UI components are found in the global variable `wp.editPost`, and are React components. #### PluginSidebar @@ -50,7 +50,7 @@ Renders a sidebar when activated. `{ contents }` - Props - - `name`: A string identifying the sidebar. Must be unique for every sidebar registered iwthin the scope of your plugin. + - `name`: A string identifying the sidebar. Must be unique for every sidebar registered within the scope of your plugin. - `title`: Title displayed at the top of the sidebar. Must be a string. The contents you render within the `PluginSidebar` will show up as content within the sidebar. From d56a58d6d709d85125c20a072f854c403807f985 Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Thu, 8 Mar 2018 22:41:12 +0100 Subject: [PATCH 47/83] Refactored editpost/api/context component to reparate folder --- edit-post/api/components/{context.js => context/index.js} | 0 edit-post/api/components/{ => context}/test/context.js | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename edit-post/api/components/{context.js => context/index.js} (100%) rename edit-post/api/components/{ => context}/test/context.js (100%) diff --git a/edit-post/api/components/context.js b/edit-post/api/components/context/index.js similarity index 100% rename from edit-post/api/components/context.js rename to edit-post/api/components/context/index.js diff --git a/edit-post/api/components/test/context.js b/edit-post/api/components/context/test/context.js similarity index 100% rename from edit-post/api/components/test/context.js rename to edit-post/api/components/context/test/context.js From 646f72cdf0cbb167c3b2ac9440657dc402d3cbf5 Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Thu, 8 Mar 2018 22:57:10 +0100 Subject: [PATCH 48/83] Moved plugin components to api folder and made API experimental --- .../components/plugin-sidebar/error-boundary.js | 0 .../{ => api}/components/plugin-sidebar/index.js | 2 +- .../components/plugin-sidebar/sidebar-layout.js | 0 .../{ => api}/components/plugin-sidebar/style.scss | 0 edit-post/api/index.js | 12 ++++++++++-- edit-post/index.js | 2 +- 6 files changed, 12 insertions(+), 4 deletions(-) rename edit-post/{ => api}/components/plugin-sidebar/error-boundary.js (100%) rename edit-post/{ => api}/components/plugin-sidebar/index.js (97%) rename edit-post/{ => api}/components/plugin-sidebar/sidebar-layout.js (100%) rename edit-post/{ => api}/components/plugin-sidebar/style.scss (100%) diff --git a/edit-post/components/plugin-sidebar/error-boundary.js b/edit-post/api/components/plugin-sidebar/error-boundary.js similarity index 100% rename from edit-post/components/plugin-sidebar/error-boundary.js rename to edit-post/api/components/plugin-sidebar/error-boundary.js diff --git a/edit-post/components/plugin-sidebar/index.js b/edit-post/api/components/plugin-sidebar/index.js similarity index 97% rename from edit-post/components/plugin-sidebar/index.js rename to edit-post/api/components/plugin-sidebar/index.js index 7b610b5f0a38b3..42befab58562cb 100644 --- a/edit-post/components/plugin-sidebar/index.js +++ b/edit-post/api/components/plugin-sidebar/index.js @@ -9,7 +9,7 @@ import { withSelect, withDispatch } from '@wordpress/data'; * Internal dependencies */ import './style.scss'; -import { withPluginContext } from '../../api/plugin'; +import { withPluginContext } from '../../plugin'; import SidebarLayout from './sidebar-layout'; import ErrorBoundary from './error-boundary'; diff --git a/edit-post/components/plugin-sidebar/sidebar-layout.js b/edit-post/api/components/plugin-sidebar/sidebar-layout.js similarity index 100% rename from edit-post/components/plugin-sidebar/sidebar-layout.js rename to edit-post/api/components/plugin-sidebar/sidebar-layout.js diff --git a/edit-post/components/plugin-sidebar/style.scss b/edit-post/api/components/plugin-sidebar/style.scss similarity index 100% rename from edit-post/components/plugin-sidebar/style.scss rename to edit-post/api/components/plugin-sidebar/style.scss diff --git a/edit-post/api/index.js b/edit-post/api/index.js index 34eee0f07b3868..113b013e880695 100644 --- a/edit-post/api/index.js +++ b/edit-post/api/index.js @@ -1,3 +1,11 @@ -export { +import { registerPlugin } from './plugin'; +import { PluginSidebarFill as PluginSidebar } from './components/plugin-sidebar'; + +const __experimental = { registerPlugin, -} from './plugin'; + PluginSidebar, +}; + +export { + __experimental, +}; diff --git a/edit-post/index.js b/edit-post/index.js index 759d578c6f9732..13a72075090f04 100644 --- a/edit-post/index.js +++ b/edit-post/index.js @@ -21,7 +21,7 @@ import Layout from './components/layout'; import store from './store'; import { initializeMetaBoxState } from './store/actions'; -export { PluginSidebarFill as PluginSidebar } from './components/plugin-sidebar'; +export { PluginSidebarFill as PluginSidebar } from './api/components/plugin-sidebar'; export * from './api'; // Configure moment globally From 60d174cee6f8ca9932a4a5dabd6b0d2d306e9c84 Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Thu, 8 Mar 2018 23:00:28 +0100 Subject: [PATCH 49/83] Updated docs to reflect experimental status --- docs/extensibility.md | 8 ++++---- edit-post/api/README.md | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/extensibility.md b/docs/extensibility.md index d4a559eb54e943..a94bf2a0b261dd 100644 --- a/docs/extensibility.md +++ b/docs/extensibility.md @@ -124,7 +124,7 @@ _Note:_ This filter must always be run on every page load, and not in your brows Extending the editor's UI is done with in `registerPlugin` API, and allows you to define all your plugin's UI elements in one place. -`wp.editPost.registerPlugin( { name: string, render: function } )` +`wp.editPost.__experimental.registerPlugin( { name: string, render: function } )` This method takes one argument: - An object containing the following data: @@ -135,7 +135,7 @@ This method takes one argument: ```js const Fragment = wp.element.Fragment; -const PluginSidebar = wp.editPost.PluginSidebar; +const PluginSidebar = wp.editPost.__experimental.PluginSidebar; const Component = () => ( @@ -148,7 +148,7 @@ const Component = () => ( ); -wp.editPost.registerPlugin( { +wp.editPost.__experimental.registerPlugin( { name: 'plugin-name', render: Component, } ); @@ -162,7 +162,7 @@ You can activate the sidebars using the following lines: ### Available UI components -The available UI components are found in the global variable `wp.editPost`, and are React components. +The available UI components are found in the global variable `wp.editPost.__experimental`, and are React components. #### PluginSidebar diff --git a/edit-post/api/README.md b/edit-post/api/README.md index 2b64867c95913d..d127d64511135d 100644 --- a/edit-post/api/README.md +++ b/edit-post/api/README.md @@ -3,7 +3,7 @@ Edit post API The edit post API contains the following methods: -## `wp.editPost.registerPlugin( { name: string, render: function } )` +## `wp.editPost.__experimental.registerPlugin( { name: string, render: function } )` This method takes one argument: - An object containing the following data: @@ -14,7 +14,7 @@ This method takes one argument: ```js const Fragment = wp.element.Fragment; -const PluginSidebar = wp.editPost.PluginSidebar; +const PluginSidebar = wp.editPost.__experimental.PluginSidebar; const Component = () => ( @@ -27,7 +27,7 @@ const Component = () => ( ); -wp.editPost.registerPlugin( { +wp.editPost.__experimental.registerPlugin( { name: 'plugin-name', render: Component, } ); @@ -41,7 +41,7 @@ You can activate the sidebars using the following lines: ### Available UI components -The available UI components are found in the global variable `wp.editPost`, and are React components. +The available UI components are found in the global variable `wp.editPost.__experimental`, and are React components. #### PluginSidebar From 42652a59e84cb79085a4530b72c8acf7028e22dc Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Thu, 8 Mar 2018 23:24:18 +0100 Subject: [PATCH 50/83] Updated documentation --- docs/extensibility.md | 29 +++++++++++++---------------- edit-post/api/README.md | 27 ++++++++++++--------------- 2 files changed, 25 insertions(+), 31 deletions(-) diff --git a/docs/extensibility.md b/docs/extensibility.md index a94bf2a0b261dd..85f7d420de6033 100644 --- a/docs/extensibility.md +++ b/docs/extensibility.md @@ -120,11 +120,11 @@ wp.hooks.addFilter( _Note:_ This filter must always be run on every page load, and not in your browser's developer tools console. Otherwise, a [block validation](https://wordpress.org/gutenberg/handbook/block-api/block-edit-save/#validation) error will occur the next time the post is edited. This is due to the fact that block validation occurs by verifying that the saved output matches what is stored in the post's content during editor initialization. So, if this filter does not exist when the editor loads, the block will be marked as invalid. -## Extending the editor's UI (Slot and Fill) +## Extending the editor's UI (Experimental) Extending the editor's UI is done with in `registerPlugin` API, and allows you to define all your plugin's UI elements in one place. -`wp.editPost.__experimental.registerPlugin( { name: string, render: function } )` +### `wp.editPost.__experimental.registerPlugin( { name: string, render: function } )` This method takes one argument: - An object containing the following data: @@ -140,10 +140,10 @@ const PluginSidebar = wp.editPost.__experimental.PluginSidebar; const Component = () => ( -

Content of the first sidebar

+

Content of the first sidebar

-

Content of the second sidebar

+

Content of the second sidebar

); @@ -156,9 +156,10 @@ wp.editPost.__experimental.registerPlugin( { You can activate the sidebars using the following lines: -`wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/first-sidebar-name' );` -`wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/second-sidebar-name' );` - +```js +wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/first-sidebar-name' ); +wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/second-sidebar-name' ); +``` ### Available UI components @@ -167,17 +168,13 @@ The available UI components are found in the global variable `wp.editPost.__expe #### PluginSidebar Renders a sidebar when activated. - -`{ contents }` - +```js + + + +``` - Props - `name`: A string identifying the sidebar. Must be unique for every sidebar registered within the scope of your plugin. - `title`: Title displayed at the top of the sidebar. Must be a string. The contents you render within the `PluginSidebar` will show up as content within the sidebar. - -The sidebar can be activated using the data api: - -`wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/sidebar-name' );` - -Notice that you need to use both the plugin name and sidebar name separated by a `/` to show the correct sidebar. diff --git a/edit-post/api/README.md b/edit-post/api/README.md index d127d64511135d..2e5c81e7d1636d 100644 --- a/edit-post/api/README.md +++ b/edit-post/api/README.md @@ -3,7 +3,7 @@ Edit post API The edit post API contains the following methods: -## `wp.editPost.__experimental.registerPlugin( { name: string, render: function } )` +### `wp.editPost.__experimental.registerPlugin( { name: string, render: function } )` This method takes one argument: - An object containing the following data: @@ -19,10 +19,10 @@ const PluginSidebar = wp.editPost.__experimental.PluginSidebar; const Component = () => ( -

Content of the first sidebar

+

Content of the first sidebar

-

Content of the second sidebar

+

Content of the second sidebar

); @@ -35,9 +35,10 @@ wp.editPost.__experimental.registerPlugin( { You can activate the sidebars using the following lines: -`wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/first-sidebar-name' );` -`wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/second-sidebar-name' );` - +```js +wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/first-sidebar-name' ); +wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/second-sidebar-name' ); +``` ### Available UI components @@ -46,17 +47,13 @@ The available UI components are found in the global variable `wp.editPost.__expe #### PluginSidebar Renders a sidebar when activated. - -`{ contents }` - +```js + + + +``` - Props - `name`: A string identifying the sidebar. Must be unique for every sidebar registered within the scope of your plugin. - `title`: Title displayed at the top of the sidebar. Must be a string. The contents you render within the `PluginSidebar` will show up as content within the sidebar. - -The sidebar can be activated using the data api: - -`wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/sidebar-name' );` - -Notice that you need to use both the plugin name and sidebar name separated by a `/` to show the correct sidebar. From 210d0aba4b801eddcf8d254259b851ff8e2e977f Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Thu, 8 Mar 2018 23:28:36 +0100 Subject: [PATCH 51/83] Fixed failing test --- edit-post/api/components/context/test/context.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edit-post/api/components/context/test/context.js b/edit-post/api/components/context/test/context.js index a5b72980c3fc75..af864d0cdd6a93 100644 --- a/edit-post/api/components/context/test/context.js +++ b/edit-post/api/components/context/test/context.js @@ -6,7 +6,7 @@ import { mount } from 'enzyme'; /** * Internal dependencies */ -import { PluginContextProvider, withPluginContext } from '../context'; +import { PluginContextProvider, withPluginContext } from '../index'; describe( 'plugin/context', () => { describe( 'withPluginContext', () => { From f082bf0ae67fa069c6ab02219820ce5acdfdc2cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20=28Greg=29=20Zi=C3=B3=C5=82kowski?= Date: Fri, 9 Mar 2018 08:04:27 +0100 Subject: [PATCH 52/83] Docs: Add a few tweaks to the Extensibility doc --- docs/extensibility.md | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/docs/extensibility.md b/docs/extensibility.md index 85f7d420de6033..f8d524d6db17b6 100644 --- a/docs/extensibility.md +++ b/docs/extensibility.md @@ -137,44 +137,42 @@ This method takes one argument: const Fragment = wp.element.Fragment; const PluginSidebar = wp.editPost.__experimental.PluginSidebar; -const Component = () => ( +const MyPluginComponent = () => ( - -

Content of the first sidebar

-
- -

Content of the second sidebar

-
+ +

Content of the first sidebar

+
+ +

Content of the second sidebar

+
); wp.editPost.__experimental.registerPlugin( { - name: 'plugin-name', - render: Component, + name: 'my-plugin-name', + render: MyPluginComponent, } ); ``` - -You can activate the sidebars using the following lines: - -```js -wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/first-sidebar-name' ); -wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/second-sidebar-name' ); -``` ### Available UI components -The available UI components are found in the global variable `wp.editPost.__experimental`, and are React components. +The available UI components are found in the global variable `wp.editPost.__experimental`, and are WrordPress components. #### PluginSidebar Renders a sidebar when activated. ```js - - + +

Content of the first sidebar

``` - Props - `name`: A string identifying the sidebar. Must be unique for every sidebar registered within the scope of your plugin. - `title`: Title displayed at the top of the sidebar. Must be a string. +You can activate the sidebar using the following statement: +```js +wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'my-plugin-name/first-sidebar-name' ); +``` The contents you render within the `PluginSidebar` will show up as content within the sidebar. + From b629a858b9da52e5a7d80e1506607d93c0962a68 Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Mon, 12 Mar 2018 17:18:02 +0100 Subject: [PATCH 53/83] Rebase master --- edit-post/components/layout/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edit-post/components/layout/index.js b/edit-post/components/layout/index.js index f0f77cfb99bf3c..5146b3a9ebbda9 100644 --- a/edit-post/components/layout/index.js +++ b/edit-post/components/layout/index.js @@ -32,7 +32,7 @@ import EditorModeKeyboardShortcuts from '../keyboard-shortcuts'; import MetaBoxes from '../meta-boxes'; import { getMetaBoxContainer } from '../../utils/meta-boxes'; import { PluginArea } from '../../api/plugin'; -import { PluginSidebar } from '../plugin-sidebar'; +import { PluginSidebar } from '../../api/components/plugin-sidebar'; function Layout( { mode, From 1f6b5f547f493cf1e2491143cef3e4d44b6d498e Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Tue, 13 Mar 2018 10:33:26 +0100 Subject: [PATCH 54/83] Register UI components in plugin.js --- .../api/components/plugin-sidebar/index.js | 6 ++- edit-post/api/index.js | 3 +- edit-post/api/plugin.js | 43 ++++++++++++++++++- edit-post/store/selectors.js | 5 ++- 4 files changed, 53 insertions(+), 4 deletions(-) diff --git a/edit-post/api/components/plugin-sidebar/index.js b/edit-post/api/components/plugin-sidebar/index.js index 7f45eece9ad418..bbf61812ebe4b5 100644 --- a/edit-post/api/components/plugin-sidebar/index.js +++ b/edit-post/api/components/plugin-sidebar/index.js @@ -35,10 +35,14 @@ class PluginSidebar extends Component { } } - render() { + componentWillMount() { if ( ! this.namespacedName ) { this.namespacedName = `${ this.props.pluginContext.namespace }/${ this.props.name }`; } + this.props.pluginContext.registerUIComponent( this.namespacedName, 'sidebar' ); + } + + render() { if ( this.props.activePlugin !== this.namespacedName ) { return null; } diff --git a/edit-post/api/index.js b/edit-post/api/index.js index 113b013e880695..3c454518779241 100644 --- a/edit-post/api/index.js +++ b/edit-post/api/index.js @@ -1,9 +1,10 @@ -import { registerPlugin } from './plugin'; +import { registerPlugin, getRegisteredUIComponent } from './plugin'; import { PluginSidebarFill as PluginSidebar } from './components/plugin-sidebar'; const __experimental = { registerPlugin, PluginSidebar, + getRegisteredUIComponent, }; export { diff --git a/edit-post/api/plugin.js b/edit-post/api/plugin.js index 6df85817c541bc..6e53f02d0f24a9 100644 --- a/edit-post/api/plugin.js +++ b/edit-post/api/plugin.js @@ -15,6 +15,7 @@ import { isFunction, map } from 'lodash'; import { PluginContextProvider, withPluginContext } from './components/context'; const plugins = {}; +const pluginUIComponents = {}; /** * Registers a plugin to the editor. @@ -56,11 +57,46 @@ function registerPlugin( settings ) { return null; } + settings.sidebar = {}; + settings = applyFilters( 'editPost.registerPlugin', settings, settings.name ); return plugins[ settings.name ] = settings; } +/** + * A callback called by the UI components via context to register the UI component. + * + * @param {string} pluginName The plugin name. + * @param {string} uiNamespacedName The unique ui plugin identifier. + * @param {string} uiType The UI type. + */ +function registerUIComponent( pluginName, uiNamespacedName, uiType ) { + pluginUIComponents[ uiNamespacedName ] = { + pluginName, + uiType, + }; +} + +/** + * Get the registered plugin information, null if it doesn't exist. + * + * @param {string} uiNamespacedName The unique ui plugin identifier. + * @param {string?} uiType Optional UI type, will only return if ui type matches. + * + * @return {Object|null} Object containing plugin information null if the plugin doesn't exist. + */ +function getRegisteredUIComponent( uiNamespacedName, uiType = null ) { + const uiComponent = pluginUIComponents[ uiNamespacedName ] || null; + if ( uiType ) { + if ( uiComponent && uiComponent.uiType === uiType ) { + return uiComponent; + } + return null; + } + return uiComponent; +} + /** * A component that renders all plugin fills in a hidden div. */ @@ -69,8 +105,12 @@ class PluginArea extends Component { return (
{ map( plugins, plugin => { + const boundRegisterUIComponent = registerUIComponent.bind( null, plugin.name ); return ( - + { plugin.render() } ); @@ -84,4 +124,5 @@ export { PluginArea, withPluginContext, registerPlugin, + getRegisteredUIComponent, }; diff --git a/edit-post/store/selectors.js b/edit-post/store/selectors.js index f677e1eed92650..d534385997a3ac 100644 --- a/edit-post/store/selectors.js +++ b/edit-post/store/selectors.js @@ -3,6 +3,7 @@ */ import createSelector from 'rememo'; import { includes, some } from 'lodash'; +import { __experimental } from '../api'; /** * Returns the current editing mode. @@ -36,7 +37,9 @@ export function isEditorSidebarOpened( state ) { export function isPluginSidebarOpened( state ) { const activeGeneralSidebar = getPreference( state, 'activeGeneralSidebar', null ); - return activeGeneralSidebar ? activeGeneralSidebar.startsWith( 'plugin/' ) : false; + const uiComponent = __experimental.getRegisteredUIComponent( activeGeneralSidebar, 'sidebar' ); + + return !! uiComponent; } /** From 295d410b2ba71c604ea05c12a6b6eeae91fad682 Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Tue, 13 Mar 2018 11:58:02 +0100 Subject: [PATCH 55/83] Updated tests --- edit-post/store/test/selectors.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/edit-post/store/test/selectors.js b/edit-post/store/test/selectors.js index 0c4db388ccf468..7d878c6fb33b43 100644 --- a/edit-post/store/test/selectors.js +++ b/edit-post/store/test/selectors.js @@ -13,10 +13,15 @@ import { isSavingMetaBoxes, getMetaBox, } from '../selectors'; -import { getSidebarSettings as getSidebarSettingsMock } from '../../api/sidebar'; +import { getRegisteredUIComponent as getRegisteredUIComponentMock } from '../../api/plugin'; -jest.mock( '../../api/sidebar', () => ( { - getSidebarSettings: jest.fn().mockReturnValue( null ), +jest.mock( '@wordpress/element', () => ( { + compose: jest.fn().mockReturnValue( jest.fn() ), + Component: jest.fn(), + createElement: jest.fn(), +} ) ); +jest.mock( '../../api/plugin', () => ( { + getRegisteredUIComponent: jest.fn().mockReturnValue( null ), } ) ); describe( 'selectors', () => { @@ -118,9 +123,9 @@ describe( 'selectors', () => { } ); it( 'should return true when the plugin sidebar is opened', () => { - getSidebarSettingsMock.mockReturnValueOnce( { - title: 'My Sidebar', - render: () => 'My Sidebar', + getRegisteredUIComponentMock.mockReturnValueOnce( { + pluginName: 'my-plugin', + uiType: 'sidebar', } ); const name = 'my-plugin/my-sidebar'; const state = { @@ -130,7 +135,7 @@ describe( 'selectors', () => { }; expect( isPluginSidebarOpened( state ) ).toBe( true ); - expect( getSidebarSettingsMock ).toHaveBeenCalledWith( name ); + expect( getRegisteredUIComponentMock ).toHaveBeenCalledWith( name, 'sidebar' ); } ); } ); From 474ff5523285d6e732998642702f96a7c2141efd Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Tue, 13 Mar 2018 14:07:46 +0100 Subject: [PATCH 56/83] created PluginRegistry singleton --- edit-post/api/index.js | 8 +- edit-post/api/plugin.js | 172 +++++++++++++++++------------- edit-post/api/test/plugin.js | 14 ++- edit-post/store/test/selectors.js | 9 +- 4 files changed, 120 insertions(+), 83 deletions(-) diff --git a/edit-post/api/index.js b/edit-post/api/index.js index 3c454518779241..08eb2862cd339e 100644 --- a/edit-post/api/index.js +++ b/edit-post/api/index.js @@ -1,10 +1,12 @@ -import { registerPlugin, getRegisteredUIComponent } from './plugin'; +import { PluginRegistry } from './plugin'; import { PluginSidebarFill as PluginSidebar } from './components/plugin-sidebar'; +const registry = PluginRegistry.getInstance(); + const __experimental = { - registerPlugin, + registerPlugin: registry.registerPlugin, + getRegisteredUIComponent: registry.getRegisteredUIComponent, PluginSidebar, - getRegisteredUIComponent, }; export { diff --git a/edit-post/api/plugin.js b/edit-post/api/plugin.js index 6e53f02d0f24a9..70b2500479a614 100644 --- a/edit-post/api/plugin.js +++ b/edit-post/api/plugin.js @@ -14,87 +14,106 @@ import { isFunction, map } from 'lodash'; */ import { PluginContextProvider, withPluginContext } from './components/context'; -const plugins = {}; -const pluginUIComponents = {}; +class PluginRegistry { + constructor() { + this.plugins = {}; + this.pluginUIComponents = {}; -/** - * Registers a plugin to the editor. - * - * @param {Object} settings The settings for this plugin. - * @param {string} settings.name The name of the plugin. - * @param {Function} settings.render The function that renders the plugin. - * - * @return {Object} The final plugin settings object. - */ -function registerPlugin( settings ) { - if ( typeof settings !== 'object' ) { - console.error( - 'No settings object provided!' - ); - return null; - } - if ( typeof settings.name !== 'string' ) { - console.error( - 'Plugin names must be strings.' - ); - return null; - } - if ( ! /^[a-z][a-z0-9-]*$/.test( settings.name ) ) { - console.error( - 'Plugin names must include only lowercase alphanumeric characters or dashes, and start with a letter. Example: "my-plugin".' - ); - return null; - } - if ( plugins[ settings.name ] ) { - console.error( - `Plugin "${ settings.name }" is already registered.` - ); - } - if ( ! isFunction( settings.render ) ) { - console.error( - 'The "render" property must be specified and must be a valid function.' - ); - return null; + this.registerPlugin = this.registerPlugin.bind( this ); + this.getRegisteredUIComponent = this.getRegisteredUIComponent.bind( this ); } - settings.sidebar = {}; + /** + * Registers a plugin to the editor. + * + * @param {Object} settings The settings for this plugin. + * @param {string} settings.name The name of the plugin. + * @param {Function} settings.render The function that renders the plugin. + * + * @return {Object} The final plugin settings object. + */ + registerPlugin( settings ) { + if ( typeof settings !== 'object' ) { + console.error( + 'No settings object provided!' + ); + return null; + } + if ( typeof settings.name !== 'string' ) { + console.error( + 'Plugin names must be strings.' + ); + return null; + } + if ( ! /^[a-z][a-z0-9-]*$/.test( settings.name ) ) { + console.error( + 'Plugin names must include only lowercase alphanumeric characters or dashes, and start with a letter. Example: "my-plugin".' + ); + return null; + } + if ( this.plugins[ settings.name ] ) { + console.error( + `Plugin "${ settings.name }" is already registered.` + ); + } + if ( ! isFunction( settings.render ) ) { + console.error( + 'The "render" property must be specified and must be a valid function.' + ); + return null; + } - settings = applyFilters( 'editPost.registerPlugin', settings, settings.name ); + settings.sidebar = {}; - return plugins[ settings.name ] = settings; -} + settings = applyFilters( 'editPost.registerPlugin', settings, settings.name ); -/** - * A callback called by the UI components via context to register the UI component. - * - * @param {string} pluginName The plugin name. - * @param {string} uiNamespacedName The unique ui plugin identifier. - * @param {string} uiType The UI type. - */ -function registerUIComponent( pluginName, uiNamespacedName, uiType ) { - pluginUIComponents[ uiNamespacedName ] = { - pluginName, - uiType, - }; -} + return this.plugins[ settings.name ] = settings; + } -/** - * Get the registered plugin information, null if it doesn't exist. - * - * @param {string} uiNamespacedName The unique ui plugin identifier. - * @param {string?} uiType Optional UI type, will only return if ui type matches. - * - * @return {Object|null} Object containing plugin information null if the plugin doesn't exist. - */ -function getRegisteredUIComponent( uiNamespacedName, uiType = null ) { - const uiComponent = pluginUIComponents[ uiNamespacedName ] || null; - if ( uiType ) { - if ( uiComponent && uiComponent.uiType === uiType ) { - return uiComponent; + /** + * A callback called by the UI components via context to register the UI component. + * + * @param {string} pluginName The plugin name. + * @param {string} uiNamespacedName The unique ui plugin identifier. + * @param {string} uiType The UI type. + */ + registerUIComponent( pluginName, uiNamespacedName, uiType ) { + this.pluginUIComponents[ uiNamespacedName ] = { + pluginName, + uiType, + }; + } + + /** + * Get the registered plugin information, null if it doesn't exist. + * + * @param {string} uiNamespacedName The unique ui plugin identifier. + * @param {string?} uiType Optional UI type, will only return if ui type matches. + * + * @return {Object|null} Object containing plugin information null if the plugin doesn't exist. + */ + getRegisteredUIComponent( uiNamespacedName, uiType = null ) { + const uiComponent = this.pluginUIComponents[ uiNamespacedName ] || null; + if ( uiType ) { + if ( uiComponent && uiComponent.uiType === uiType ) { + return uiComponent; + } + return null; + } + return uiComponent; + } + + static getInstance() { + if ( ! this.instance ) { + this.instance = new PluginRegistry(); } - return null; + return this.instance; + } + + static resetInstance() { + this.instance = new PluginRegistry(); + return this.instance; } - return uiComponent; } /** @@ -102,10 +121,12 @@ function getRegisteredUIComponent( uiNamespacedName, uiType = null ) { */ class PluginArea extends Component { render() { + const pluginRegistry = PluginRegistry.getInstance(); + return (
- { map( plugins, plugin => { - const boundRegisterUIComponent = registerUIComponent.bind( null, plugin.name ); + { map( pluginRegistry.plugins, plugin => { + const boundRegisterUIComponent = pluginRegistry.registerUIComponent.bind( pluginRegistry, plugin.name ); return ( { + const registry = PluginRegistry.resetInstance(); + registerPlugin = registry.registerPlugin; +} ); describe( 'registerPlugin', () => { it( 'successfully registers a plugin', () => { @@ -41,6 +48,11 @@ describe( 'registerPlugin', () => { name: 'plugin', render: () => 'plugin content', } ); + registerPlugin( { + name: 'plugin', + render: () => 'plugin content', + } ); + console.log( console ); // eslint-disable-line expect( console ).toHaveErroredWith( 'Plugin "plugin" is already registered.' ); } ); } ); diff --git a/edit-post/store/test/selectors.js b/edit-post/store/test/selectors.js index 7d878c6fb33b43..3555a05697e35a 100644 --- a/edit-post/store/test/selectors.js +++ b/edit-post/store/test/selectors.js @@ -13,15 +13,18 @@ import { isSavingMetaBoxes, getMetaBox, } from '../selectors'; -import { getRegisteredUIComponent as getRegisteredUIComponentMock } from '../../api/plugin'; +import { __experimental } from '../../api'; +const getRegisteredUIComponentMock = __experimental.getRegisteredUIComponent; jest.mock( '@wordpress/element', () => ( { compose: jest.fn().mockReturnValue( jest.fn() ), Component: jest.fn(), createElement: jest.fn(), } ) ); -jest.mock( '../../api/plugin', () => ( { - getRegisteredUIComponent: jest.fn().mockReturnValue( null ), +jest.mock( '../../api', () => ( { + __experimental: { + getRegisteredUIComponent: jest.fn().mockReturnValue( null ), + }, } ) ); describe( 'selectors', () => { From b8096844c6cb3c579dd617ffbc713771c73a1cb1 Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Tue, 13 Mar 2018 14:13:39 +0100 Subject: [PATCH 57/83] Updated JSDoc --- edit-post/api/components/plugin-sidebar/index.js | 10 ++++++++++ edit-post/api/plugin.js | 8 ++++++++ 2 files changed, 18 insertions(+) diff --git a/edit-post/api/components/plugin-sidebar/index.js b/edit-post/api/components/plugin-sidebar/index.js index bbf61812ebe4b5..e3a373bc8623af 100644 --- a/edit-post/api/components/plugin-sidebar/index.js +++ b/edit-post/api/components/plugin-sidebar/index.js @@ -35,6 +35,11 @@ class PluginSidebar extends Component { } } + /** + * Generates the UI plugin identifier by combining the plugin name and the sidebar name. + * + * Also registers the plugin in the plugin registry. + */ componentWillMount() { if ( ! this.namespacedName ) { this.namespacedName = `${ this.props.pluginContext.namespace }/${ this.props.name }`; @@ -42,6 +47,11 @@ class PluginSidebar extends Component { this.props.pluginContext.registerUIComponent( this.namespacedName, 'sidebar' ); } + /** + * Renders the PluginSidebar component. + * + * @returns {ReactElement} The rendered component. + */ render() { if ( this.props.activePlugin !== this.namespacedName ) { return null; diff --git a/edit-post/api/plugin.js b/edit-post/api/plugin.js index 70b2500479a614..ed95e4da1450da 100644 --- a/edit-post/api/plugin.js +++ b/edit-post/api/plugin.js @@ -14,6 +14,9 @@ import { isFunction, map } from 'lodash'; */ import { PluginContextProvider, withPluginContext } from './components/context'; +/** + * Singleton class for registering plugins. + */ class PluginRegistry { constructor() { this.plugins = {}; @@ -103,6 +106,11 @@ class PluginRegistry { return uiComponent; } + /** + * Get singleton instance. + * + * @return {PluginRegistry} PluginRegistry instance. + */ static getInstance() { if ( ! this.instance ) { this.instance = new PluginRegistry(); From 20eb71a0aeb3fcc1b25d193bbad7d7c8510d4fda Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Tue, 13 Mar 2018 14:28:56 +0100 Subject: [PATCH 58/83] Made name first parameter of registerPlugin function instead of being part of teh settings object --- docs/extensibility.md | 51 ++++++++++--------- edit-post/api/README.md | 17 +++---- .../api/components/plugin-sidebar/index.js | 2 +- edit-post/api/plugin.js | 15 +++--- edit-post/api/test/plugin.js | 19 +++---- 5 files changed, 49 insertions(+), 55 deletions(-) diff --git a/docs/extensibility.md b/docs/extensibility.md index f8d524d6db17b6..2f79f0700d59e3 100644 --- a/docs/extensibility.md +++ b/docs/extensibility.md @@ -124,55 +124,56 @@ _Note:_ This filter must always be run on every page load, and not in your brows Extending the editor's UI is done with in `registerPlugin` API, and allows you to define all your plugin's UI elements in one place. -### `wp.editPost.__experimental.registerPlugin( { name: string, render: function } )` +### `wp.editPost.__experimental.registerPlugin( name: string, { render: function } )` -This method takes one argument: -- An object containing the following data: - - `name`: A string identifying the plugin. Must be unique across all registered plugins. - - `render`: A component containing the UI elements to be rendered. See the list below for all available UI elements. +This method takes two arguments: +1. `name`: A string identifying the plugin. Must be unique across all registered plugins. +2. `settings`: An object containing the following data: + - `render`: A component containing the UI elements to be rendered. See the list below for all available UI elements. **Example** ```js -const Fragment = wp.element.Fragment; -const PluginSidebar = wp.editPost.__experimental.PluginSidebar; +const { Fragment } = wp.element; +const { PluginSidebar } = wp.editPost.__experimental; -const MyPluginComponent = () => ( +const Component = () => ( - -

Content of the first sidebar

-
- -

Content of the second sidebar

-
+ +

Content of the first sidebar

+
+ +

Content of the second sidebar

+
); -wp.editPost.__experimental.registerPlugin( { - name: 'my-plugin-name', - render: MyPluginComponent, +wp.editPost.__experimental.registerPlugin( 'plugin-names', { + render: Component, } ); ``` + +You can activate the sidebars using the following lines: + +```js +wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/first-sidebar-name' ); +wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/second-sidebar-name' ); +``` ### Available UI components -The available UI components are found in the global variable `wp.editPost.__experimental`, and are WrordPress components. +The available UI components are found in the global variable `wp.editPost.__experimental`, and are React components. #### PluginSidebar Renders a sidebar when activated. ```js - -

Content of the first sidebar

+ + ``` - Props - `name`: A string identifying the sidebar. Must be unique for every sidebar registered within the scope of your plugin. - `title`: Title displayed at the top of the sidebar. Must be a string. -You can activate the sidebar using the following statement: -```js -wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'my-plugin-name/first-sidebar-name' ); -``` The contents you render within the `PluginSidebar` will show up as content within the sidebar. - diff --git a/edit-post/api/README.md b/edit-post/api/README.md index 2e5c81e7d1636d..a1261cce8da584 100644 --- a/edit-post/api/README.md +++ b/edit-post/api/README.md @@ -3,18 +3,18 @@ Edit post API The edit post API contains the following methods: -### `wp.editPost.__experimental.registerPlugin( { name: string, render: function } )` +### `wp.editPost.__experimental.registerPlugin( name: string, { render: function } )` -This method takes one argument: -- An object containing the following data: - - `name`: A string identifying the plugin. Must be unique across all registered plugins. - - `render`: A component containing the UI elements to be rendered. See the list below for all available UI elements. +This method takes two arguments: +1. `name`: A string identifying the plugin. Must be unique across all registered plugins. +2. `settings`: An object containing the following data: + - `render`: A component containing the UI elements to be rendered. See the list below for all available UI elements. **Example** ```js -const Fragment = wp.element.Fragment; -const PluginSidebar = wp.editPost.__experimental.PluginSidebar; +const { Fragment } = wp.element; +const { PluginSidebar } = wp.editPost.__experimental; const Component = () => ( @@ -27,8 +27,7 @@ const Component = () => ( ); -wp.editPost.__experimental.registerPlugin( { - name: 'plugin-name', +wp.editPost.__experimental.registerPlugin( 'plugin-names', { render: Component, } ); ``` diff --git a/edit-post/api/components/plugin-sidebar/index.js b/edit-post/api/components/plugin-sidebar/index.js index e3a373bc8623af..35d06b7c42b5e3 100644 --- a/edit-post/api/components/plugin-sidebar/index.js +++ b/edit-post/api/components/plugin-sidebar/index.js @@ -50,7 +50,7 @@ class PluginSidebar extends Component { /** * Renders the PluginSidebar component. * - * @returns {ReactElement} The rendered component. + * @return {ReactElement} The rendered component. */ render() { if ( this.props.activePlugin !== this.namespacedName ) { diff --git a/edit-post/api/plugin.js b/edit-post/api/plugin.js index ed95e4da1450da..ad34856012313b 100644 --- a/edit-post/api/plugin.js +++ b/edit-post/api/plugin.js @@ -29,34 +29,34 @@ class PluginRegistry { /** * Registers a plugin to the editor. * + * @param {string} name The name of the plugin. * @param {Object} settings The settings for this plugin. - * @param {string} settings.name The name of the plugin. * @param {Function} settings.render The function that renders the plugin. * * @return {Object} The final plugin settings object. */ - registerPlugin( settings ) { + registerPlugin( name, settings ) { if ( typeof settings !== 'object' ) { console.error( 'No settings object provided!' ); return null; } - if ( typeof settings.name !== 'string' ) { + if ( typeof name !== 'string' ) { console.error( 'Plugin names must be strings.' ); return null; } - if ( ! /^[a-z][a-z0-9-]*$/.test( settings.name ) ) { + 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".' ); return null; } - if ( this.plugins[ settings.name ] ) { + if ( this.plugins[ name ] ) { console.error( - `Plugin "${ settings.name }" is already registered.` + `Plugin "${ name }" is already registered.` ); } if ( ! isFunction( settings.render ) ) { @@ -66,9 +66,10 @@ class PluginRegistry { return null; } + settings.name = name; settings.sidebar = {}; - settings = applyFilters( 'editPost.registerPlugin', settings, settings.name ); + settings = applyFilters( 'editPost.registerPlugin', settings, name ); return this.plugins[ settings.name ] = settings; } diff --git a/edit-post/api/test/plugin.js b/edit-post/api/test/plugin.js index 81080d83641e3b..16af15cdd81561 100644 --- a/edit-post/api/test/plugin.js +++ b/edit-post/api/test/plugin.js @@ -9,8 +9,7 @@ beforeEach( () => { describe( 'registerPlugin', () => { it( 'successfully registers a plugin', () => { - registerPlugin( { - name: 'plugin', + registerPlugin( 'plugin', { render: () => 'plugin content', } ); } ); @@ -21,35 +20,29 @@ describe( 'registerPlugin', () => { } ); it( 'fails to register a plugin with special character in the name', () => { - registerPlugin( { - name: 'plugin/with/special/characters', + registerPlugin( 'plugin/with/special/characters', { render: () => {}, } ); expect( console ).toHaveErroredWith( 'Plugin names must include only lowercase alphanumeric characters or dashes, and start with a letter. Example: "my-plugin".' ); } ); it( 'fails to register a plugin with a non-string name', () => { - registerPlugin( { - name: () => {}, + registerPlugin( {}, { render: () => {}, } ); expect( console ).toHaveErroredWith( 'Plugin names must be strings.' ); } ); it( 'fails to register a plugin without a render function', () => { - registerPlugin( { - name: 'another-plugin', - } ); + registerPlugin( 'another-plugin', {} ); expect( console ).toHaveErroredWith( 'The "render" property must be specified and must be a valid function.' ); } ); it( 'fails to register a plugin that was already been registered', () => { - registerPlugin( { - name: 'plugin', + registerPlugin( 'plugin', { render: () => 'plugin content', } ); - registerPlugin( { - name: 'plugin', + registerPlugin( 'plugin', { render: () => 'plugin content', } ); console.log( console ); // eslint-disable-line From 688280c9266eb1cf23f18793c1f436e89c828b3a Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Tue, 13 Mar 2018 14:32:02 +0100 Subject: [PATCH 59/83] Small documentation tweaks --- docs/extensibility.md | 8 ++++---- edit-post/api/README.md | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/extensibility.md b/docs/extensibility.md index 2f79f0700d59e3..136763a31215ae 100644 --- a/docs/extensibility.md +++ b/docs/extensibility.md @@ -133,19 +133,19 @@ This method takes two arguments: **Example** -```js +```jsx const { Fragment } = wp.element; const { PluginSidebar } = wp.editPost.__experimental; const Component = () => ( - +

Content of the first sidebar

Content of the second sidebar

-
+
); wp.editPost.__experimental.registerPlugin( 'plugin-names', { @@ -167,7 +167,7 @@ The available UI components are found in the global variable `wp.editPost.__expe #### PluginSidebar Renders a sidebar when activated. -```js +```jsx diff --git a/edit-post/api/README.md b/edit-post/api/README.md index a1261cce8da584..7683f6789438c4 100644 --- a/edit-post/api/README.md +++ b/edit-post/api/README.md @@ -12,19 +12,19 @@ This method takes two arguments: **Example** -```js +```jsx const { Fragment } = wp.element; const { PluginSidebar } = wp.editPost.__experimental; const Component = () => ( - +

Content of the first sidebar

Content of the second sidebar

-
+
); wp.editPost.__experimental.registerPlugin( 'plugin-names', { @@ -46,7 +46,7 @@ The available UI components are found in the global variable `wp.editPost.__expe #### PluginSidebar Renders a sidebar when activated. -```js +```jsx From d25b07da37bf1f0d9e665ecebb33c1952b0a0512 Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Wed, 14 Mar 2018 10:54:49 +0100 Subject: [PATCH 60/83] Moved and documented plugin context provider and higher order component --- edit-post/api/index.js | 2 +- edit-post/api/plugin.js | 3 +- .../with-plugin-context}/index.js | 30 ++++++------------ edit-post/components/layout/index.js | 2 +- .../plugin-context-provider/index.js | 31 +++++++++++++++++++ .../plugin-context-provider}/test/context.js | 0 .../plugin-sidebar/error-boundary.js | 0 .../components/plugin-sidebar/index.js | 2 +- .../plugin-sidebar/sidebar-layout.js | 0 .../components/plugin-sidebar/style.scss | 0 edit-post/index.js | 2 +- 11 files changed, 46 insertions(+), 26 deletions(-) rename edit-post/{api/components/context => components/higher-order/with-plugin-context}/index.js (53%) create mode 100644 edit-post/components/plugin-context-provider/index.js rename edit-post/{api/components/context => components/plugin-context-provider}/test/context.js (100%) rename edit-post/{api => }/components/plugin-sidebar/error-boundary.js (100%) rename edit-post/{api => }/components/plugin-sidebar/index.js (97%) rename edit-post/{api => }/components/plugin-sidebar/sidebar-layout.js (100%) rename edit-post/{api => }/components/plugin-sidebar/style.scss (100%) diff --git a/edit-post/api/index.js b/edit-post/api/index.js index 08eb2862cd339e..e7ed5e0fb5869f 100644 --- a/edit-post/api/index.js +++ b/edit-post/api/index.js @@ -1,5 +1,5 @@ import { PluginRegistry } from './plugin'; -import { PluginSidebarFill as PluginSidebar } from './components/plugin-sidebar'; +import { PluginSidebarFill as PluginSidebar } from '../components/plugin-sidebar'; const registry = PluginRegistry.getInstance(); diff --git a/edit-post/api/plugin.js b/edit-post/api/plugin.js index ad34856012313b..5dea8e6693c0dc 100644 --- a/edit-post/api/plugin.js +++ b/edit-post/api/plugin.js @@ -12,7 +12,7 @@ import { isFunction, map } from 'lodash'; /** * Internal dependencies */ -import { PluginContextProvider, withPluginContext } from './components/context'; +import PluginContextProvider from '../components/plugin-context-provider'; /** * Singleton class for registering plugins. @@ -152,6 +152,5 @@ class PluginArea extends Component { export { PluginArea, - withPluginContext, PluginRegistry, }; diff --git a/edit-post/api/components/context/index.js b/edit-post/components/higher-order/with-plugin-context/index.js similarity index 53% rename from edit-post/api/components/context/index.js rename to edit-post/components/higher-order/with-plugin-context/index.js index 90df3a15e5ba3a..17b0ffc6a410e1 100644 --- a/edit-post/api/components/context/index.js +++ b/edit-post/components/higher-order/with-plugin-context/index.js @@ -8,22 +8,15 @@ import { noop } from 'lodash'; */ import { Component } from '@wordpress/element'; -class PluginContextProvider extends Component { - getChildContext() { - return { - pluginContext: this.props.value, - }; - } - - render() { - return this.props.children; - } -} - -PluginContextProvider.childContextTypes = { - pluginContext: noop, -}; - +/** + * Higher-order component creator that creates a new component and provides + * the plugin context provided by the PluginContextProvider component as + * props to that component under props.pluginContext. + * + * @param {ReactElement} WrappedComponent The component to be decorated. + * + * @return {PluginContextConsumer} The higher order component. + */ function withPluginContext( WrappedComponent ) { class PluginContextConsumer extends Component { render() { @@ -41,7 +34,4 @@ function withPluginContext( WrappedComponent ) { return PluginContextConsumer; } -export { - PluginContextProvider, - withPluginContext, -}; +export default withPluginContext; diff --git a/edit-post/components/layout/index.js b/edit-post/components/layout/index.js index 5146b3a9ebbda9..f0f77cfb99bf3c 100644 --- a/edit-post/components/layout/index.js +++ b/edit-post/components/layout/index.js @@ -32,7 +32,7 @@ import EditorModeKeyboardShortcuts from '../keyboard-shortcuts'; import MetaBoxes from '../meta-boxes'; import { getMetaBoxContainer } from '../../utils/meta-boxes'; import { PluginArea } from '../../api/plugin'; -import { PluginSidebar } from '../../api/components/plugin-sidebar'; +import { PluginSidebar } from '../plugin-sidebar'; function Layout( { mode, diff --git a/edit-post/components/plugin-context-provider/index.js b/edit-post/components/plugin-context-provider/index.js new file mode 100644 index 00000000000000..f9ea56038bac49 --- /dev/null +++ b/edit-post/components/plugin-context-provider/index.js @@ -0,0 +1,31 @@ +/** + * External dependencies + */ +import { noop } from 'lodash'; + +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; + +/** + * Provives context to plugins, in combination with the withPluginContext + * higher order component. + */ +class PluginContextProvider extends Component { + getChildContext() { + return { + pluginContext: this.props.value, + }; + } + + render() { + return this.props.children; + } +} + +PluginContextProvider.childContextTypes = { + pluginContext: noop, +}; + +export default PluginContextProvider; diff --git a/edit-post/api/components/context/test/context.js b/edit-post/components/plugin-context-provider/test/context.js similarity index 100% rename from edit-post/api/components/context/test/context.js rename to edit-post/components/plugin-context-provider/test/context.js diff --git a/edit-post/api/components/plugin-sidebar/error-boundary.js b/edit-post/components/plugin-sidebar/error-boundary.js similarity index 100% rename from edit-post/api/components/plugin-sidebar/error-boundary.js rename to edit-post/components/plugin-sidebar/error-boundary.js diff --git a/edit-post/api/components/plugin-sidebar/index.js b/edit-post/components/plugin-sidebar/index.js similarity index 97% rename from edit-post/api/components/plugin-sidebar/index.js rename to edit-post/components/plugin-sidebar/index.js index 35d06b7c42b5e3..282010d3e708b1 100644 --- a/edit-post/api/components/plugin-sidebar/index.js +++ b/edit-post/components/plugin-sidebar/index.js @@ -9,7 +9,7 @@ import { withSelect, withDispatch } from '@wordpress/data'; * Internal dependencies */ import './style.scss'; -import { withPluginContext } from '../../plugin'; +import withPluginContext from '../higher-order/with-plugin-context'; import SidebarLayout from './sidebar-layout'; import ErrorBoundary from './error-boundary'; diff --git a/edit-post/api/components/plugin-sidebar/sidebar-layout.js b/edit-post/components/plugin-sidebar/sidebar-layout.js similarity index 100% rename from edit-post/api/components/plugin-sidebar/sidebar-layout.js rename to edit-post/components/plugin-sidebar/sidebar-layout.js diff --git a/edit-post/api/components/plugin-sidebar/style.scss b/edit-post/components/plugin-sidebar/style.scss similarity index 100% rename from edit-post/api/components/plugin-sidebar/style.scss rename to edit-post/components/plugin-sidebar/style.scss diff --git a/edit-post/index.js b/edit-post/index.js index 13a72075090f04..759d578c6f9732 100644 --- a/edit-post/index.js +++ b/edit-post/index.js @@ -21,7 +21,7 @@ import Layout from './components/layout'; import store from './store'; import { initializeMetaBoxState } from './store/actions'; -export { PluginSidebarFill as PluginSidebar } from './api/components/plugin-sidebar'; +export { PluginSidebarFill as PluginSidebar } from './components/plugin-sidebar'; export * from './api'; // Configure moment globally From 4d5209aba81e80b677c84f1c5f4de62977a561c6 Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Wed, 14 Mar 2018 10:57:41 +0100 Subject: [PATCH 61/83] Updated test --- edit-post/components/plugin-context-provider/test/context.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/edit-post/components/plugin-context-provider/test/context.js b/edit-post/components/plugin-context-provider/test/context.js index af864d0cdd6a93..420fdcfa71cef0 100644 --- a/edit-post/components/plugin-context-provider/test/context.js +++ b/edit-post/components/plugin-context-provider/test/context.js @@ -6,7 +6,8 @@ import { mount } from 'enzyme'; /** * Internal dependencies */ -import { PluginContextProvider, withPluginContext } from '../index'; +import PluginContextProvider from '../index'; +import withPluginContext from '../../higher-order/with-plugin-context'; describe( 'plugin/context', () => { describe( 'withPluginContext', () => { From db3c98ccbfd65aebe81ed39b76798b97b3ed129a Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Wed, 14 Mar 2018 11:23:26 +0100 Subject: [PATCH 62/83] Fixed sass class names --- edit-post/components/plugin-sidebar/index.js | 2 +- ...{error-boundary.js => sidebar-error-boundary.js} | 8 +++++--- .../components/plugin-sidebar/sidebar-layout.js | 6 +++--- edit-post/components/plugin-sidebar/style.scss | 13 ++++++------- 4 files changed, 15 insertions(+), 14 deletions(-) rename edit-post/components/plugin-sidebar/{error-boundary.js => sidebar-error-boundary.js} (69%) diff --git a/edit-post/components/plugin-sidebar/index.js b/edit-post/components/plugin-sidebar/index.js index 282010d3e708b1..ad76acfd3a529d 100644 --- a/edit-post/components/plugin-sidebar/index.js +++ b/edit-post/components/plugin-sidebar/index.js @@ -11,7 +11,7 @@ import { withSelect, withDispatch } from '@wordpress/data'; import './style.scss'; import withPluginContext from '../higher-order/with-plugin-context'; import SidebarLayout from './sidebar-layout'; -import ErrorBoundary from './error-boundary'; +import ErrorBoundary from './sidebar-error-boundary'; /** * Name of slot in which the sidebar should fill. diff --git a/edit-post/components/plugin-sidebar/error-boundary.js b/edit-post/components/plugin-sidebar/sidebar-error-boundary.js similarity index 69% rename from edit-post/components/plugin-sidebar/error-boundary.js rename to edit-post/components/plugin-sidebar/sidebar-error-boundary.js index 3cce18b752cfb3..e354fc6d4f3a44 100644 --- a/edit-post/components/plugin-sidebar/error-boundary.js +++ b/edit-post/components/plugin-sidebar/sidebar-error-boundary.js @@ -16,9 +16,11 @@ class SidebarErrorBoundary extends Component { render() { if ( this.state.hasError ) { - return

- { sprintf( __( 'An error occurred rendering the plugin sidebar with id "%s".' ), this.props.pluginName ) } -

; + return ( +

+ { sprintf( __( 'An error occurred rendering the plugin sidebar with id "%s".' ), this.props.pluginName ) } +

+ ); } return this.props.children; } diff --git a/edit-post/components/plugin-sidebar/sidebar-layout.js b/edit-post/components/plugin-sidebar/sidebar-layout.js index edbcb5134bd666..b9842dbc45e08f 100644 --- a/edit-post/components/plugin-sidebar/sidebar-layout.js +++ b/edit-post/components/plugin-sidebar/sidebar-layout.js @@ -9,11 +9,11 @@ import { __ } from '@wordpress/i18n'; const SidebarLayout = ( { onClose, title, children } ) => { return (
-
+

{ title }

{ label={ __( 'Close settings' ) } />
-
+
{ children }
diff --git a/edit-post/components/plugin-sidebar/style.scss b/edit-post/components/plugin-sidebar/style.scss index 0b8372a48714ad..7e110dba83be00 100644 --- a/edit-post/components/plugin-sidebar/style.scss +++ b/edit-post/components/plugin-sidebar/style.scss @@ -1,5 +1,5 @@ -.edit-post-layout { - .edit-post-plugins-panel__header { +.edit-post-plugin-sidebar { + &__header { padding: 0 8px 0 16px; height: $panel-header-height; border-bottom: 1px solid $light-gray-500; @@ -16,10 +16,9 @@ color: inherit; } } -} -.plugin-sidebar-error { - padding: 16px; - color: red; + &__error { + padding: 16px; + color: red; + } } - From b2526da9309f578560389694ad973b6b3de17f95 Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Wed, 14 Mar 2018 14:05:52 +0100 Subject: [PATCH 63/83] Made scss specific for plugin0-sidebar which nog longer depends on edit-post-sidebar scss --- .../plugin-sidebar/sidebar-layout.js | 4 +- .../components/plugin-sidebar/style.scss | 38 ++++++++++++++++--- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/edit-post/components/plugin-sidebar/sidebar-layout.js b/edit-post/components/plugin-sidebar/sidebar-layout.js index b9842dbc45e08f..cd72d24736481b 100644 --- a/edit-post/components/plugin-sidebar/sidebar-layout.js +++ b/edit-post/components/plugin-sidebar/sidebar-layout.js @@ -9,12 +9,12 @@ import { __ } from '@wordpress/i18n'; const SidebarLayout = ( { onClose, title, children } ) => { return (
-

{ title }

+

{ title }

Date: Wed, 14 Mar 2018 14:19:40 +0100 Subject: [PATCH 64/83] Moved PLuginsArea to separate file --- edit-post/api/index.js | 5 ++- edit-post/api/plugin.js | 38 ++------------------ edit-post/components/layout/index.js | 2 +- edit-post/components/plugin-area/index.js | 42 +++++++++++++++++++++++ 4 files changed, 49 insertions(+), 38 deletions(-) create mode 100644 edit-post/components/plugin-area/index.js diff --git a/edit-post/api/index.js b/edit-post/api/index.js index e7ed5e0fb5869f..b452ef47400fd1 100644 --- a/edit-post/api/index.js +++ b/edit-post/api/index.js @@ -1,4 +1,7 @@ -import { PluginRegistry } from './plugin'; +/** + * Internal dependencies + */ +import PluginRegistry from './plugin'; import { PluginSidebarFill as PluginSidebar } from '../components/plugin-sidebar'; const registry = PluginRegistry.getInstance(); diff --git a/edit-post/api/plugin.js b/edit-post/api/plugin.js index 5dea8e6693c0dc..c1a5de4b633c43 100644 --- a/edit-post/api/plugin.js +++ b/edit-post/api/plugin.js @@ -4,15 +4,9 @@ * WordPress dependencies */ import { applyFilters } from '@wordpress/hooks'; -import { Component } from '@wordpress/element'; /* External dependencies */ -import { isFunction, map } from 'lodash'; - -/** - * Internal dependencies - */ -import PluginContextProvider from '../components/plugin-context-provider'; +import { isFunction } from 'lodash'; /** * Singleton class for registering plugins. @@ -125,32 +119,4 @@ class PluginRegistry { } } -/** - * A component that renders all plugin fills in a hidden div. - */ -class PluginArea extends Component { - render() { - const pluginRegistry = PluginRegistry.getInstance(); - - return ( -
- { map( pluginRegistry.plugins, plugin => { - const boundRegisterUIComponent = pluginRegistry.registerUIComponent.bind( pluginRegistry, plugin.name ); - return ( - - { plugin.render() } - - ); - } ) } -
- ); - } -} - -export { - PluginArea, - PluginRegistry, -}; +export default PluginRegistry; diff --git a/edit-post/components/layout/index.js b/edit-post/components/layout/index.js index f0f77cfb99bf3c..54d9c022cda1f0 100644 --- a/edit-post/components/layout/index.js +++ b/edit-post/components/layout/index.js @@ -31,7 +31,7 @@ import VisualEditor from '../visual-editor'; import EditorModeKeyboardShortcuts from '../keyboard-shortcuts'; import MetaBoxes from '../meta-boxes'; import { getMetaBoxContainer } from '../../utils/meta-boxes'; -import { PluginArea } from '../../api/plugin'; +import PluginArea from '../plugin-area'; import { PluginSidebar } from '../plugin-sidebar'; function Layout( { diff --git a/edit-post/components/plugin-area/index.js b/edit-post/components/plugin-area/index.js new file mode 100644 index 00000000000000..a71dd2698bfce1 --- /dev/null +++ b/edit-post/components/plugin-area/index.js @@ -0,0 +1,42 @@ +/** + * External dependencies + */ +import { map } from 'lodash'; + +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import PluginRegistry from '../../api/plugin'; +import PluginContextProvider from '../plugin-context-provider'; + +/** + * A component that renders all plugin fills in a hidden div. + */ +class PluginArea extends Component { + render() { + const pluginRegistry = PluginRegistry.getInstance(); + + return ( +
+ { map( pluginRegistry.plugins, plugin => { + const boundRegisterUIComponent = pluginRegistry.registerUIComponent.bind( pluginRegistry, plugin.name ); + return ( + + { plugin.render() } + + ); + } ) } +
+ ); + } +} + +export default PluginArea; From eed00e2d62a31dc17f14c250dadfe66a2f69a1fe Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Wed, 14 Mar 2018 14:30:29 +0100 Subject: [PATCH 65/83] Updated test --- edit-post/api/test/plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edit-post/api/test/plugin.js b/edit-post/api/test/plugin.js index 16af15cdd81561..d211c277bbbd93 100644 --- a/edit-post/api/test/plugin.js +++ b/edit-post/api/test/plugin.js @@ -1,4 +1,4 @@ -import { PluginRegistry } from '../plugin'; +import PluginRegistry from '../plugin'; let registerPlugin; From 995d7de1ce7d1eb140de806b4bef761838b5ec41 Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Wed, 14 Mar 2018 15:49:29 +0100 Subject: [PATCH 66/83] Moved plugin api to plugins module --- docs/extensibility.md | 2 +- edit-post/components/layout/index.js | 2 +- edit-post/components/plugin-sidebar/index.js | 2 +- edit-post/index.js | 7 +++++-- edit-post/store/selectors.js | 6 +++++- edit-post/store/test/selectors.js | 4 ++-- lib/client-assets.php | 10 +++++++++- package.json | 4 ++-- {edit-post/api => plugins}/README.md | 2 +- {edit-post => plugins}/api/index.js | 2 -- {edit-post => plugins}/api/plugin.js | 0 {edit-post => plugins}/api/test/plugin.js | 0 .../higher-order/with-plugin-context/index.js | 0 plugins/components/index.js | 3 +++ {edit-post => plugins}/components/plugin-area/index.js | 0 .../components/plugin-context-provider/index.js | 0 .../components/plugin-context-provider/test/context.js | 0 plugins/index.js | 2 ++ test/unit/setup-wp-aliases.js | 1 + webpack.config.js | 1 + 20 files changed, 34 insertions(+), 14 deletions(-) rename {edit-post/api => plugins}/README.md (95%) rename {edit-post => plugins}/api/index.js (74%) rename {edit-post => plugins}/api/plugin.js (100%) rename {edit-post => plugins}/api/test/plugin.js (100%) rename {edit-post => plugins}/components/higher-order/with-plugin-context/index.js (100%) create mode 100644 plugins/components/index.js rename {edit-post => plugins}/components/plugin-area/index.js (100%) rename {edit-post => plugins}/components/plugin-context-provider/index.js (100%) rename {edit-post => plugins}/components/plugin-context-provider/test/context.js (100%) create mode 100644 plugins/index.js diff --git a/docs/extensibility.md b/docs/extensibility.md index 136763a31215ae..e76bad0cebc145 100644 --- a/docs/extensibility.md +++ b/docs/extensibility.md @@ -124,7 +124,7 @@ _Note:_ This filter must always be run on every page load, and not in your brows Extending the editor's UI is done with in `registerPlugin` API, and allows you to define all your plugin's UI elements in one place. -### `wp.editPost.__experimental.registerPlugin( name: string, { render: function } )` +### `wp.plugins.__experimental.registerPlugin( name: string, { render: function } )` This method takes two arguments: 1. `name`: A string identifying the plugin. Must be unique across all registered plugins. diff --git a/edit-post/components/layout/index.js b/edit-post/components/layout/index.js index 54d9c022cda1f0..21e34ae1d933dc 100644 --- a/edit-post/components/layout/index.js +++ b/edit-post/components/layout/index.js @@ -19,6 +19,7 @@ import { } from '@wordpress/editor'; import { withDispatch, withSelect } from '@wordpress/data'; import { compose } from '@wordpress/element'; +import { PluginArea } from '@wordpress/plugins'; /** * Internal dependencies @@ -31,7 +32,6 @@ import VisualEditor from '../visual-editor'; import EditorModeKeyboardShortcuts from '../keyboard-shortcuts'; import MetaBoxes from '../meta-boxes'; import { getMetaBoxContainer } from '../../utils/meta-boxes'; -import PluginArea from '../plugin-area'; import { PluginSidebar } from '../plugin-sidebar'; function Layout( { diff --git a/edit-post/components/plugin-sidebar/index.js b/edit-post/components/plugin-sidebar/index.js index ad76acfd3a529d..f3e59ffa1fdd09 100644 --- a/edit-post/components/plugin-sidebar/index.js +++ b/edit-post/components/plugin-sidebar/index.js @@ -4,12 +4,12 @@ import { Component, Children, cloneElement, compose } from '@wordpress/element'; import { Slot, Fill, withFocusReturn } from '@wordpress/components'; import { withSelect, withDispatch } from '@wordpress/data'; +import { withPluginContext } from '@wordpress/plugins'; /** * Internal dependencies */ import './style.scss'; -import withPluginContext from '../higher-order/with-plugin-context'; import SidebarLayout from './sidebar-layout'; import ErrorBoundary from './sidebar-error-boundary'; diff --git a/edit-post/index.js b/edit-post/index.js index 759d578c6f9732..f83dc5b31ba958 100644 --- a/edit-post/index.js +++ b/edit-post/index.js @@ -21,8 +21,7 @@ import Layout from './components/layout'; import store from './store'; import { initializeMetaBoxState } from './store/actions'; -export { PluginSidebarFill as PluginSidebar } from './components/plugin-sidebar'; -export * from './api'; +import { PluginSidebarFill as PluginSidebar } from './components/plugin-sidebar'; // Configure moment globally moment.locale( dateSettings.l10n.locale ); @@ -110,3 +109,7 @@ export function initializeEditor( id, post, settings ) { }, }; } + +export const __experimental = { + PluginSidebar, +}; diff --git a/edit-post/store/selectors.js b/edit-post/store/selectors.js index d534385997a3ac..d2a8602fe7ca79 100644 --- a/edit-post/store/selectors.js +++ b/edit-post/store/selectors.js @@ -3,7 +3,11 @@ */ import createSelector from 'rememo'; import { includes, some } from 'lodash'; -import { __experimental } from '../api'; + +/** + * WordPress dependencies + */ +import { __experimental } from '@wordpress/plugins'; /** * Returns the current editing mode. diff --git a/edit-post/store/test/selectors.js b/edit-post/store/test/selectors.js index 3555a05697e35a..7cddd9dddd57f3 100644 --- a/edit-post/store/test/selectors.js +++ b/edit-post/store/test/selectors.js @@ -13,7 +13,7 @@ import { isSavingMetaBoxes, getMetaBox, } from '../selectors'; -import { __experimental } from '../../api'; +import { __experimental } from '@wordpress/plugins'; const getRegisteredUIComponentMock = __experimental.getRegisteredUIComponent; jest.mock( '@wordpress/element', () => ( { @@ -21,7 +21,7 @@ jest.mock( '@wordpress/element', () => ( { Component: jest.fn(), createElement: jest.fn(), } ) ); -jest.mock( '../../api', () => ( { +jest.mock( '@wordpress/plugins', () => ( { __experimental: { getRegisteredUIComponent: jest.fn().mockReturnValue( null ), }, diff --git a/lib/client-assets.php b/lib/client-assets.php index a24279f6b7b59a..e06c3172ab049b 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -248,6 +248,7 @@ function gutenberg_register_scripts_and_styles() { 'wp-components', 'wp-utils', 'wp-viewport', + 'wp-plugins', 'word-count', 'editor', ), @@ -257,7 +258,7 @@ function gutenberg_register_scripts_and_styles() { wp_register_script( 'wp-edit-post', gutenberg_url( 'edit-post/build/index.js' ), - array( 'jquery', 'heartbeat', 'wp-element', 'wp-components', 'wp-editor', 'wp-i18n', 'wp-date', 'wp-utils', 'wp-data', 'wp-embed', 'wp-viewport' ), + array( 'jquery', 'heartbeat', 'wp-element', 'wp-components', 'wp-editor', 'wp-i18n', 'wp-date', 'wp-utils', 'wp-data', 'wp-embed', 'wp-viewport', 'wp-plugins' ), filemtime( gutenberg_dir_path() . 'edit-post/build/index.js' ), true ); @@ -307,6 +308,13 @@ function gutenberg_register_scripts_and_styles() { filemtime( gutenberg_dir_path() . 'blocks/build/edit-blocks.css' ) ); wp_style_add_data( 'wp-edit-blocks', 'rtl', 'replace' ); + + wp_register_script( + 'wp-plugins', + gutenberg_url( 'plugins/build/index.js' ), + array( 'wp-element', 'wp-components', 'wp-utils', 'wp-data' ), + filemtime( gutenberg_dir_path() . 'plugins/build/index.js' ) + ); } add_action( 'wp_enqueue_scripts', 'gutenberg_register_scripts_and_styles', 5 ); add_action( 'admin_enqueue_scripts', 'gutenberg_register_scripts_and_styles', 5 ); diff --git a/package.json b/package.json index 21ba6d0adaff84..0d2caf8377ac12 100644 --- a/package.json +++ b/package.json @@ -113,10 +113,10 @@ }, "jest": { "collectCoverageFrom": [ - "(blocks|components|date|editor|element|i18n|data|utils|edit-post|viewport)/**/*.js" + "(blocks|components|date|editor|element|i18n|data|utils|edit-post|viewport|plugins)/**/*.js" ], "moduleNameMapper": { - "@wordpress\\/(blocks|components|date|editor|element|i18n|data|utils|edit-post|viewport)": "$1" + "@wordpress\\/(blocks|components|date|editor|element|i18n|data|utils|edit-post|viewport|plugins)": "$1" }, "preset": "@wordpress/jest-preset-default", "setupFiles": [ diff --git a/edit-post/api/README.md b/plugins/README.md similarity index 95% rename from edit-post/api/README.md rename to plugins/README.md index 7683f6789438c4..116bbc89956d31 100644 --- a/edit-post/api/README.md +++ b/plugins/README.md @@ -3,7 +3,7 @@ Edit post API The edit post API contains the following methods: -### `wp.editPost.__experimental.registerPlugin( name: string, { render: function } )` +### `wp.plugins.__experimental.registerPlugin( name: string, { render: function } )` This method takes two arguments: 1. `name`: A string identifying the plugin. Must be unique across all registered plugins. diff --git a/edit-post/api/index.js b/plugins/api/index.js similarity index 74% rename from edit-post/api/index.js rename to plugins/api/index.js index b452ef47400fd1..a3edefe8b97c67 100644 --- a/edit-post/api/index.js +++ b/plugins/api/index.js @@ -2,14 +2,12 @@ * Internal dependencies */ import PluginRegistry from './plugin'; -import { PluginSidebarFill as PluginSidebar } from '../components/plugin-sidebar'; const registry = PluginRegistry.getInstance(); const __experimental = { registerPlugin: registry.registerPlugin, getRegisteredUIComponent: registry.getRegisteredUIComponent, - PluginSidebar, }; export { diff --git a/edit-post/api/plugin.js b/plugins/api/plugin.js similarity index 100% rename from edit-post/api/plugin.js rename to plugins/api/plugin.js diff --git a/edit-post/api/test/plugin.js b/plugins/api/test/plugin.js similarity index 100% rename from edit-post/api/test/plugin.js rename to plugins/api/test/plugin.js diff --git a/edit-post/components/higher-order/with-plugin-context/index.js b/plugins/components/higher-order/with-plugin-context/index.js similarity index 100% rename from edit-post/components/higher-order/with-plugin-context/index.js rename to plugins/components/higher-order/with-plugin-context/index.js diff --git a/plugins/components/index.js b/plugins/components/index.js new file mode 100644 index 00000000000000..a2aa9492cb5eb1 --- /dev/null +++ b/plugins/components/index.js @@ -0,0 +1,3 @@ +export { default as withPluginContext } from './higher-order/with-plugin-context'; +export { default as PluginContextProvider } from './plugin-context-provider'; +export { default as PluginArea } from './plugin-area'; diff --git a/edit-post/components/plugin-area/index.js b/plugins/components/plugin-area/index.js similarity index 100% rename from edit-post/components/plugin-area/index.js rename to plugins/components/plugin-area/index.js diff --git a/edit-post/components/plugin-context-provider/index.js b/plugins/components/plugin-context-provider/index.js similarity index 100% rename from edit-post/components/plugin-context-provider/index.js rename to plugins/components/plugin-context-provider/index.js diff --git a/edit-post/components/plugin-context-provider/test/context.js b/plugins/components/plugin-context-provider/test/context.js similarity index 100% rename from edit-post/components/plugin-context-provider/test/context.js rename to plugins/components/plugin-context-provider/test/context.js diff --git a/plugins/index.js b/plugins/index.js new file mode 100644 index 00000000000000..3d5bec67d6d1f9 --- /dev/null +++ b/plugins/index.js @@ -0,0 +1,2 @@ +export * from './components'; +export * from './api'; diff --git a/test/unit/setup-wp-aliases.js b/test/unit/setup-wp-aliases.js index d93003dc4079cb..a5482b8acbf9b7 100644 --- a/test/unit/setup-wp-aliases.js +++ b/test/unit/setup-wp-aliases.js @@ -16,6 +16,7 @@ global.wp = { 'data', 'edit-post', 'viewport', + 'plugins', ].forEach( entryPointName => { Object.defineProperty( global.wp, entryPointName, { get: () => require( entryPointName ), diff --git a/webpack.config.js b/webpack.config.js index 276ceb4c4db27d..0ec60bcf8ac684 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -57,6 +57,7 @@ const entryPointNames = [ 'data', 'viewport', [ 'editPost', 'edit-post' ], + 'plugins', ]; const packageNames = [ From e706a7aa891661af0706f144416f00aeedf8fe26 Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Wed, 14 Mar 2018 17:02:58 +0100 Subject: [PATCH 67/83] Updated documentation --- bin/build-plugin-zip.sh | 2 +- docs/extensibility.md | 2 +- edit-post/api/sidebar.js | 10 ++++++++++ plugins/README.md | 6 +++--- plugins/api/plugin.js | 4 +++- 5 files changed, 18 insertions(+), 6 deletions(-) create mode 100644 edit-post/api/sidebar.js diff --git a/bin/build-plugin-zip.sh b/bin/build-plugin-zip.sh index 3cf089bf0dda7e..4c92566b93927b 100755 --- a/bin/build-plugin-zip.sh +++ b/bin/build-plugin-zip.sh @@ -99,7 +99,7 @@ zip -r gutenberg.zip \ blocks/library/*/*.php \ post-content.js \ $vendor_scripts \ - {blocks,components,date,editor,element,hooks,i18n,data,utils,edit-post,viewport}/build/*.{js,map} \ + {blocks,components,date,editor,element,hooks,i18n,data,utils,edit-post,viewport,plugins}/build/*.{js,map} \ {blocks,components,editor,edit-post}/build/*.css \ languages/gutenberg.pot \ languages/gutenberg-translations.php \ diff --git a/docs/extensibility.md b/docs/extensibility.md index e76bad0cebc145..9a7f5ad8b75e03 100644 --- a/docs/extensibility.md +++ b/docs/extensibility.md @@ -148,7 +148,7 @@ const Component = () => ( ); -wp.editPost.__experimental.registerPlugin( 'plugin-names', { +wp.plugins.__experimental.registerPlugin( 'plugin-names', { render: Component, } ); ``` diff --git a/edit-post/api/sidebar.js b/edit-post/api/sidebar.js new file mode 100644 index 00000000000000..f3e84555a17d13 --- /dev/null +++ b/edit-post/api/sidebar.js @@ -0,0 +1,10 @@ +import { deprecated } from '@wordpress/utils'; + +function __experimentalRegisterSidebar( name, settings ) { + deprecated( '__experimentalRegisterSidebar', { + alternative: 'wp.plugins.registerPlugin', + version: '2.6', + } ); + + +} \ No newline at end of file diff --git a/plugins/README.md b/plugins/README.md index 116bbc89956d31..dd209c157de797 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -1,7 +1,7 @@ -Edit post API +Plugins API ==== -The edit post API contains the following methods: +The plugins API contains the following methods: ### `wp.plugins.__experimental.registerPlugin( name: string, { render: function } )` @@ -27,7 +27,7 @@ const Component = () => ( ); -wp.editPost.__experimental.registerPlugin( 'plugin-names', { +wp.plugins.__experimental.registerPlugin( 'plugin-names', { render: Component, } ); ``` diff --git a/plugins/api/plugin.js b/plugins/api/plugin.js index c1a5de4b633c43..2bc7382f6157d8 100644 --- a/plugins/api/plugin.js +++ b/plugins/api/plugin.js @@ -5,7 +5,9 @@ */ import { applyFilters } from '@wordpress/hooks'; -/* External dependencies */ +/** + * External dependencies + */ import { isFunction } from 'lodash'; /** From dc717f8c6e78eddb00ed2d5c1b17f7709673bf2b Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Wed, 14 Mar 2018 17:11:43 +0100 Subject: [PATCH 68/83] Converted spaces to tabs in style.scss file --- .../components/plugin-sidebar/style.scss | 84 +++++++++---------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/edit-post/components/plugin-sidebar/style.scss b/edit-post/components/plugin-sidebar/style.scss index 14353006a455c8..eb380f765827c8 100644 --- a/edit-post/components/plugin-sidebar/style.scss +++ b/edit-post/components/plugin-sidebar/style.scss @@ -1,50 +1,50 @@ .edit-post-plugin-sidebar { - position: fixed; - z-index: z-index( '.edit-post-sidebar' ); - top: 0; - right: 0; - bottom: 0; - width: $sidebar-width; - border-left: 1px solid $light-gray-500; - background: $light-gray-300; - color: $dark-gray-500; - height: 100vh; - overflow: hidden; + position: fixed; + z-index: z-index( '.edit-post-sidebar' ); + top: 0; + right: 0; + bottom: 0; + width: $sidebar-width; + border-left: 1px solid $light-gray-500; + background: $light-gray-300; + color: $dark-gray-500; + height: 100vh; + overflow: hidden; - @include break-small() { - top: $admin-bar-height-big + $header-height; - z-index: z-index( '.edit-post-sidebar {greater than small}' ); - height: auto; - overflow: auto; - -webkit-overflow-scrolling: touch; - } + @include break-small() { + top: $admin-bar-height-big + $header-height; + z-index: z-index( '.edit-post-sidebar {greater than small}' ); + height: auto; + overflow: auto; + -webkit-overflow-scrolling: touch; + } - @include break-medium() { - top: $admin-bar-height + $header-height; - } + @include break-medium() { + top: $admin-bar-height + $header-height; + } - &__header { - padding: 0 8px 0 16px; - height: $panel-header-height; - border-bottom: 1px solid $light-gray-500; - display: flex; - align-items: center; + &__header { + padding: 0 8px 0 16px; + height: $panel-header-height; + border-bottom: 1px solid $light-gray-500; + display: flex; + align-items: center; - .components-icon-button { - margin-left: auto; - } - } + .components-icon-button { + margin-left: auto; + } + } - &__error { - padding: 16px; - margin: 0; - color: red; - } + &__error { + padding: 16px; + margin: 0; + color: red; + } - &__title { - font-size: 13px; - font-weight: 600; - color: inherit; - margin: 0; - } + &__title { + font-size: 13px; + font-weight: 600; + color: inherit; + margin: 0; + } } From 644b7e83c3c7519cc60ec39465a8863d6c09ddb1 Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Wed, 14 Mar 2018 17:29:37 +0100 Subject: [PATCH 69/83] Comply to coding style conventions regarding css in plugin-sidebar --- .../plugin-sidebar/sidebar-error-boundary.js | 2 +- .../components/plugin-sidebar/sidebar-layout.js | 8 ++++---- edit-post/components/plugin-sidebar/style.scss | 16 +++++++++------- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/edit-post/components/plugin-sidebar/sidebar-error-boundary.js b/edit-post/components/plugin-sidebar/sidebar-error-boundary.js index e354fc6d4f3a44..6bc60bc72a834d 100644 --- a/edit-post/components/plugin-sidebar/sidebar-error-boundary.js +++ b/edit-post/components/plugin-sidebar/sidebar-error-boundary.js @@ -17,7 +17,7 @@ class SidebarErrorBoundary extends Component { render() { if ( this.state.hasError ) { return ( -

+

{ sprintf( __( 'An error occurred rendering the plugin sidebar with id "%s".' ), this.props.pluginName ) }

); diff --git a/edit-post/components/plugin-sidebar/sidebar-layout.js b/edit-post/components/plugin-sidebar/sidebar-layout.js index cd72d24736481b..c6571a1122051e 100644 --- a/edit-post/components/plugin-sidebar/sidebar-layout.js +++ b/edit-post/components/plugin-sidebar/sidebar-layout.js @@ -9,19 +9,19 @@ import { __ } from '@wordpress/i18n'; const SidebarLayout = ( { onClose, title, children } ) => { return (
-
-

{ title }

+
+

{ title }

-
+
{ children }
diff --git a/edit-post/components/plugin-sidebar/style.scss b/edit-post/components/plugin-sidebar/style.scss index eb380f765827c8..9b0514d02db0e5 100644 --- a/edit-post/components/plugin-sidebar/style.scss +++ b/edit-post/components/plugin-sidebar/style.scss @@ -1,4 +1,4 @@ -.edit-post-plugin-sidebar { +.edit-post-plugin-sidebar__sidebar-layout { position: fixed; z-index: z-index( '.edit-post-sidebar' ); top: 0; @@ -35,12 +35,6 @@ } } - &__error { - padding: 16px; - margin: 0; - color: red; - } - &__title { font-size: 13px; font-weight: 600; @@ -48,3 +42,11 @@ margin: 0; } } + +.edit-post-plugin-sidebar__sidebar-error-boundary { + &__message { + padding: 16px; + margin: 0; + color: red; + } +} From 02a5924252019c5719a2e086ca72d829a3cd2882 Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Wed, 14 Mar 2018 17:51:39 +0100 Subject: [PATCH 70/83] Added slot to pluginsidebar fill for consistency --- edit-post/api/sidebar.js | 10 --------- edit-post/components/layout/index.js | 4 ++-- edit-post/components/plugin-sidebar/index.js | 22 ++++++++++---------- edit-post/index.js | 2 +- 4 files changed, 14 insertions(+), 24 deletions(-) delete mode 100644 edit-post/api/sidebar.js diff --git a/edit-post/api/sidebar.js b/edit-post/api/sidebar.js deleted file mode 100644 index f3e84555a17d13..00000000000000 --- a/edit-post/api/sidebar.js +++ /dev/null @@ -1,10 +0,0 @@ -import { deprecated } from '@wordpress/utils'; - -function __experimentalRegisterSidebar( name, settings ) { - deprecated( '__experimentalRegisterSidebar', { - alternative: 'wp.plugins.registerPlugin', - version: '2.6', - } ); - - -} \ No newline at end of file diff --git a/edit-post/components/layout/index.js b/edit-post/components/layout/index.js index 21e34ae1d933dc..87f41c5c2b5f24 100644 --- a/edit-post/components/layout/index.js +++ b/edit-post/components/layout/index.js @@ -32,7 +32,7 @@ import VisualEditor from '../visual-editor'; import EditorModeKeyboardShortcuts from '../keyboard-shortcuts'; import MetaBoxes from '../meta-boxes'; import { getMetaBoxContainer } from '../../utils/meta-boxes'; -import { PluginSidebar } from '../plugin-sidebar'; +import PluginSidebar from '../plugin-sidebar'; function Layout( { mode, @@ -82,7 +82,7 @@ function Layout( { /> ) } { editorSidebarOpened && } - { pluginSidebarOpened && } + { pluginSidebarOpened && }
diff --git a/edit-post/components/plugin-sidebar/index.js b/edit-post/components/plugin-sidebar/index.js index f3e59ffa1fdd09..fc3801b27235fb 100644 --- a/edit-post/components/plugin-sidebar/index.js +++ b/edit-post/components/plugin-sidebar/index.js @@ -77,16 +77,7 @@ class PluginSidebar extends Component { } } -/** - * The plugin sidebar slot. - * - * @return {ReactElement} The plugin sidebar slot. - */ -const PluginSidebarSlot = () => ( - -); - -const PluginSidebarFill = compose( [ +const WrappedPluginSidebar = compose( [ withSelect( select => { return { activePlugin: select( 'core/edit-post' ).getActiveGeneralSidebarName(), @@ -101,4 +92,13 @@ const PluginSidebarFill = compose( [ withPluginContext, ] )( PluginSidebar ); -export { PluginSidebarFill, PluginSidebarSlot as PluginSidebar }; +/** + * The plugin sidebar slot. + * + * @return {ReactElement} The plugin sidebar slot. + */ +WrappedPluginSidebar.Slot = () => ( + +); + +export default WrappedPluginSidebar; diff --git a/edit-post/index.js b/edit-post/index.js index f83dc5b31ba958..e22f28a07ad0d5 100644 --- a/edit-post/index.js +++ b/edit-post/index.js @@ -21,7 +21,7 @@ import Layout from './components/layout'; import store from './store'; import { initializeMetaBoxState } from './store/actions'; -import { PluginSidebarFill as PluginSidebar } from './components/plugin-sidebar'; +import PluginSidebar from './components/plugin-sidebar'; // Configure moment globally moment.locale( dateSettings.l10n.locale ); From 687e25c989b9d6439b5a204d9cdbf658ca25f27a Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Wed, 14 Mar 2018 18:04:21 +0100 Subject: [PATCH 71/83] Moved styles.scss under Internal dependencies --- .../components/plugin-sidebar/sidebar-error-boundary.js | 5 +++++ edit-post/components/plugin-sidebar/sidebar-layout.js | 7 +++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/edit-post/components/plugin-sidebar/sidebar-error-boundary.js b/edit-post/components/plugin-sidebar/sidebar-error-boundary.js index 6bc60bc72a834d..0e1e42535cb9c6 100644 --- a/edit-post/components/plugin-sidebar/sidebar-error-boundary.js +++ b/edit-post/components/plugin-sidebar/sidebar-error-boundary.js @@ -4,6 +4,11 @@ import { Component } from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; +/** + * Internal dependencies + */ +import './style.scss'; + class SidebarErrorBoundary extends Component { constructor( props ) { super( props ); diff --git a/edit-post/components/plugin-sidebar/sidebar-layout.js b/edit-post/components/plugin-sidebar/sidebar-layout.js index c6571a1122051e..56979f25b08b82 100644 --- a/edit-post/components/plugin-sidebar/sidebar-layout.js +++ b/edit-post/components/plugin-sidebar/sidebar-layout.js @@ -1,11 +1,14 @@ -import './style.scss'; - /** * WordPress dependencies */ import { IconButton } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; +/** + * Internal dependencies + */ +import './style.scss'; + const SidebarLayout = ( { onClose, title, children } ) => { return (
Date: Wed, 14 Mar 2018 19:26:29 +0100 Subject: [PATCH 72/83] removed unnecessary id from hidden div for plugin fills --- plugins/components/plugin-area/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/components/plugin-area/index.js b/plugins/components/plugin-area/index.js index a71dd2698bfce1..6dd1644b563d1c 100644 --- a/plugins/components/plugin-area/index.js +++ b/plugins/components/plugin-area/index.js @@ -22,7 +22,7 @@ class PluginArea extends Component { const pluginRegistry = PluginRegistry.getInstance(); return ( -
+
{ map( pluginRegistry.plugins, plugin => { const boundRegisterUIComponent = pluginRegistry.registerUIComponent.bind( pluginRegistry, plugin.name ); return ( From b435d4bc905031129a7500fdc956f39364cb1e25 Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Thu, 15 Mar 2018 11:51:33 +0100 Subject: [PATCH 73/83] Removed getPluginUIComponent API function in favor of 'plugin-sidebar/'-prefix in sidebar identifier --- edit-post/components/plugin-sidebar/index.js | 5 ++--- edit-post/store/selectors.js | 11 ++++------ edit-post/store/test/selectors.js | 14 +------------ plugins/api/plugin.js | 21 -------------------- 4 files changed, 7 insertions(+), 44 deletions(-) diff --git a/edit-post/components/plugin-sidebar/index.js b/edit-post/components/plugin-sidebar/index.js index fc3801b27235fb..68d46e8bc8481e 100644 --- a/edit-post/components/plugin-sidebar/index.js +++ b/edit-post/components/plugin-sidebar/index.js @@ -42,9 +42,8 @@ class PluginSidebar extends Component { */ componentWillMount() { if ( ! this.namespacedName ) { - this.namespacedName = `${ this.props.pluginContext.namespace }/${ this.props.name }`; + this.namespacedName = `plugin-sidebar/${ this.props.pluginContext.namespace }/${ this.props.name }`; } - this.props.pluginContext.registerUIComponent( this.namespacedName, 'sidebar' ); } /** @@ -69,7 +68,7 @@ class PluginSidebar extends Component { title={ props.title } onClose={ props.onClose } > - { cloneElement( Children.only( children ), newProps ) } + { typeof children === 'string' ? children : cloneElement( Children.only( children ), newProps ) } diff --git a/edit-post/store/selectors.js b/edit-post/store/selectors.js index d2a8602fe7ca79..1bc0679a5d379f 100644 --- a/edit-post/store/selectors.js +++ b/edit-post/store/selectors.js @@ -4,11 +4,6 @@ import createSelector from 'rememo'; import { includes, some } from 'lodash'; -/** - * WordPress dependencies - */ -import { __experimental } from '@wordpress/plugins'; - /** * Returns the current editing mode. * @@ -41,9 +36,11 @@ export function isEditorSidebarOpened( state ) { export function isPluginSidebarOpened( state ) { const activeGeneralSidebar = getPreference( state, 'activeGeneralSidebar', null ); - const uiComponent = __experimental.getRegisteredUIComponent( activeGeneralSidebar, 'sidebar' ); + if ( ! activeGeneralSidebar || typeof activeGeneralSidebar !== 'string' ) { + return false; + } - return !! uiComponent; + return activeGeneralSidebar.startsWith( 'plugin-sidebar/' ); } /** diff --git a/edit-post/store/test/selectors.js b/edit-post/store/test/selectors.js index 7cddd9dddd57f3..a541ff6c247256 100644 --- a/edit-post/store/test/selectors.js +++ b/edit-post/store/test/selectors.js @@ -13,19 +13,12 @@ import { isSavingMetaBoxes, getMetaBox, } from '../selectors'; -import { __experimental } from '@wordpress/plugins'; -const getRegisteredUIComponentMock = __experimental.getRegisteredUIComponent; jest.mock( '@wordpress/element', () => ( { compose: jest.fn().mockReturnValue( jest.fn() ), Component: jest.fn(), createElement: jest.fn(), } ) ); -jest.mock( '@wordpress/plugins', () => ( { - __experimental: { - getRegisteredUIComponent: jest.fn().mockReturnValue( null ), - }, -} ) ); describe( 'selectors', () => { describe( 'getEditorMode', () => { @@ -126,11 +119,7 @@ describe( 'selectors', () => { } ); it( 'should return true when the plugin sidebar is opened', () => { - getRegisteredUIComponentMock.mockReturnValueOnce( { - pluginName: 'my-plugin', - uiType: 'sidebar', - } ); - const name = 'my-plugin/my-sidebar'; + const name = 'plugin-sidebar/my-plugin/my-sidebar'; const state = { preferences: { activeGeneralSidebar: name, @@ -138,7 +127,6 @@ describe( 'selectors', () => { }; expect( isPluginSidebarOpened( state ) ).toBe( true ); - expect( getRegisteredUIComponentMock ).toHaveBeenCalledWith( name, 'sidebar' ); } ); } ); diff --git a/plugins/api/plugin.js b/plugins/api/plugin.js index 2bc7382f6157d8..dadb70a1e23c11 100644 --- a/plugins/api/plugin.js +++ b/plugins/api/plugin.js @@ -16,10 +16,8 @@ import { isFunction } from 'lodash'; class PluginRegistry { constructor() { this.plugins = {}; - this.pluginUIComponents = {}; this.registerPlugin = this.registerPlugin.bind( this ); - this.getRegisteredUIComponent = this.getRegisteredUIComponent.bind( this ); } /** @@ -84,25 +82,6 @@ class PluginRegistry { }; } - /** - * Get the registered plugin information, null if it doesn't exist. - * - * @param {string} uiNamespacedName The unique ui plugin identifier. - * @param {string?} uiType Optional UI type, will only return if ui type matches. - * - * @return {Object|null} Object containing plugin information null if the plugin doesn't exist. - */ - getRegisteredUIComponent( uiNamespacedName, uiType = null ) { - const uiComponent = this.pluginUIComponents[ uiNamespacedName ] || null; - if ( uiType ) { - if ( uiComponent && uiComponent.uiType === uiType ) { - return uiComponent; - } - return null; - } - return uiComponent; - } - /** * Get singleton instance. * From 90d0af81574487bd2c7daadb5ace29d7fe41f69c Mon Sep 17 00:00:00 2001 From: Alexander Botteram Date: Thu, 15 Mar 2018 16:08:46 +0100 Subject: [PATCH 74/83] Don't throw error on invalid PluginSidebar props --- edit-post/components/plugin-sidebar/index.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/edit-post/components/plugin-sidebar/index.js b/edit-post/components/plugin-sidebar/index.js index 68d46e8bc8481e..42453f7348e3a2 100644 --- a/edit-post/components/plugin-sidebar/index.js +++ b/edit-post/components/plugin-sidebar/index.js @@ -27,11 +27,18 @@ class PluginSidebar extends Component { constructor( props ) { super( props ); + this.state = { invalid: false }; + if ( typeof props.name !== 'string' ) { - throw 'Sidebar names must be strings.'; + // eslint-disable-next-line no-console + console.error( 'PluginSidebar name should be of type string' ); + this.state.invalid = true; } if ( ! /^[a-z][a-z0-9-]*$/.test( props.name ) ) { - throw 'Sidebar names must include only lowercase alphanumeric characters or dashes, and start with a letter. Example: "my-sidebar".'; + // eslint-disable-next-line no-console + console.error( 'Sidebar names must include only lowercase alphanumeric characters or dashes,' + + ' and start with a letter. Example: "my-sidebar".' ); + this.state.invalid = true; } } @@ -52,7 +59,7 @@ class PluginSidebar extends Component { * @return {ReactElement} The rendered component. */ render() { - if ( this.props.activePlugin !== this.namespacedName ) { + if ( this.props.activePlugin !== this.namespacedName || this.state.invalid ) { return null; } From 6540b05dc4a8fa84cc6fb6f1436bd6ccb52f6ca8 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Thu, 15 Mar 2018 14:45:49 -0400 Subject: [PATCH 75/83] Plugins: Refactor plugin UI registration as Slot/Fill --- edit-post/components/layout/index.js | 4 +- edit-post/components/plugin-sidebar/index.js | 99 ++++------------ edit-post/store/selectors.js | 15 +-- plugins/README.md | 2 +- plugins/api/index.js | 111 ++++++++++++++++-- plugins/api/plugin.js | 103 ---------------- plugins/api/test/{plugin.js => index.js} | 22 ++-- .../higher-order/with-plugin-context/index.js | 37 ------ plugins/components/index.js | 1 - plugins/components/plugin-area/index.js | 44 +++---- .../plugin-context-provider/index.js | 7 +- .../plugin-context-provider/test/context.js | 32 ----- 12 files changed, 166 insertions(+), 311 deletions(-) delete mode 100644 plugins/api/plugin.js rename plugins/api/test/{plugin.js => index.js} (87%) delete mode 100644 plugins/components/higher-order/with-plugin-context/index.js delete mode 100644 plugins/components/plugin-context-provider/test/context.js diff --git a/edit-post/components/layout/index.js b/edit-post/components/layout/index.js index 87f41c5c2b5f24..30de1adb50b945 100644 --- a/edit-post/components/layout/index.js +++ b/edit-post/components/layout/index.js @@ -38,6 +38,7 @@ function Layout( { mode, editorSidebarOpened, pluginSidebarOpened, + sidebarName, publishSidebarOpened, hasFixedToolbar, closePublishSidebar, @@ -82,7 +83,7 @@ function Layout( { /> ) } { editorSidebarOpened && } - { pluginSidebarOpened && } + { pluginSidebarOpened && }
@@ -94,6 +95,7 @@ export default compose( mode: select( 'core/edit-post' ).getEditorMode(), editorSidebarOpened: select( 'core/edit-post' ).isEditorSidebarOpened(), pluginSidebarOpened: select( 'core/edit-post' ).isPluginSidebarOpened(), + sidebarName: select( 'core/edit-post' ).getActiveGeneralSidebarName(), publishSidebarOpened: select( 'core/edit-post' ).isPublishSidebarOpened(), hasFixedToolbar: select( 'core/edit-post' ).isFeatureActive( 'fixedToolbar' ), metaBoxes: select( 'core/edit-post' ).getMetaBoxes(), diff --git a/edit-post/components/plugin-sidebar/index.js b/edit-post/components/plugin-sidebar/index.js index 42453f7348e3a2..f554a9e357e7e9 100644 --- a/edit-post/components/plugin-sidebar/index.js +++ b/edit-post/components/plugin-sidebar/index.js @@ -1,110 +1,57 @@ /** * WordPress dependencies */ -import { Component, Children, cloneElement, compose } from '@wordpress/element'; -import { Slot, Fill, withFocusReturn } from '@wordpress/components'; -import { withSelect, withDispatch } from '@wordpress/data'; -import { withPluginContext } from '@wordpress/plugins'; +import { compose } from '@wordpress/element'; +import { Slot, Fill, withFocusReturn, withContext } from '@wordpress/components'; +import { withDispatch } from '@wordpress/data'; /** * Internal dependencies */ import './style.scss'; import SidebarLayout from './sidebar-layout'; -import ErrorBoundary from './sidebar-error-boundary'; /** * Name of slot in which the sidebar should fill. * * @type {String} */ -const SLOT_NAME = 'PluginSidebar'; +const SLOT_NAME = 'plugin-sidebar'; /** - * The plugin sidebar fill. + * Renders the plugin sidebar fill. + * + * @return {WPElement} Plugin sidebar fill. */ -class PluginSidebar extends Component { - constructor( props ) { - super( props ); - - this.state = { invalid: false }; - - if ( typeof props.name !== 'string' ) { - // eslint-disable-next-line no-console - console.error( 'PluginSidebar name should be of type string' ); - this.state.invalid = true; - } - if ( ! /^[a-z][a-z0-9-]*$/.test( props.name ) ) { - // eslint-disable-next-line no-console - console.error( 'Sidebar names must include only lowercase alphanumeric characters or dashes,' + - ' and start with a letter. Example: "my-sidebar".' ); - this.state.invalid = true; - } - } - - /** - * Generates the UI plugin identifier by combining the plugin name and the sidebar name. - * - * Also registers the plugin in the plugin registry. - */ - componentWillMount() { - if ( ! this.namespacedName ) { - this.namespacedName = `plugin-sidebar/${ this.props.pluginContext.namespace }/${ this.props.name }`; - } - } - - /** - * Renders the PluginSidebar component. - * - * @return {ReactElement} The rendered component. - */ - render() { - if ( this.props.activePlugin !== this.namespacedName || this.state.invalid ) { - return null; - } - - const { children, ...props } = this.props; - const newProps = { - ...props, - namespacedName: this.namespacedName, - }; - - return ( - - - - { typeof children === 'string' ? children : cloneElement( Children.only( children ), newProps ) } - - - - ); - } +function PluginSidebar( { pluginName, name, title, onClose, children } ) { + return ( + + + { children } + + + ); } -const WrappedPluginSidebar = compose( [ - withSelect( select => { - return { - activePlugin: select( 'core/edit-post' ).getActiveGeneralSidebarName(), - }; - } ), +PluginSidebar = compose( [ withDispatch( dispatch => { return { onClose: dispatch( 'core/edit-post' ).closeGeneralSidebar, }; } ), withFocusReturn, - withPluginContext, + withContext( 'pluginName' )(), ] )( PluginSidebar ); /** * The plugin sidebar slot. * - * @return {ReactElement} The plugin sidebar slot. + * @return {WPElement} The plugin sidebar slot. */ -WrappedPluginSidebar.Slot = () => ( - +PluginSidebar.Slot = ( { name } ) => ( + ); -export default WrappedPluginSidebar; +export default PluginSidebar; diff --git a/edit-post/store/selectors.js b/edit-post/store/selectors.js index 1bc0679a5d379f..f34c0df9e5a2e4 100644 --- a/edit-post/store/selectors.js +++ b/edit-post/store/selectors.js @@ -34,13 +34,8 @@ export function isEditorSidebarOpened( state ) { * @return {boolean} Whether the plugin sidebar is opened. */ export function isPluginSidebarOpened( state ) { - const activeGeneralSidebar = getPreference( state, 'activeGeneralSidebar', null ); - - if ( ! activeGeneralSidebar || typeof activeGeneralSidebar !== 'string' ) { - return false; - } - - return activeGeneralSidebar.startsWith( 'plugin-sidebar/' ); + const activeGeneralSidebar = getActiveGeneralSidebarName( state ); + return !! activeGeneralSidebar && ! isEditorSidebarOpened( state ); } /** @@ -51,11 +46,7 @@ export function isPluginSidebarOpened( state ) { * @return {?string} Active general sidebar name. */ export function getActiveGeneralSidebarName( state ) { - const activeGeneralSidebar = getPreference( state, 'activeGeneralSidebar', null ); - - return activeGeneralSidebar && ( isEditorSidebarOpened( state ) || isPluginSidebarOpened( state ) ) ? - activeGeneralSidebar : - null; + return getPreference( state, 'activeGeneralSidebar', null ); } /** diff --git a/plugins/README.md b/plugins/README.md index dd209c157de797..ad31afbb6fee17 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -41,7 +41,7 @@ wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/second-sid ### Available UI components -The available UI components are found in the global variable `wp.editPost.__experimental`, and are React components. +The available UI components are found in the global variable `wp.plugins.__experimental`, and are React components. #### PluginSidebar diff --git a/plugins/api/index.js b/plugins/api/index.js index a3edefe8b97c67..bd9bca521a8d20 100644 --- a/plugins/api/index.js +++ b/plugins/api/index.js @@ -1,15 +1,106 @@ +/* eslint no-console: [ 'error', { allow: [ 'error' ] } ] */ + +/** + * WordPress dependencies + */ +import { applyFilters } from '@wordpress/hooks'; + +/** + * External dependencies + */ +import { isFunction } from 'lodash'; + +/** + * Plugin definitions keyed by plugin name. + * + * @type {Object.} + */ +const plugins = {}; + /** - * Internal dependencies + * Registers a plugin to the editor. + * + * @param {string} name The name of the plugin. + * @param {Object} settings The settings for this plugin. + * @param {Function} settings.render The function that renders the plugin. + * + * @return {Object} The final plugin settings object. */ -import PluginRegistry from './plugin'; +export function registerPlugin( name, settings ) { + if ( typeof settings !== 'object' ) { + console.error( + 'No settings object provided!' + ); + return null; + } + if ( typeof name !== 'string' ) { + console.error( + 'Plugin names must be strings.' + ); + 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".' + ); + return null; + } + if ( plugins[ name ] ) { + console.error( + `Plugin "${ name }" is already registered.` + ); + } + if ( ! isFunction( settings.render ) ) { + console.error( + 'The "render" property must be specified and must be a valid function.' + ); + return null; + } -const registry = PluginRegistry.getInstance(); + settings.name = name; + settings.sidebar = {}; -const __experimental = { - registerPlugin: registry.registerPlugin, - getRegisteredUIComponent: registry.getRegisteredUIComponent, -}; + settings = applyFilters( 'plugins.registerPlugin', settings, name ); -export { - __experimental, -}; + return plugins[ settings.name ] = settings; +} + +/** + * Unregisters a plugin by name. + * + * @param {string} name Plugin name. + * + * @return {?WPPlugin} The previous plugin settings object, if it has been + * successfully unregistered; otherwise `undefined`. + */ +export function unregisterPlugin( name ) { + if ( ! plugins[ name ] ) { + console.error( + 'Plugin "' + name + '" is not registered.' + ); + return; + } + const oldPlugin = plugins[ name ]; + delete plugins[ name ]; + return oldPlugin; +} + +/** + * Returns a registered plugin settings. + * + * @param {string} name Plugin name. + * + * @return {?Object} Plugin setting. + */ +export function getPlugin( name ) { + return plugins[ name ]; +} + +/** + * Returns all registered plugins. + * + * @return {Array} Plugin settings. + */ +export function getPlugins() { + return Object.values( plugins ); +} diff --git a/plugins/api/plugin.js b/plugins/api/plugin.js deleted file mode 100644 index dadb70a1e23c11..00000000000000 --- a/plugins/api/plugin.js +++ /dev/null @@ -1,103 +0,0 @@ -/* eslint no-console: [ 'error', { allow: [ 'error' ] } ] */ - -/** - * WordPress dependencies - */ -import { applyFilters } from '@wordpress/hooks'; - -/** - * External dependencies - */ -import { isFunction } from 'lodash'; - -/** - * Singleton class for registering plugins. - */ -class PluginRegistry { - constructor() { - this.plugins = {}; - - this.registerPlugin = this.registerPlugin.bind( this ); - } - - /** - * Registers a plugin to the editor. - * - * @param {string} name The name of the plugin. - * @param {Object} settings The settings for this plugin. - * @param {Function} settings.render The function that renders the plugin. - * - * @return {Object} The final plugin settings object. - */ - registerPlugin( name, settings ) { - if ( typeof settings !== 'object' ) { - console.error( - 'No settings object provided!' - ); - return null; - } - if ( typeof name !== 'string' ) { - console.error( - 'Plugin names must be strings.' - ); - 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".' - ); - return null; - } - if ( this.plugins[ name ] ) { - console.error( - `Plugin "${ name }" is already registered.` - ); - } - if ( ! isFunction( settings.render ) ) { - console.error( - 'The "render" property must be specified and must be a valid function.' - ); - return null; - } - - settings.name = name; - settings.sidebar = {}; - - settings = applyFilters( 'editPost.registerPlugin', settings, name ); - - return this.plugins[ settings.name ] = settings; - } - - /** - * A callback called by the UI components via context to register the UI component. - * - * @param {string} pluginName The plugin name. - * @param {string} uiNamespacedName The unique ui plugin identifier. - * @param {string} uiType The UI type. - */ - registerUIComponent( pluginName, uiNamespacedName, uiType ) { - this.pluginUIComponents[ uiNamespacedName ] = { - pluginName, - uiType, - }; - } - - /** - * Get singleton instance. - * - * @return {PluginRegistry} PluginRegistry instance. - */ - static getInstance() { - if ( ! this.instance ) { - this.instance = new PluginRegistry(); - } - return this.instance; - } - - static resetInstance() { - this.instance = new PluginRegistry(); - return this.instance; - } -} - -export default PluginRegistry; diff --git a/plugins/api/test/plugin.js b/plugins/api/test/index.js similarity index 87% rename from plugins/api/test/plugin.js rename to plugins/api/test/index.js index d211c277bbbd93..60aa1e72ec89d7 100644 --- a/plugins/api/test/plugin.js +++ b/plugins/api/test/index.js @@ -1,13 +1,19 @@ -import PluginRegistry from '../plugin'; - -let registerPlugin; - -beforeEach( () => { - const registry = PluginRegistry.resetInstance(); - registerPlugin = registry.registerPlugin; -} ); +/** + * Internal dependencies + */ +import { + registerPlugin, + unregisterPlugin, + getPlugins, +} from '../'; describe( 'registerPlugin', () => { + afterEach( () => { + getPlugins().forEach( ( plugin ) => { + unregisterPlugin( plugin.name ); + } ); + } ); + it( 'successfully registers a plugin', () => { registerPlugin( 'plugin', { render: () => 'plugin content', diff --git a/plugins/components/higher-order/with-plugin-context/index.js b/plugins/components/higher-order/with-plugin-context/index.js deleted file mode 100644 index 17b0ffc6a410e1..00000000000000 --- a/plugins/components/higher-order/with-plugin-context/index.js +++ /dev/null @@ -1,37 +0,0 @@ -/** - * External dependencies - */ -import { noop } from 'lodash'; - -/** - * WordPress dependencies - */ -import { Component } from '@wordpress/element'; - -/** - * Higher-order component creator that creates a new component and provides - * the plugin context provided by the PluginContextProvider component as - * props to that component under props.pluginContext. - * - * @param {ReactElement} WrappedComponent The component to be decorated. - * - * @return {PluginContextConsumer} The higher order component. - */ -function withPluginContext( WrappedComponent ) { - class PluginContextConsumer extends Component { - render() { - return ; - } - } - - PluginContextConsumer.contextTypes = { - pluginContext: noop, - }; - - return PluginContextConsumer; -} - -export default withPluginContext; diff --git a/plugins/components/index.js b/plugins/components/index.js index a2aa9492cb5eb1..e924080013b20e 100644 --- a/plugins/components/index.js +++ b/plugins/components/index.js @@ -1,3 +1,2 @@ export { default as withPluginContext } from './higher-order/with-plugin-context'; -export { default as PluginContextProvider } from './plugin-context-provider'; export { default as PluginArea } from './plugin-area'; diff --git a/plugins/components/plugin-area/index.js b/plugins/components/plugin-area/index.js index 6dd1644b563d1c..26020d1115a239 100644 --- a/plugins/components/plugin-area/index.js +++ b/plugins/components/plugin-area/index.js @@ -3,40 +3,32 @@ */ import { map } from 'lodash'; -/** - * WordPress dependencies - */ -import { Component } from '@wordpress/element'; - /** * Internal dependencies */ -import PluginRegistry from '../../api/plugin'; import PluginContextProvider from '../plugin-context-provider'; +import { getPlugins } from '../../api'; /** * A component that renders all plugin fills in a hidden div. + * + * @return {WPElement} Plugin area. */ -class PluginArea extends Component { - render() { - const pluginRegistry = PluginRegistry.getInstance(); - - return ( -
- { map( pluginRegistry.plugins, plugin => { - const boundRegisterUIComponent = pluginRegistry.registerUIComponent.bind( pluginRegistry, plugin.name ); - return ( - - { plugin.render() } - - ); - } ) } -
- ); - } +function PluginArea() { + return ( +
+ { map( getPlugins(), ( plugin ) => { + return ( + + { plugin.render() } + + ); + } ) } +
+ ); } export default PluginArea; diff --git a/plugins/components/plugin-context-provider/index.js b/plugins/components/plugin-context-provider/index.js index f9ea56038bac49..95cd6931087f64 100644 --- a/plugins/components/plugin-context-provider/index.js +++ b/plugins/components/plugin-context-provider/index.js @@ -9,13 +9,12 @@ import { noop } from 'lodash'; import { Component } from '@wordpress/element'; /** - * Provives context to plugins, in combination with the withPluginContext - * higher order component. + * Provides context to plugins, injecting name for slot name assignnment. */ class PluginContextProvider extends Component { getChildContext() { return { - pluginContext: this.props.value, + pluginName: this.props.pluginName, }; } @@ -25,7 +24,7 @@ class PluginContextProvider extends Component { } PluginContextProvider.childContextTypes = { - pluginContext: noop, + pluginName: noop, }; export default PluginContextProvider; diff --git a/plugins/components/plugin-context-provider/test/context.js b/plugins/components/plugin-context-provider/test/context.js deleted file mode 100644 index 420fdcfa71cef0..00000000000000 --- a/plugins/components/plugin-context-provider/test/context.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * External dependencies - */ -import { mount } from 'enzyme'; - -/** - * Internal dependencies - */ -import PluginContextProvider from '../index'; -import withPluginContext from '../../higher-order/with-plugin-context'; - -describe( 'plugin/context', () => { - describe( 'withPluginContext', () => { - it( 'passed the plugin context to the decorated component', () => { - const Component = ( props ) => { - return props.pluginContext; - }; - - const WrappedComponent = withPluginContext( Component ); - - const MountedComponent = mount( - -
- -
-
- ); - - expect( MountedComponent.find( Component ).props().pluginContext ).toEqual( 'plugin-namespace' ); - } ); - } ); -} ); From f127727d2617e7aa591355b75d25af0a75f89152 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Thu, 15 Mar 2018 14:51:34 -0400 Subject: [PATCH 76/83] Plugins: Remove unused code --- .../plugin-sidebar/sidebar-error-boundary.js | 34 ------------------- edit-post/store/test/selectors.js | 6 ---- plugins/components/index.js | 2 +- 3 files changed, 1 insertion(+), 41 deletions(-) delete mode 100644 edit-post/components/plugin-sidebar/sidebar-error-boundary.js diff --git a/edit-post/components/plugin-sidebar/sidebar-error-boundary.js b/edit-post/components/plugin-sidebar/sidebar-error-boundary.js deleted file mode 100644 index 0e1e42535cb9c6..00000000000000 --- a/edit-post/components/plugin-sidebar/sidebar-error-boundary.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * WordPress dependencies - */ -import { Component } from '@wordpress/element'; -import { __, sprintf } from '@wordpress/i18n'; - -/** - * Internal dependencies - */ -import './style.scss'; - -class SidebarErrorBoundary extends Component { - constructor( props ) { - super( props ); - this.state = { hasError: false }; - } - - componentDidCatch() { - this.setState( { hasError: true } ); - } - - render() { - if ( this.state.hasError ) { - return ( -

- { sprintf( __( 'An error occurred rendering the plugin sidebar with id "%s".' ), this.props.pluginName ) } -

- ); - } - return this.props.children; - } -} - -export default SidebarErrorBoundary; diff --git a/edit-post/store/test/selectors.js b/edit-post/store/test/selectors.js index a541ff6c247256..6c2dbbd87e8ebb 100644 --- a/edit-post/store/test/selectors.js +++ b/edit-post/store/test/selectors.js @@ -14,12 +14,6 @@ import { getMetaBox, } from '../selectors'; -jest.mock( '@wordpress/element', () => ( { - compose: jest.fn().mockReturnValue( jest.fn() ), - Component: jest.fn(), - createElement: jest.fn(), -} ) ); - describe( 'selectors', () => { describe( 'getEditorMode', () => { it( 'should return the selected editor mode', () => { diff --git a/plugins/components/index.js b/plugins/components/index.js index e924080013b20e..f126d903a3b76e 100644 --- a/plugins/components/index.js +++ b/plugins/components/index.js @@ -1,2 +1,2 @@ -export { default as withPluginContext } from './higher-order/with-plugin-context'; export { default as PluginArea } from './plugin-area'; +export { default as PluginContextProvider } from './plugin-context-provider'; From cdc51c580cdb0fa9180af12043faa1e4bb28b459 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Thu, 15 Mar 2018 14:55:27 -0400 Subject: [PATCH 77/83] Plugins: Represent plugin render as the component it is Use createElement --- plugins/components/plugin-area/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/components/plugin-area/index.js b/plugins/components/plugin-area/index.js index 26020d1115a239..fbb869f65040a5 100644 --- a/plugins/components/plugin-area/index.js +++ b/plugins/components/plugin-area/index.js @@ -18,12 +18,14 @@ function PluginArea() { return (
{ map( getPlugins(), ( plugin ) => { + const { render: Plugin } = plugin; + return ( - { plugin.render() } + ); } ) } From 5488d066a193020b7ca4de5aea5ab2df445d942c Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Thu, 15 Mar 2018 14:56:56 -0400 Subject: [PATCH 78/83] Plugins: Revert plugin sidebar slot name to PluginSidebar Apparently the convention we established (see: Popover) --- edit-post/components/plugin-sidebar/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edit-post/components/plugin-sidebar/index.js b/edit-post/components/plugin-sidebar/index.js index f554a9e357e7e9..9ec20307bb3eb9 100644 --- a/edit-post/components/plugin-sidebar/index.js +++ b/edit-post/components/plugin-sidebar/index.js @@ -16,7 +16,7 @@ import SidebarLayout from './sidebar-layout'; * * @type {String} */ -const SLOT_NAME = 'plugin-sidebar'; +const SLOT_NAME = 'PluginSidebar'; /** * Renders the plugin sidebar fill. From 9ee20f18a271d1b02ba7b3b2ba4d6dc2be7ef1ae Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Fri, 16 Mar 2018 08:15:33 -0400 Subject: [PATCH 79/83] Plugins: Remove unused error boundary styles --- edit-post/components/plugin-sidebar/style.scss | 8 -------- 1 file changed, 8 deletions(-) diff --git a/edit-post/components/plugin-sidebar/style.scss b/edit-post/components/plugin-sidebar/style.scss index 9b0514d02db0e5..13e3611bad3956 100644 --- a/edit-post/components/plugin-sidebar/style.scss +++ b/edit-post/components/plugin-sidebar/style.scss @@ -42,11 +42,3 @@ margin: 0; } } - -.edit-post-plugin-sidebar__sidebar-error-boundary { - &__message { - padding: 16px; - margin: 0; - color: red; - } -} From ce9b374a474056a67b889cf36e8e72f87f6303c0 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Fri, 16 Mar 2018 08:15:43 -0400 Subject: [PATCH 80/83] Plugin: Remove unused sidebar settings --- plugins/api/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/api/index.js b/plugins/api/index.js index bd9bca521a8d20..91c222b68f9561 100644 --- a/plugins/api/index.js +++ b/plugins/api/index.js @@ -58,7 +58,6 @@ export function registerPlugin( name, settings ) { } settings.name = name; - settings.sidebar = {}; settings = applyFilters( 'plugins.registerPlugin', settings, name ); From a322b70e42852e062623dacbad1c61276b4f6f8e Mon Sep 17 00:00:00 2001 From: Anton Timmermans Date: Fri, 16 Mar 2018 14:14:06 +0100 Subject: [PATCH 81/83] Fix plugin sidebar with on mobile The className for the plugin sidebar was changed in 644b7e83c. But this was missed in one location. This commit changes the class there too. --- edit-post/components/sidebar/style.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edit-post/components/sidebar/style.scss b/edit-post/components/sidebar/style.scss index e5f4cf734f6bdc..8ea6e074f446ae 100644 --- a/edit-post/components/sidebar/style.scss +++ b/edit-post/components/sidebar/style.scss @@ -90,7 +90,7 @@ .edit-post-layout.is-sidebar-opened { .edit-post-sidebar, - .edit-post-plugins-panel { + .edit-post-plugin-sidebar__sidebar-layout { /* Sidebar covers screen on mobile */ width: 100%; From 853655cec8633cc38745874be445ff8945c49482 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Fri, 16 Mar 2018 13:50:52 -0400 Subject: [PATCH 82/83] Documentation: Remove experimental flag from plugins --- docs/extensibility.md | 58 +++---------------------------------------- plugins/README.md | 47 +++++++++++++++++++---------------- 2 files changed, 28 insertions(+), 77 deletions(-) diff --git a/docs/extensibility.md b/docs/extensibility.md index 9a7f5ad8b75e03..00acc529100046 100644 --- a/docs/extensibility.md +++ b/docs/extensibility.md @@ -120,60 +120,8 @@ wp.hooks.addFilter( _Note:_ This filter must always be run on every page load, and not in your browser's developer tools console. Otherwise, a [block validation](https://wordpress.org/gutenberg/handbook/block-api/block-edit-save/#validation) error will occur the next time the post is edited. This is due to the fact that block validation occurs by verifying that the saved output matches what is stored in the post's content during editor initialization. So, if this filter does not exist when the editor loads, the block will be marked as invalid. -## Extending the editor's UI (Experimental) +## Extending the Editor UI -Extending the editor's UI is done with in `registerPlugin` API, and allows you to define all your plugin's UI elements in one place. +Extending the editor UI can be accomplished with the `registerPlugin` API, allowing you to define all your plugin's UI elements in one place. -### `wp.plugins.__experimental.registerPlugin( name: string, { render: function } )` - -This method takes two arguments: -1. `name`: A string identifying the plugin. Must be unique across all registered plugins. -2. `settings`: An object containing the following data: - - `render`: A component containing the UI elements to be rendered. See the list below for all available UI elements. - -**Example** - -```jsx -const { Fragment } = wp.element; -const { PluginSidebar } = wp.editPost.__experimental; - -const Component = () => ( - - -

Content of the first sidebar

-
- -

Content of the second sidebar

-
-
-); - -wp.plugins.__experimental.registerPlugin( 'plugin-names', { - render: Component, -} ); -``` - -You can activate the sidebars using the following lines: - -```js -wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/first-sidebar-name' ); -wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/second-sidebar-name' ); -``` - -### Available UI components - -The available UI components are found in the global variable `wp.editPost.__experimental`, and are React components. - -#### PluginSidebar - -Renders a sidebar when activated. -```jsx - - - -``` -- Props - - `name`: A string identifying the sidebar. Must be unique for every sidebar registered within the scope of your plugin. - - `title`: Title displayed at the top of the sidebar. Must be a string. - -The contents you render within the `PluginSidebar` will show up as content within the sidebar. +Refer to [the plugins module documentation](../plugins/) for more information. diff --git a/plugins/README.md b/plugins/README.md index ad31afbb6fee17..886a8556e35471 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -3,32 +3,34 @@ Plugins API The plugins API contains the following methods: -### `wp.plugins.__experimental.registerPlugin( name: string, { render: function } )` +### `wp.plugins.registerPlugin( name: string, settings: Object )` This method takes two arguments: + 1. `name`: A string identifying the plugin. Must be unique across all registered plugins. 2. `settings`: An object containing the following data: - `render`: A component containing the UI elements to be rendered. See the list below for all available UI elements. - + **Example** ```jsx const { Fragment } = wp.element; const { PluginSidebar } = wp.editPost.__experimental; +const { registerPlugin } = wp.plugins; const Component = () => ( - - -

Content of the first sidebar

-
- -

Content of the second sidebar

-
-
+ + + Content of the first sidebar + + + Content of the second sidebar + + ); -wp.plugins.__experimental.registerPlugin( 'plugin-names', { - render: Component, +registerPlugin( 'plugin-name', { + render: Component, } ); ``` @@ -38,21 +40,22 @@ You can activate the sidebars using the following lines: wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/first-sidebar-name' ); wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'plugin-name/second-sidebar-name' ); ``` - -### Available UI components -The available UI components are found in the global variable `wp.plugins.__experimental`, and are React components. +### Components + +The following components are found in the global variable `wp.plugins` when defining `wp-plugins` as a script dependency. #### PluginSidebar -Renders a sidebar when activated. +Renders a sidebar when activated. The contents within the `PluginSidebar` will appear as content within the sidebar. + ```jsx - + ``` -- Props - - `name`: A string identifying the sidebar. Must be unique for every sidebar registered within the scope of your plugin. - - `title`: Title displayed at the top of the sidebar. Must be a string. - -The contents you render within the `PluginSidebar` will show up as content within the sidebar. + +`PluginSidebar` accepts the following props: + +- `name`: A string identifying the sidebar. Must be unique for every sidebar registered within the scope of your plugin. +- `title`: Title displayed at the top of the sidebar. Must be a string. From a1de97b18f8c7f4ecd00b8147452bd220ac2a049 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Fri, 16 Mar 2018 13:51:17 -0400 Subject: [PATCH 83/83] Testing: Include plugins folder in ESLint import rule --- .eslintrc.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.eslintrc.js b/.eslintrc.js index 09d97476dfe722..573af215844d63 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -78,6 +78,10 @@ module.exports = { selector: 'ImportDeclaration[source.value=/^viewport$/]', message: 'Use @wordpress/viewport as import path instead.', }, + { + selector: 'ImportDeclaration[source.value=/^plugins$/]', + message: 'Use @wordpress/plugins as import path instead.', + }, { selector: 'CallExpression[callee.name="deprecated"] Property[key.name="version"][value.value=/' + majorMinorRegExp + '/]', message: 'Deprecated functions must be removed before releasing this version.',