Skip to content

Commit

Permalink
Add an API to add a plugin sidebar (new) (#4777)
Browse files Browse the repository at this point in the history
* Make it possible for plugins to render a sidebar

* Fix sidebar styling

* Remove unused mobile middleware

* Fix editor mode switching

* Fix editorMode switch test

* Fix documentation

* Change `@returns` into `@return`

* Add storeKey to make the plugins panel connect to the correct store

* Rename 'plugins' to 'plugin' in selector

* Set editor as default activeGeneralSidebar

* Unexpose getSidebarSettings

* Remove duplicate scss and remove renderSidebar function

* Fix unittests

* Fix header height, font and close button

* Update index.js

* Edit Post: Remove no longer used getPluginSidebar

* Edit Post: Remove no longer used imports from PluginsPanel

* Edit Post: Update Layout to handle missing plugin properly

* Edit Post: Remove obsolete blank line
  • Loading branch information
IreneStr authored and gziolo committed Feb 21, 2018
1 parent 3f43e32 commit 25de703
Show file tree
Hide file tree
Showing 28 changed files with 630 additions and 345 deletions.
4 changes: 4 additions & 0 deletions edit-post/api/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export {
registerSidebar,
activateSidebar,
} from './sidebar';
98 changes: 98 additions & 0 deletions edit-post/api/sidebar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/* eslint no-console: [ 'error', { allow: [ 'error' ] } ] */

/* External dependencies */
import { isFunction } from 'lodash';

/* Internal dependencies */
import store from '../store';
import { setGeneralSidebarActivePanel, openGeneralSidebar } from '../store/actions';
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;
}

/**
* 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 ) );
}
39 changes: 25 additions & 14 deletions edit-post/components/header/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,25 @@ import {
import './style.scss';
import EllipsisMenu from './ellipsis-menu';
import HeaderToolbar from './header-toolbar';
import { isSidebarOpened } from '../../store/selectors';
import { toggleSidebar } from '../../store/actions';
import {
getOpenedGeneralSidebar,
isPublishSidebarOpened,
} from '../../store/selectors';
import {
openGeneralSidebar,
closeGeneralSidebar,
togglePublishSidebar,
} from '../../store/actions';

function Header( {
onToggleDefaultSidebar,
isGeneralSidebarEditorOpen,
onOpenGeneralSidebar,
onCloseGeneralSidebar,
isPublishSidebarOpen,
onTogglePublishSidebar,
isDefaultSidebarOpened,
isPublishSidebarOpened,
} ) {
const toggleGeneralSidebar = isGeneralSidebarEditorOpen ? onCloseGeneralSidebar : onOpenGeneralSidebar;

return (
<div
role="region"
Expand All @@ -37,20 +47,20 @@ function Header( {
tabIndex="-1"
>
<HeaderToolbar />
{ ! isPublishSidebarOpened && (
{ ! isPublishSidebarOpen && (
<div className="edit-post-header__settings">
<PostSavedState />
<PostPreviewButton />
<PostPublishPanelToggle
isOpen={ isPublishSidebarOpened }
isOpen={ isPublishSidebarOpen }
onToggle={ onTogglePublishSidebar }
/>
<IconButton
icon="admin-generic"
onClick={ onToggleDefaultSidebar }
isToggled={ isDefaultSidebarOpened }
onClick={ toggleGeneralSidebar }
isToggled={ isGeneralSidebarEditorOpen }
label={ __( 'Settings' ) }
aria-expanded={ isDefaultSidebarOpened }
aria-expanded={ isGeneralSidebarEditorOpen }
/>
<EllipsisMenu key="ellipsis-menu" />
</div>
Expand All @@ -61,12 +71,13 @@ function Header( {

export default connect(
( state ) => ( {
isDefaultSidebarOpened: isSidebarOpened( state ),
isPublishSidebarOpened: isSidebarOpened( state, 'publish' ),
isGeneralSidebarEditorOpen: getOpenedGeneralSidebar( state ) === 'editor',
isPublishSidebarOpen: isPublishSidebarOpened( state ),
} ),
{
onToggleDefaultSidebar: () => toggleSidebar(),
onTogglePublishSidebar: () => toggleSidebar( 'publish' ),
onOpenGeneralSidebar: () => openGeneralSidebar( 'editor' ),
onCloseGeneralSidebar: closeGeneralSidebar,
onTogglePublishSidebar: togglePublishSidebar,
},
undefined,
{ storeKey: 'edit-post' }
Expand Down
52 changes: 37 additions & 15 deletions edit-post/components/layout/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,41 @@ import VisualEditor from '../modes/visual-editor';
import EditorModeKeyboardShortcuts from '../modes/keyboard-shortcuts';
import {
getEditorMode,
hasFixedToolbar,
hasOpenSidebar,
isSidebarOpened,
isFeatureActive,
getOpenedGeneralSidebar,
isPublishSidebarOpened,
getActivePlugin,
} from '../../store/selectors';
import { toggleSidebar } from '../../store/actions';
import { closePublishSidebar } from '../../store/actions';
import PluginsPanel from '../../components/plugins-panel/index.js';
import { getSidebarSettings } from '../../api/sidebar';

function GeneralSidebar( { openedGeneralSidebar } ) {
switch ( openedGeneralSidebar ) {
case 'editor':
return <Sidebar />;
case 'plugin':
return <PluginsPanel />;
default:
}
return null;
}

function Layout( {
mode,
layoutHasOpenSidebar,
isDefaultSidebarOpened,
isPublishSidebarOpened,
fixedToolbarActive,
onClosePublishPanel,
publishSidebarOpen,
openedGeneralSidebar,
hasFixedToolbar,
onClosePublishSidebar,
plugin,
} ) {
const isSidebarOpened = layoutHasOpenSidebar &&
( openedGeneralSidebar !== 'plugin' || getSidebarSettings( plugin ) );
const className = classnames( 'edit-post-layout', {
'is-sidebar-opened': layoutHasOpenSidebar,
'has-fixed-toolbar': fixedToolbarActive,
'is-sidebar-opened': isSidebarOpened,
'has-fixed-toolbar': hasFixedToolbar,
} );

return (
Expand All @@ -68,8 +86,11 @@ function Layout( {
<MetaBoxes location="advanced" />
</div>
</div>
{ isDefaultSidebarOpened && <Sidebar /> }
{ isPublishSidebarOpened && <PostPublishPanel onClose={ onClosePublishPanel } /> }
{ publishSidebarOpen && <PostPublishPanel onClose={ onClosePublishSidebar } /> }
{
openedGeneralSidebar !== null && <GeneralSidebar
openedGeneralSidebar={ openedGeneralSidebar } />
}
<Popover.Slot />
</div>
);
Expand All @@ -79,12 +100,13 @@ export default connect(
( state ) => ( {
mode: getEditorMode( state ),
layoutHasOpenSidebar: hasOpenSidebar( state ),
isDefaultSidebarOpened: isSidebarOpened( state ),
isPublishSidebarOpened: isSidebarOpened( state, 'publish' ),
fixedToolbarActive: hasFixedToolbar( state ),
openedGeneralSidebar: getOpenedGeneralSidebar( state ),
publishSidebarOpen: isPublishSidebarOpened( state ),
hasFixedToolbar: isFeatureActive( state, 'fixedToolbar' ),
plugin: getActivePlugin( state ),
} ),
{
onClosePublishPanel: () => toggleSidebar( 'publish', false ),
onClosePublishSidebar: closePublishSidebar,
},
undefined,
{ storeKey: 'edit-post' }
Expand Down
33 changes: 11 additions & 22 deletions edit-post/components/modes/visual-editor/block-inspector-button.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,39 +13,31 @@ import { IconButton, withSpokenMessages } from '@wordpress/components';
/**
* Internal dependencies
*/
import { getActivePanel, isSidebarOpened } from '../../../store/selectors';
import { toggleSidebar, setActivePanel } from '../../../store/actions';
import { getActiveEditorPanel, isGeneralSidebarPanelOpened } from '../../../store/selectors';
import { openGeneralSidebar } from '../../../store/actions';

export function BlockInspectorButton( {
isDefaultSidebarOpened,
isGeneralSidebarEditorOpened,
onOpenGeneralSidebarEditor,
panel,
toggleDefaultSidebar,
onShowInspector,
onClick = noop,
small = false,
speak,
} ) {
const toggleInspector = () => {
onShowInspector();
if ( ! isDefaultSidebarOpened || panel === 'block' ) {
toggleDefaultSidebar();
}
};

const speakMessage = () => {
if ( ! isDefaultSidebarOpened || ( isDefaultSidebarOpened && panel !== 'block' ) ) {
if ( ! isGeneralSidebarEditorOpened || ( isGeneralSidebarEditorOpened && panel !== 'block' ) ) {
speak( __( 'Additional settings are now available in the Editor advanced settings sidebar' ) );
} else {
speak( __( 'Advanced settings closed' ) );
}
};

const label = ( isDefaultSidebarOpened && panel === 'block' ) ? __( 'Hide Advanced Settings' ) : __( 'Show Advanced Settings' );
const label = ( isGeneralSidebarEditorOpened && panel === 'block' ) ? __( 'Hide Advanced Settings' ) : __( 'Show Advanced Settings' );

return (
<IconButton
className="editor-block-settings-menu__control"
onClick={ flow( toggleInspector, speakMessage, onClick ) }
onClick={ flow( onOpenGeneralSidebarEditor, speakMessage, onClick ) }
icon="admin-generic"
label={ small ? label : undefined }
>
Expand All @@ -56,15 +48,12 @@ export function BlockInspectorButton( {

export default connect(
( state ) => ( {
isDefaultSidebarOpened: isSidebarOpened( state ),
panel: getActivePanel( state ),
isGeneralSidebarEditorOpened: isGeneralSidebarPanelOpened( state, 'editor' ),
panel: getActiveEditorPanel( state ),
} ),
( dispatch ) => ( {
onShowInspector() {
dispatch( setActivePanel( 'block' ) );
},
toggleDefaultSidebar() {
dispatch( toggleSidebar() );
onOpenGeneralSidebarEditor() {
dispatch( openGeneralSidebar( 'editor', 'block' ) );
},
} ),
undefined,
Expand Down
63 changes: 63 additions & 0 deletions edit-post/components/plugins-panel/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/**
* 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';

function PluginsPanel( { onClose, plugin } ) {
const pluginSidebar = getSidebarSettings( plugin );

if ( ! pluginSidebar ) {
return null;
}

const {
title,
render,
} = pluginSidebar;

return (
<div
className="edit-post-sidebar edit-post-plugins-panel"
role="region"
aria-label={ __( 'Editor plugins' ) }
tabIndex="-1">
<div className="edit-post-plugins-panel__header">
<h3>{ title }</h3>
<IconButton
onClick={ onClose }
icon="no-alt"
label={ __( 'Close settings' ) }
/>
</div>
<div className="edit-post-plugins-panel__content">
{ render() }
</div>
</div>
);
}

export default connect(
( state ) => {
return {
plugin: getActivePlugin( state ),
};
}, {
onClose: closeGeneralSidebar,
},
undefined,
{ storeKey: 'edit-post' }
)( withFocusReturn( PluginsPanel ) );
Loading

0 comments on commit 25de703

Please sign in to comment.